ADR-0016: CI test gating with a lean install and a notebook render-freshness guard¶
Date: 2026-07-04 Status: Accepted
Context¶
Until now the only CI workflow was docs.yml (build + deploy the site). Nothing ran
the test suite or linters in CI — the 211 tests only ran on a developer's machine. For
a portfolio that advertises "tests alongside implementation," an ungated suite is a
visible gap: a reviewer sees no green "tests passing" signal, and a regression could land
on main unnoticed.
Two constraints shape how a test workflow can run here:
- The real data and heavy deps are absent/expensive in CI. The catalog CSV and
artifacts/are gitignored (so absent on a cold clone), and the ML stack (torch,sentence-transformers,faiss,matplotlib,umap) is slow and heavy to install on a runner. - The published notebook renders can drift. ADR-0015 keeps
notebooks/*.pyas source and commits the pre-executeddocs/notebooks/*.ipynbas a manualmake docs-notebooksartifact — so a.pyedit can leave its render stale, and CI cannot re-execute notebooks to check (no catalog, no Ollama, by that ADR).
Decision¶
- Add
.github/workflows/test.ymlwith alintjob (ruff+black --check+ the render-freshness check below) and atestjob (pytest), on every push tomainand every PR. - Run the suite under a lean
[dev]install, not the full extras. The tests use a tiny synthetic-catalog fixture (tests/conftest.py), and every heavy-dep or real-data test guards withpytest.mark.skipif/importorskip/ a data-present skip. So under[dev]the SBERT/torch, matplotlib, and real-catalog tests skip cleanly (≈185 passed / 26 skipped in CI) instead of forcing a slow, flaky ML install. The heavy tests are exercised locally with the full extras. One unguarded matplotlib test was fixed toimportorskipto match the existing pattern. - Add
scripts/check_notebook_render_fresh.py(+make docs-notebooks-check): a git-timestamp check that fails when anotebooks/NN.pywas committed after itsdocs/notebooks/NN.ipynbrender. It runs in the CIlintjob withfetch-depth: 0, catching the one drift ADR-0015's manual re-render cannot auto-fix.
Consequences¶
- CI coverage is deliberately a subset. SBERT/rerank/clustering-viz and real-catalog
tests skip on the runner. Accepted: they run locally, the fixture suite covers the core
logic and the
Recommenderinterface contract, and CI stays fast (~30 s lint) and reliable (no multi-minute torch install to flake on). - The freshness guard is timestamp-based, not a content diff. A no-op
.pyedit (a comment, a rename) trips it — the fix is a harmlessmake docs-notebooks. It also cannot detect a render that went stale without a.pyedit (e.g. underlying results changed); that remains manual discipline, as flagged in ADR-0015.
Alternatives rejected¶
- Full-extras CI (install
[dev,semantic,viz]): the torch install is heavy, slow, and a flake surface, for little marginal coverage over a tiny synthetic fixture. - Execute-and-diff the notebooks in CI: would need the catalog + a live Ollama — the exact infeasibility ADR-0015 was built around.
Builds on ADR-0014 (notebooks) and ADR-0015 (docs site + manual render).