Skip to content

Audit fixes: MLX chat template, Ollama export preflight, migration intent preservation, smol-135m warning #145

Audit fixes: MLX chat template, Ollama export preflight, migration intent preservation, smol-135m warning

Audit fixes: MLX chat template, Ollama export preflight, migration intent preservation, smol-135m warning #145

Workflow file for this run

name: CI
on:
push:
branches: [trunk]
pull_request:
branches: [trunk]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
UV_VERSION: "0.11.6"
PYTHON_VERSION: "3.11"
# Pinned to BASE_MODELS["smollm2-135m"].revision (Sprint 06 registry).
TINY_MODEL_REVISION: "12fd25f77366fa6b3b4b768ec3050bf629380bac"
jobs:
lint-type-test:
name: lint / typecheck / test (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v4
with:
version: ${{ env.UV_VERSION }}
- name: Sync dependencies
run: uv sync --all-extras --dev
- name: Install minisign (for share/signing coverage)
# The signing code path probes `shutil.which("minisign")` and
# refuses with a typed error when absent. CI installs it so the
# "available → sign/verify" branch runs alongside the "absent"
# refusal branch that's exercised on developer machines without
# it. Best-effort: if the install fails (e.g. Homebrew rate
# limit), tests still pass via the refusal path.
run: |
if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then
sudo apt-get update -qq
sudo apt-get install -y minisign || true
elif [ "${{ matrix.os }}" = "macos-latest" ]; then
brew install minisign || true
fi
command -v minisign && minisign -v || echo "minisign not available; tests use the refusal path"
- name: Ruff lint
run: uv run ruff check .
- name: Ruff format check
run: uv run ruff format --check .
- name: Mypy
run: uv run mypy src/dlm
- name: Pytest (unit + integration, non-slow)
run: uv run pytest
- name: Coverage gate — src/dlm/doc = 100% (audit 02 M4)
if: matrix.os == 'ubuntu-latest'
run: |
uv run pytest tests/unit/doc \
--cov=src/dlm/doc \
--cov-report=term-missing \
--cov-fail-under=100
- name: Coverage gate — src/dlm/store = 100% (Sprint 04)
if: matrix.os == 'ubuntu-latest'
run: |
uv run pytest tests/unit/store \
--cov=src/dlm/store \
--cov-report=term-missing \
--cov-fail-under=100
- name: Coverage gate — src/dlm/hardware = 100% (Sprint 05)
if: matrix.os == 'ubuntu-latest'
run: |
uv run pytest tests/unit/hardware \
--cov=src/dlm/hardware \
--cov-report=term-missing \
--cov-fail-under=100
- name: Coverage gate — src/dlm/base_models = 100% (Sprint 06)
if: matrix.os == 'ubuntu-latest'
run: |
uv run pytest tests/unit/base_models \
--cov=src/dlm/base_models \
--cov-report=term-missing \
--cov-fail-under=100
- name: Coverage gate — src/dlm/data = 100% (Sprint 07)
if: matrix.os == 'ubuntu-latest'
run: |
uv run pytest tests/unit/data \
--cov=src/dlm/data \
--cov-report=term-missing \
--cov-fail-under=100
- name: Coverage gate — src/dlm/replay = 100% (Sprint 08)
if: matrix.os == 'ubuntu-latest'
run: |
uv run pytest tests/unit/replay \
--cov=src/dlm/replay \
--cov-report=term-missing \
--cov-fail-under=100
- name: Coverage gate — src/dlm/train = 100% (Sprint 09)
if: matrix.os == 'ubuntu-latest'
run: |
uv run pytest tests/unit/train \
--cov=src/dlm/train \
--cov-report=term-missing \
--cov-fail-under=100
- name: Coverage gate — src/dlm/train/preference = 100%
if: matrix.os == 'ubuntu-latest'
run: |
uv run pytest tests/unit/train/preference \
--cov=src/dlm/train/preference \
--cov-report=term-missing \
--cov-fail-under=100
- name: Coverage gate — src/dlm/eval = 100% (Sprint 10)
if: matrix.os == 'ubuntu-latest'
run: |
uv run pytest tests/unit/eval \
--cov=src/dlm/eval \
--cov-report=term-missing \
--cov-fail-under=100
- name: Coverage gate — src/dlm/inference = 100% (Sprint 10)
if: matrix.os == 'ubuntu-latest'
run: |
uv run pytest tests/unit/inference \
--cov=src/dlm/inference \
--cov-report=term-missing \
--cov-fail-under=100
- name: Coverage gate — src/dlm/export = 100% (Sprint 11)
if: matrix.os == 'ubuntu-latest'
run: |
uv run pytest tests/unit/export \
--cov=src/dlm/export \
--cov-report=term-missing \
--cov-fail-under=100
- name: Coverage gate — src/dlm/export/ollama = 100% (Sprint 12)
if: matrix.os == 'ubuntu-latest'
run: |
uv run pytest tests/unit/export/ollama \
--cov=src/dlm/export/ollama \
--cov-report=term-missing \
--cov-fail-under=100
- name: Coverage gate — src/dlm/cli/reporter = 100% (Sprint 13)
if: matrix.os == 'ubuntu-latest'
run: |
uv run pytest tests/unit/cli \
--cov=dlm.cli.reporter \
--cov-report=term-missing \
--cov-fail-under=100
- name: Coverage gate — src/dlm/io/ulid = 100% (Sprint 13)
if: matrix.os == 'ubuntu-latest'
run: |
uv run pytest tests/unit/test_io_ulid.py \
--cov=dlm.io.ulid \
--cov-report=term-missing \
--cov-fail-under=100
- name: Coverage gate — src/dlm/pack = 100% (Sprint 14)
if: matrix.os == 'ubuntu-latest'
run: |
uv run pytest tests/unit/pack tests/integration/pack \
--cov=src/dlm/pack \
--cov-report=term-missing \
--cov-fail-under=100
- name: Coverage gate — src/dlm/lock = 100% (Sprint 15)
if: matrix.os == 'ubuntu-latest'
run: |
uv run pytest tests/unit/lock \
--cov=src/dlm/lock \
--cov-report=term-missing \
--cov-fail-under=100
no-network-sandbox:
# audit F13: dlm init / doctor / show must work with zero outbound network.
name: no-network sandbox (ubuntu-latest)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v4
with:
version: ${{ env.UV_VERSION }}
- name: Sync dependencies (before blocking network)
run: uv sync --all-extras --dev
- name: Block egress then exercise local-only CLI commands
env:
# Belt-and-braces: force HF / transformers offline posture.
HF_HUB_OFFLINE: "1"
TRANSFORMERS_OFFLINE: "1"
HF_DATASETS_OFFLINE: "1"
run: |
set -euxo pipefail
# ALWAYS flush OUTPUT on exit — otherwise the post-step hooks
# (cache upload, artifact collection) lose the runner's
# heartbeat to GitHub Actions and the job fails with
# "hosted runner lost communication with the server".
trap 'sudo iptables -F OUTPUT || true' EXIT
# Drop all non-loopback egress. Commands that try to reach out
# will fail — CI fails if any currently-"local-only" command
# attempts network.
sudo iptables -A OUTPUT -o lo -j ACCEPT
sudo iptables -A OUTPUT -d 127.0.0.0/8 -j ACCEPT
sudo iptables -A OUTPUT -j REJECT
# Sanity check: confirm egress is blocked.
(! curl --max-time 3 -sS https://example.com -o /dev/null) || (echo "egress not blocked" && exit 1)
# Exercise CLI surfaces that must be local-only at this sprint.
uv run dlm --version
uv run dlm --help
# Sprint 05 landed: `dlm doctor` probes torch + psutil only
# and emits JSON with no outbound traffic. If it ever reaches
# for the network under the iptables-blocked sandbox, this job
# fails loudly (audit-03 M4).
uv run dlm doctor --json >/dev/null
uv run dlm doctor >/dev/null
# `dlm show` lands in Sprint 13 (CLI finalization); add here
# when it's wired.
slow-tests:
# Sprint 02: marker-gated tests that touch HF. Cache-keyed on
# (pyproject.toml hash, tiny-model revision) per audit guidance.
# Sprint 11: also initializes + builds `vendor/llama.cpp` so export
# integration tests can exercise real GGUF conversion.
name: slow tests (hf-cache + llama.cpp)
runs-on: ubuntu-latest
steps:
- name: Checkout with llama.cpp submodule
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install uv
uses: astral-sh/setup-uv@v4
with:
version: ${{ env.UV_VERSION }}
- name: Sync dependencies
run: uv sync --all-extras --dev
- name: Restore HF cache
id: hf-cache
uses: actions/cache@v4
with:
path: ${{ github.workspace }}/.hf-cache
key: hf-tiny-${{ env.TINY_MODEL_REVISION }}-${{ hashFiles('pyproject.toml') }}
restore-keys: |
hf-tiny-${{ env.TINY_MODEL_REVISION }}-
- name: Pre-warm tiny model
env:
HF_HOME: ${{ github.workspace }}/.hf-cache
DLM_TINY_MODEL_REVISION: ${{ env.TINY_MODEL_REVISION }}
run: |
set -euxo pipefail
echo "Cache hit: ${{ steps.hf-cache.outputs.cache-hit }}"
uv run python - <<'PY'
from tests.fixtures.tiny_model import tiny_model_path
print("tiny model at:", tiny_model_path())
PY
- name: Restore llama.cpp build cache
id: llama-cpp-cache
uses: actions/cache@v4
with:
path: vendor/llama.cpp/build
# Cache key: submodule HEAD sha + build profile. CI uses a
# portable CPU build so cached binaries stay runnable across
# heterogeneous ubuntu runner hosts.
key: llama-cpp-build-portable-v1-${{ runner.os }}-${{ hashFiles('.gitmodules', 'vendor/llama.cpp/VERSION') }}
- name: Build llama.cpp tools (if not cached)
if: steps.llama-cpp-cache.outputs.cache-hit != 'true'
run: |
set -euxo pipefail
# ubuntu-latest ships cmake; `sudo apt-get install -y cmake` is a no-op fallback.
command -v cmake >/dev/null 2>&1 || sudo apt-get install -y cmake
scripts/bump-llama-cpp.sh build --portable --with-server
- name: Run slow tests
env:
HF_HOME: ${{ github.workspace }}/.hf-cache
DLM_TINY_MODEL_REVISION: ${{ env.TINY_MODEL_REVISION }}
DLM_ENABLE_SLOW_INTEGRATION: "1"
run: uv run pytest -m "slow" -v