Reproducible CI dependency lock + drift ratchet (closes #150) #205

Merged
conductor merged 14 commits from ci-dependency-lock into master 2026-09-07 22:51:56 +01:00
Owner

Summary

  • Fully-pinned requirements-ci.txt (37 pins, uv pip compile --python-version 3.11 = runner-parity) committed with a TDD'd drift ratchet (scripts/check_dep_lock.py — regenerates to temp, diffs pins, fails with the regen command; fails loudly on non-pin lines).
  • All FOUR CI venvs (lint/test/canary/type-check) now install lock-only (-r requirements-ci.txt + -e . --no-deps); the canary || true escape hatch is deleted.
  • Kill test (the acceptance): fresh Python 3.11.2 runner-parity venv → lock install → FULL suite green (614+84 at implementation, 619+84 after discharge) + all 23 canaries green.
  • Gate discharges (red-first, 8 commits): pinned-uv PATH defect (checker prefers interpreter-adjacent uv + step exports venv PATH), loud failure on floor-style lock lines, actionable missing-uv error, stdout quieting, docstring disclosures, type-check venv brought under the lock.

Verification

  • Full suite green; pyright ratchet at parity (117e/407w); ruff clean; henxels exit 0; ci.yml parses after every edit.
  • Same commit + same lock = same resolution: proven by the 3.11 kill test.
  • pyproject.toml untouched (packaging rewrite avoided); runners need no uv (plain pip consumes the lock).
## Summary - Fully-pinned `requirements-ci.txt` (37 pins, `uv pip compile --python-version 3.11` = runner-parity) committed with a TDD'd drift ratchet (`scripts/check_dep_lock.py` — regenerates to temp, diffs pins, fails with the regen command; fails loudly on non-pin lines). - All FOUR CI venvs (lint/test/canary/type-check) now install lock-only (`-r requirements-ci.txt` + `-e . --no-deps`); the canary `|| true` escape hatch is deleted. - Kill test (the acceptance): fresh Python 3.11.2 runner-parity venv → lock install → FULL suite green (614+84 at implementation, 619+84 after discharge) + all 23 canaries green. - Gate discharges (red-first, 8 commits): pinned-uv PATH defect (checker prefers interpreter-adjacent uv + step exports venv PATH), loud failure on floor-style lock lines, actionable missing-uv error, stdout quieting, docstring disclosures, type-check venv brought under the lock. ## Verification - Full suite green; pyright ratchet at parity (117e/407w); ruff clean; henxels exit 0; ci.yml parses after every edit. - Same commit + same lock = same resolution: proven by the 3.11 kill test. - pyproject.toml untouched (packaging rewrite avoided); runners need no uv (plain pip consumes the lock).
conductor added 10 commits 2026-09-07 15:23:12 +01:00
requirements-ci.txt: fully-pinned output of
'uv pip compile pyproject.toml -o requirements-ci.txt --extra dev --extra
memory --python-version 3.11' (plain pip format; runners need no uv).
scripts/check_dep_lock.py: drift ratchet — regenerates the compile into a
temp file, diffs requirement pins against the committed lock, exits 1 with
the regeneration command on drift. Unit-tested against fixture files.
All three CI venvs (.ci-venv, .test-venv, .canary-venv) now install deps
exclusively from the pinned requirements-ci.txt, then the project itself
with --no-deps — no floating escape hatch remains. The canary job's ad-hoc
'textual==8.2.8 textual-autocomplete==4.0.6 sqlite-vec || true' pre-install
is deleted: the lock pins exactly those versions. Lint job gains a
dependency-lock drift ratchet step (scripts/check_dep_lock.py) that fails
the job when the lock no longer matches pyproject.toml.
README Development section: how to regenerate requirements-ci.txt after
dependency edits (exact uv pip compile command), why --python-version 3.11
(runner-interpreter markers), and why CI installs the project with
--no-deps (deps only from the pinned lock). Regeneration command and
rationale also documented in the scripts/check_dep_lock.py header.
Gate-review F1: the drift-ratchet step installed uv==0.10.11 into
.ci-venv but never exported PATH, so check_dep_lock.py resolved
/usr/bin/uv (0.12.10) and re-compiled the lock with a different
resolver than generated it. Both ends fixed:

- ci.yml drift step: export PATH="$PWD/.ci-venv/bin:$PATH" before
  invoking the checker.
- check_dep_lock.uv_binary(): prefer the interpreter-adjacent uv
  (sys.executable's dir — where CI pins it) over shutil.which.

RED->GREEN: two new tests (PATH-stripped + decoy-uv-on-PATH) failed
against the old resolver order, pass after the fix.
Gate-review F2: lock_requirements() silently skipped any requirement
line lacking '==', so a degraded floor-style lock (e.g. 'rune>=0.49.0')
would pass the drift diff with its floor pins intact. Options
(-r/--index-url/...) remain the only tolerated non-pin lines; any other
non-pinned form (floor, range, wildcard, bare name) now exits 1 with an
actionable message naming the offending line and the regen command.

RED->GREEN: two new tests (floor fixture + unpinned/wildcard variants)
failed against the skip behavior, pass after the loud-failure fix. The
pre-existing skip-options test was updated to match the tightened
contract; the committed lock (37 pins) still parses clean.
Gate-review F3: with uv absent, run_uv_compile() let subprocess raise a
raw FileNotFoundError traceback. Now caught (sentinel -1) and main()
prints the house actionable pattern: what to install (pinned uv into
.ci-venv), what command (pip install + PATH export), local install doc.

Gate-review F5: uv's full resolution listing went to stdout even on
success, polluting CI logs. stdout/stderr are now captured and printed
only on compile failure.

RED->GREEN: new test (uv_binary stubbed to a missing binary, PATH
stripped) failed with FileNotFoundError, passes with exit 1 + actionable
stderr. Real smoke: successful run no longer prints uv's 'Resolved 37
packages' listing.
Gate-review F4: two known blind spots were undocumented — (1) marker-only
drift (env-marker changes with identical pins) is invisible to the pin
diff, (2) direct-URL requirement lines are skipped by the old parser /
now fail loudly instead of being compared. Both disclosed in the module
docstring with guidance; no behavior change.

RED->GREEN: docs-only finding; red evidence is the pre-existing docstring
lacking the disclosure (gate finding itself). Checker behavior and tests
unchanged (15 pass).
Gate-review F6 (pre-existing): the type-check job still installed
".[dev,memory]" — the one remaining floating install, leaving the
PROTECTED type-check toolchain unreproducible. Switched to the same
lock-only pattern as lint/unit-tests/canary: pip install -r
requirements-ci.txt + pip install -e . --no-deps. The lock pins
pyright==1.1.411, so the ratchet now runs against a pinned pyright.

RED->GREEN: config-only finding; red evidence was ci.yml:196 installing
floating extras (gate finding). yaml.safe_load parses after the edit;
no floating 'pip install .[...] ' remains in any workflow.
Quote-style only (double -> single for the line containing \$PWD);
battery re-verified after format (15 checker tests pass).
docs(ci): gate-review discharge record in plan completion record (#150)
Auto-label / priority-gate (pull_request) Skipped
Auto-label / auto-label (pull_request) Successful in 1s
CI Pipeline / lint (push) Successful in 28s
CI Pipeline / unit-tests (push) Successful in 35s
CI Pipeline / canary (push) Successful in 41s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 21s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / required-checks (push) Skipped
CI Pipeline / lint (pull_request) Successful in 25s
CI Pipeline / unit-tests (pull_request) Successful in 47s
CI Pipeline / canary (pull_request) Successful in 30s
CI Pipeline / best-practices (pull_request) Successful in 2s
CI Pipeline / documentation (pull_request) Successful in 2s
CI Pipeline / type-check (pull_request) Successful in 22s
CI Pipeline / gitea-offline (pull_request) Successful in 14s
CI Pipeline / required-checks (pull_request) Successful in 0s
ada5456678
Per-finding map: finding -> commit -> red evidence for all six gate
findings (F1-F6), final battery numbers, and the F3+F5 shared-commit
deviation note.
gitea-actions Bot added the governance label 2026-09-07 15:23:17 +01:00
conductor added 1 commit 2026-09-07 16:07:25 +01:00
Merge remote-tracking branch 'gitea/master' into ci-dependency-lock
CI Pipeline / lint (push) Successful in 47s
CI Pipeline / unit-tests (push) Successful in 36s
CI Pipeline / canary (push) Successful in 23s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 22s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / required-checks (push) Skipped
CI Pipeline / lint (pull_request) Successful in 25s
CI Pipeline / unit-tests (pull_request) Successful in 30s
CI Pipeline / canary (pull_request) Successful in 23s
CI Pipeline / best-practices (pull_request) Successful in 6s
CI Pipeline / documentation (pull_request) Successful in 7s
CI Pipeline / type-check (pull_request) Successful in 22s
CI Pipeline / gitea-offline (pull_request) Successful in 14s
CI Pipeline / required-checks (pull_request) Successful in 0s
c8c8d85624
conductor added 1 commit 2026-09-07 21:01:15 +01:00
Merge remote-tracking branch 'gitea/master' into ci-dependency-lock
CI Pipeline / lint (pull_request) Successful in 24s
CI Pipeline / unit-tests (pull_request) Successful in 34s
CI Pipeline / canary (pull_request) Successful in 23s
CI Pipeline / best-practices (pull_request) Successful in 2s
CI Pipeline / documentation (pull_request) Successful in 2s
CI Pipeline / type-check (pull_request) Successful in 22s
CI Pipeline / gitea-offline (pull_request) Successful in 13s
CI Pipeline / required-checks (pull_request) Successful in 1s
auto-version.yml (push) Invalid workflow file
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / lint (push) Failing after 38s
CI Pipeline / canary (push) Failing after 41s
CI Pipeline / type-check (push) Successful in 35s
CI Pipeline / unit-tests (push) Failing after 49s
CI Pipeline / required-checks (push) Skipped
50eac65d18
conductor added 1 commit 2026-09-07 22:34:16 +01:00
merge master (behind-base update for robot drain)
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / lint (push) Successful in 30s
CI Pipeline / canary (push) Successful in 25s
CI Pipeline / unit-tests (push) Successful in 34s
CI Pipeline / type-check (push) Successful in 23s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / required-checks (push) Skipped
CI Pipeline / best-practices (pull_request) Successful in 3s
CI Pipeline / documentation (pull_request) Successful in 3s
CI Pipeline / lint (pull_request) Successful in 29s
CI Pipeline / unit-tests (pull_request) Successful in 34s
CI Pipeline / canary (pull_request) Successful in 28s
CI Pipeline / gitea-offline (pull_request) Successful in 13s
CI Pipeline / type-check (pull_request) Successful in 22s
CI Pipeline / required-checks (pull_request) Successful in 0s
73f3c15da4
conductor added 1 commit 2026-09-07 22:45:38 +01:00
merge master (behind-base update, round 2)
CI Pipeline / lint (push) Successful in 29s
CI Pipeline / canary (push) Successful in 38s
CI Pipeline / unit-tests (push) Successful in 45s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / type-check (push) Successful in 23s
CI Pipeline / best-practices (pull_request) Successful in 2s
CI Pipeline / documentation (pull_request) Successful in 3s
CI Pipeline / lint (pull_request) Successful in 28s
CI Pipeline / canary (pull_request) Successful in 24s
CI Pipeline / unit-tests (pull_request) Successful in 34s
CI Pipeline / gitea-offline (pull_request) Successful in 13s
CI Pipeline / type-check (pull_request) Successful in 22s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / required-checks (push) Skipped
CI Pipeline / required-checks (pull_request) Successful in 0s
79ea4bcf95
conductor merged commit c36e5b07ac into master 2026-09-07 22:51:56 +01:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: conductor/rune#205