Catch a containment check changed without VERSION being bumped - #57
Conversation
VERSION is part of the cache key, so editing a scoring check without bumping it leaves every workspace with a warm cache serving judgements from the old check. Nothing errors and nothing warns; the numbers are simply wrong and they look exactly like the right numbers. TRACKER.md has carried this as an accepted limit, which was reasonable while the judge was new and is not reasonable now that other people can clone the repository. CHECKS_DIGEST records a content hash over everything a containment verdict depends on, and a test fails when the two drift apart. It sits immediately beside VERSION, so the two things that must move together are adjacent, and carries the instructions in a comment where the next person will be editing. The digest covers the matched data as well as the code. HEDGES, HEDGE_KEYS, GROUNDED_KEYS, CORRECT_KEYS, WORD and STOPWORDS are all matched at judging time -- adding one word to HEDGE_KEYS changes which criterion a failure is cited under just as surely as editing the branch that reads it, and a digest over code alone would not notice. A parametrised test asserts each of those five is really covered, so the claim cannot rot. Reformatting must not trip it, or the test gets suppressed and the real guarantee goes with it. Normalisation tokenises the source and drops COMMENT and NL, which is exactly what `ruff format` produces when it reflows a call or a developer edits a comment. INDENT and DEDENT are deliberately kept: moving a statement into or out of an `if` is a behaviour change. String literals are compared by value rather than by their quoting, since `ruff format` normalises quotes. Verified both directions against the real file rather than only against fixtures: `ruff format src/langchef/judge/providers.py` leaves the digest at fc7b2758193b, and adding one word to HEDGE_KEYS moves it to 4dcc398bc200 and fails the test with the VERSION-bump instructions rather than a bare hash mismatch. The failure message names the current VERSION, says why the cache makes this dangerous, gives the two edits to make, and tells a reader who only reformatted to report it -- because a test that cries wolf is a test that gets deleted. One known limit, stated rather than hidden: adding or removing a magic trailing comma does move the digest. `ruff format` will not do that to already-formatted source, so it can only be reached by editing the comma by hand.
|
Reviewed, and this is the strongest PR on the repo so far. Two calls in it are better than the issue asked for. Keeping The failure message is the best part. Naming One gap worth closing before merge. A threshold change does not trip the digest:
Adding Also: |
#57 and #67 landed on origin while #28, #31 and #54 were being merged locally. tests/test_flow.py conflicted: #28 added a per-criterion attribution test and #58 added four ambiguity-disclosure tests, and git tangled them because both open with identical fixture setup. Resolved as the union of both, reconstructed from each side's full version rather than hand-patched, so no assertion was silently dropped in the middle of a hunk.
Closes #32
CHECKS_DIGESTbesideVERSIONinproviders.py, and a test that fails when they drift apart.Both directions verified against the real file
Fixtures prove the normaliser; these prove the guard:
and the failure a developer actually sees:
The digest covers the matched data, not just the code
HEDGES,HEDGE_KEYS,GROUNDED_KEYS,CORRECT_KEYS,WORDandSTOPWORDSare all matched at judging time. Adding one word toHEDGE_KEYSchanges which criterion a failure is cited under just as surely as editing the branch that reads it — the polarity inversion you describe in the issue is exactly this shape.A digest over code alone would miss it, so a parametrised test monkeypatches each of the five in turn and asserts the digest moves. That keeps the coverage claim from rotting as the keywords change.
Why
ruff formatcannot trip itThe AC is explicit that reformatting must not fire, and the reason is in the issue: a test that cries wolf gets suppressed, and the real guarantee goes with it.
Normalisation tokenises the source and drops
COMMENTandNL— precisely whatruff formatemits when it reflows a call across lines, and what an edited comment produces.Two deliberate non-drops:
INDENT/DEDENTare kept. Moving a statement into or out of anifis a behaviour change, not layout. A test assertsx = 1at function level differs from the same line insideif a:.ruff formatnormalises quote style, so'x'and"x"must hash the same — but editing a rationale string is user-visible behaviour and must not. Both are tested.Reuses the
experiments.pyshapeSame
sha256(...).hexdigest()[:12], same idea: hash the content, record the hash next to the thing it authorises, and let a mismatch revoke it.experiments.pyexcludes[approval]from its own hash; here the digest covers named objects rather than the whole module, soCHECKS_DIGESTcannot hash itself.Where the next person will look
Beside
VERSION, in a comment that says what goes wrong, and the two edits to make.TRACKER.md's accepted-limit entry is rewritten from "nothing catches forgetting" to what now does.One limit, stated rather than hidden
Adding or removing a magic trailing comma does move the digest.
ruff formatwill not do that to already-formatted source — it only explodes a call when you add the comma yourself — so it is reachable by hand-editing only, and the message tells such a reader to report it. I'd rather leave that visible than special-case commas in the normaliser.Two things I said on the issue I would confirm
VERSIONinproviders.py. It matchesexperiments.py's pattern of recording the hash next to what it authorises, and keeps the two things that must move together adjacent. Happy to move it into the test if you'd rather keep generated constants out of the source.Verification
Collected tests go 183 → 194 (+11). The single failure is
test_docs_in_sync.py::test_verify_script_is_executable, which fails identically on a cleanmainon this machine — Git for Windows does not preserve the executable bit onscripts/verify.sh. It is unrelated to this branch and should be green on Linux CI.