pgc_ledger.py gate prints the census and does not enforce it, so a merge can land a census that contradicts its own ledger.
Measured
Fixture: a 20-row ledger with a budget claiming checks_never_observed_red 5, ceiling unchanged, inside a git repo so --against resolves.
ledger rows = 20 budget census claims = 5 ceiling 250 -> 250
GATE rc=0
ledger census: rows=20 | never observed red=20, ever red=0, new this run=0
ledger coverage: registered=251 | covered=1, not covered=250, ceiling=250
ceiling against HEAD: 250 -> 250, which does not rise
rc=0 on a fifteen-row lie. The gate prints rows=20 on the line above and never compares it to the 5 the budget states. It enforces the ceiling, which is monotone, and reports the census, which is a measurement — and reporting is not enforcing.
Why it matters now rather than in the abstract
Found by @OffgridwithJD while simulating the merge of seven approved PRs. #943 and #947 both rewrite the census from main's 756 — to 769 and 762 respectively. Merged together, the ledger takes both sets of rows while the budget keeps whichever side won the conflict, and the composed tree states 769 against 775 actual rows.
What catches it today is #925's selftest arm, in the suites job:
FAIL the committed census matches the committed ledger: got [769] want [775]
That arm runs on a pull request. It does not run before a merge commit lands. So two PRs that are each green can compose into a main whose ledger contradicts its own budget, and the first signal is the next suites run.
This is the third instance today
The census has now had to be re-derived on #925, on #947's rebase, and in this simulation. That is no longer an accident of scheduling: the census is a measurement of the tree, and every merge invalidates it. An artifact with that property needs the mechanism that gates it to check it, because a human re-deriving it correctly three times running is not a control.
Proposed
gate compares the budget's checks_never_observed_red against the ledger it was handed, and refuses on a mismatch — the same comparison the selftest arm already makes, moved to the tool that CI runs.
The ceiling stays as it is: monotone, compared against the prior. The census is not a ceiling and must not become one — that deadlock is already argued in check_ledger_budget.txt and nothing here changes it. The new refusal is "these two numbers describe the same file and disagree", which is decidable from the two inputs alone with no prior needed.
Red first
The fixture above is the test: a ledger and a budget that disagree, asserted to be refused, beside a control where they agree, asserted to pass. Both harnesses, independently implemented, per the standing rule.
Out of scope
Deciding what the census should be. The tool cannot know; it can only refuse to certify a pair that contradicts itself.
pgc_ledger.py gateprints the census and does not enforce it, so a merge can land a census that contradicts its own ledger.Measured
Fixture: a 20-row ledger with a budget claiming
checks_never_observed_red 5, ceiling unchanged, inside a git repo so--againstresolves.rc=0 on a fifteen-row lie. The gate prints
rows=20on the line above and never compares it to the 5 the budget states. It enforces the ceiling, which is monotone, and reports the census, which is a measurement — and reporting is not enforcing.Why it matters now rather than in the abstract
Found by @OffgridwithJD while simulating the merge of seven approved PRs. #943 and #947 both rewrite the census from main's 756 — to 769 and 762 respectively. Merged together, the ledger takes both sets of rows while the budget keeps whichever side won the conflict, and the composed tree states 769 against 775 actual rows.
What catches it today is #925's selftest arm, in the suites job:
That arm runs on a pull request. It does not run before a merge commit lands. So two PRs that are each green can compose into a main whose ledger contradicts its own budget, and the first signal is the next suites run.
This is the third instance today
The census has now had to be re-derived on #925, on #947's rebase, and in this simulation. That is no longer an accident of scheduling: the census is a measurement of the tree, and every merge invalidates it. An artifact with that property needs the mechanism that gates it to check it, because a human re-deriving it correctly three times running is not a control.
Proposed
gatecompares the budget'schecks_never_observed_redagainst the ledger it was handed, and refuses on a mismatch — the same comparison the selftest arm already makes, moved to the tool that CI runs.The ceiling stays as it is: monotone, compared against the prior. The census is not a ceiling and must not become one — that deadlock is already argued in
check_ledger_budget.txtand nothing here changes it. The new refusal is "these two numbers describe the same file and disagree", which is decidable from the two inputs alone with no prior needed.Red first
The fixture above is the test: a ledger and a budget that disagree, asserted to be refused, beside a control where they agree, asserted to pass. Both harnesses, independently implemented, per the standing rule.
Out of scope
Deciding what the census should be. The tool cannot know; it can only refuse to certify a pair that contradicts itself.