Skip to content

pgc_ledger.py gate prints the census and does not enforce it, so a merge can land a budget that contradicts its own ledger #952

Description

@jdatcmd

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions