Repository navigation
claims.yaml has a duplicate reason: key: a +622-line waiver is recorded with a +10-line justification, and claim_check passes #1087
Description
Activity
- added a commit that references this issue
on Aug 27, 2026 Fixed on
mainas #1088 (67a4e0ef). Staying open until v0.60 is tagged, per the close-on-the-release rule.Verified live against
main's own checker:$ python3 <main>/scripts/claim_check.py <the pre-fix claims.yaml from 64aa5bd2> claim_check: duplicate key 'reason': declared at line 1311 and AGAIN at line 1330 — YAML keeps the LAST one silently, so the first value is discarded with no diagnostic. Split the mapping (#1087). EXIT=1 $ strict duplicate-key load of main's current claims.yaml strict-cleanSo the exact ledger state that shipped this defect is now refused by the gate that let it through, and the record it destroyed — RQ-60-VFPPRESSURE increment 1's justification for a +622-line growth — is restored on the waiver it belongs to.
Reopened — closed by accident, by me.
PR #1088's body opened with
Closes #1087, so merging auto-closed this. The repo's rule is to close on the tagged release, not the merge: "merged ≠ released — close on the tagged-release ref, never a merge commit alone; the tag is the evidence an outsider can check." I wrote the closing keyword anyway, one message after saying this issue should stay open until v0.60 is tagged.Nothing about the fix changes — it is on
mainas67a4e0efand verified live. This issue closes when v0.60 is tagged, citing the tag.Noting the mechanism rather than just the slip:
Closes #Nin a PR body is evaluated by GitHub at merge time, which is structurally the wrong moment for a repo whose closure rule is release-based.Refs #Nis the correct keyword here in every case, and the same trap false-completes umbrella issues when a PR fixes one item of several.Closed by v0.60.0 (tag
79e2daa5), fixed as PR #1088.Verified live against
main's own checker, not the PR's:$ <main>/scripts/claim_check.py <the pre-fix claims.yaml from 64aa5bd2> claim_check: duplicate key 'reason': declared at line 1311 and AGAIN at line 1330 — YAML keeps the LAST one silently, so the first value is discarded with no diagnostic. Split the mapping (#1087). EXIT=1 $ strict duplicate-key load of main's current claims.yaml strict-cleanThe ledger state that shipped this defect is now refused by the gate that let it through, and the record it destroyed — RQ-60-VFPPRESSURE increment 1's justification for a +622-line growth of the instruction selector — sits on the waiver it belongs to.
One number to read carefully in the v0.60 notes:
selector_lines_code's waiver count moved 9 -> 10 with the value unchanged at 19,199. That is a destroyed record being restored, not a further growth being permitted. The two are opposite events and look identical in a summary table.The class is #1059's duplicate key in a different file: the strict loader already existed in
status_evidence_check.pyand had never been pointed at the ledger every other claim is checked against. Swept for completeness —claims.yamlhad exactly one,artifacts/**/*.yamlandrivet.yamlhave zero.
The ledger stopped recording why the North Star's headline metric moved, and the gate stayed green
claims.yamlonmaincarries a duplicatereason:key inside a single waiver mapping of theselector_lines_coderatchet:YAML keeps the last value on a duplicate key, silently. So the surviving justification for a +622 line growth of the instruction selector is RQ-59-I64SHIFT's reason for a +10 line growth — a different lane, a different release, a different change.
Measured from the ledger's own history (
git log -Lon thevalue:line):The lane did its job — its commit message and the discarded reason both state the +622 honestly. The file edit turned
to: 18288intoto: 18910and appended a secondreason:instead of adding a second waiver entry, and nothing in the toolchain could see it.Why every gate passed
scripts/claim_check.pyloads the ledger withyaml.safe_load(permissive), andcheck_ratchetasks only:to == value? — yesreasonnon-empty? — yes, it is a perfectly good stringNeither question can distinguish "the right reason" from "someone else's reason". And the waiver's whole purpose is stated in the ratchet documentation: "the waiver is bound to that value, so a second regression needs a second waiver — permission is per-growth, never standing." A duplicate key converts a per-growth permission into a standing one and deletes the record of the growth it was granted for, leaving the gate green.
Red-first, against the real artifact rather than a fixture — the same
claims.yamlfrommain, under the shipped checker and under a duplicate-key-strict one:The class, and why it landed here specifically
This is #1059's duplicate-key defect, in a different file.
scripts/status_evidence_check.pyalready parses duplicate-key-strict — but onlyartifacts/release-*.yaml. The strict loader was never pointed atclaims.yaml, which is the one file every other claim in the repo is checked against.Swept for completeness:
claims.yamlhas exactly 1 duplicate key (this one);artifacts/**/*.yamlandrivet.yamlhave 0 — the existing strict loader is doing its job on the surface it covers.Fix
to: 18910and give RQ-59-I64SHIFT's back its ownto: 18288entry. The record is the deliverable; the numbers were always right.claims.yamlduplicate-key-strict inclaim_check.py(both load sites), with a diagnostic that names the discarded value's line — because the failure mode is silence, not error.scripts/test_claim_check.py: the exact shipped shape refused, the fixed two-waiver split accepted, and the repo's own ledger asserted clean (non-vacuity against the real artifact, not only fixtures). Mutation-verified — neutering the check fails 2 tests and lets the real defective ledger pass again.Found while auditing v0.60's ratchet numbers for the release notes:
selector_lines_codereads +1,302 against a baseline it must fall below, withsel_dsl_rulesflat at 80 — worth stating plainly in the notes either way, and the reason each step was permitted is exactly what the ledger is for.