Audit fixes: MLX chat template, Ollama export preflight, migration intent preservation, smol-135m warning #145
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |