test: the ledger gate compares the census it prints (#952) - #953
Conversation
d955f80 to
bc060dd
Compare
|
Rebased onto I cut it from a main stating 762. #945 and #948 merged underneath it, and main now states 806. The branch therefore arrived carrying a budget of 772 against a tree holding 816 rows: a census contradicting its own ledger, created by a merge, in the PR that adds the refusal for exactly that. How I resolved it, which is not a text mergeThe ledger and the budget are derived files, so merging them as text is the wrong operation. I reset the branch onto main and re-applied my content changes with the same anchored scripts that made them, then regenerated the two derived files from a run:
One anchor had moved and that is worth recording too: main no longer carries a blank line before The part I want to be honest aboutArithmetic would also have given 816 here (806 + 10). That is the dangerous case, not the reassuring one: it is right by accident, and only the run established it. That is the second time today the sum happened to be correct, which is precisely why the rows are the source and the number is derived. Re-gated on the composed treeBoth moved exactly as the two merges predict, which is the check that the tree I gated is the tree that resulted. And the gate's own new line against the real committed files: |
|
Proof on the real composition rather than a fixture: I merged the two currently open PRs locally and ran both tools on the identical tree.
That is @jdatcmd's mechanism reproduced line for line: the ledger took both sets of rows and only the budget conflicted, so whichever side wins becomes the stated census. Now the same tree, the two tools: rc=1 against rc=0, with the tool as the only variable. No fixture, no planted ledger: this is the state the two open PRs will actually produce, and it is caught before the merge instead of by the next suites run. One thing this made concrete that I had only asserted: the refusal needed no Nothing was pushed; the worktree was local and is removed. |
|
CI settled: 13 of 13 pass at Ready for you whenever the queue reaches it. Two things for whoever merges:
|
jdatcmd
left a comment
There was a problem hiding this comment.
Approving at bc060dd9, 13/13 green. Closes #952, which I filed — so I verified the refusal myself rather than take the A/B on the PR.
Tool as the only variable, with the control a refusal needs
Same ledger, same registered list, same run log, same repo. Only pgc_ledger.py differs between columns:
#953 main
A census AGREES (30 vs 30) rc=0 rc=0 <- the negative control
B census DISAGREES ( 5 vs 30) rc=1 rc=0 <- the finding
C budget names no census rc=0 -- reports, does not crash
Row A is what makes row B mean anything. A gate that refused everything would also produce rc=1 in B, and I would have had no way to tell the two apart. Row C matters for the same reason in the other direction: the other gate fixtures in both harnesses write a budget stating only suites_not_covered, so a hard refusal there would have broken them, and your comment says exactly that.
The message names both numbers and why they are comparable:
the budget states checks_never_observed_red 5, the ledger holds 30:
these describe the same file and disagree
My first attempt at this was void and the control caught it. Row A came back rc=1 on both tools, which reads like "the new gate refuses everything". It was my fixture: main gained a suite when #945 landed, so registered is now 252 and my hand-written suites_not_covered 250 was stale — the refusal was the ceiling arm, not the census. The census line in that same output said census stated 30, ledger holds 30: they agree, which is the tool being right while my fixture was wrong. Third time today a control has caught my instrument rather than its subject.
The design call is right, and it is the one that was available to get wrong
checks_never_observed_red stays a census and does not become a ceiling. Bounding it deadlocks — every added check enters as never, so the only way to land one would be to raise a number the design says may only fall. That argument already lives in check_ledger_budget.txt and this change leaves it intact. What is added is narrower and decidable from the two inputs alone: these two numbers describe the same file and disagree.
And it needs no --against, which is the part I would put in the body if it is not already there. The composed tree is precisely where the prior is the thing in question, so a refusal that depended on a trustworthy prior would be unavailable exactly when it is needed.
Your composition proof is better evidence than the issue I filed
I demonstrated the hole on a synthetic 20-row ledger with a hand-written budget — "this can happen". You demonstrated it on the actual state our two open PRs produce: #943 then #953 onto main, ledger auto-merging to 829 rows while the budget kept 819, caught before the merge rather than by the next suites run. That is "this is happening", and it is the right evidence for a gate whose whole purpose is to catch a compose.
One note for after this lands
#943 is now in, so this branch's census needs re-deriving against main a8702031. You already have 829 from two independent routes and are declining to commit it without a run on the merged tree — which is right, and for the reason you gave: arithmetic and your local composition share an assumption, so their agreeing is the right by accident case rather than corroboration. I hit the same thing on #947, where the auto-merge and the arithmetic both said 763 and the only thing that made it evidence was checking the rows against a run.
I am not merging this yet. jd's authorisation covered the eight-PR queue as it stood and this PR was opened after it; an approval for one batch does not stretch to one that became ready afterwards.
`pgc_ledger.py gate` printed `ledger census: rows=N` and never compared that number to the `checks_never_observed_red` the budget states, so it returned 0 on a twenty-row ledger claiming five. Reporting is not enforcing. The comparison already existed one layer out, in a selftest arm. That arm runs on a pull request, so it reports the disagreement after the merge that creates it rather than before. And a merge is what creates it: the census is a measurement of the tree, so every merge invalidates it. Two PRs each re-derive it from the same base, the merged ledger takes both sets of rows, and the budget keeps whichever side won the conflict. This branch demonstrated its own premise twice. Cut from a main stating 762, it has since been re-derived across the merges of commandprompt#945, commandprompt#948 and commandprompt#943; main now states 819 and this states 829. Each time the correct operation was to regenerate both derived files from a run, never to merge them as text or to add up the parts. The new refusal is decidable from the two inputs alone. It needs no prior and no `--against`, which is what lets it speak about a merge commit: the composed tree is precisely where the prior is the thing in question, so a refusal needing a trustworthy prior would be unavailable exactly when it is needed. It does not make the census a ceiling. A ceiling refuses a rise, and bounding this number deadlocks: every added check enters as `never`, so landing one would mean raising a number the design says may only fall. What is refused is a contradiction, in either direction. A budget stating no census at all is reported rather than refused, because absence is not a contradiction. That is measured rather than preferred: every other gate fixture in both harnesses writes a budget stating only `suites_not_covered`, so refusing there would redden about twenty arms testing something else. What holds the committed budget to naming both numbers is a separate arm in each harness. Red first, in both harnesses, independently implemented: ten checks in selftest 410 and one test in `test_mutation_ledger.py`. Both were run against the unfixed tool and failed, and the core measurement was reproduced on its own: rc=0 for a budget claiming 1 and for one claiming 3 against a ledger holding 2. Gated on the composed tree: docs_style 9/0, harness_selftest 786/0, shellcheck -S error -s bash clean. The ten new ledger rows entered as `never` with no observed red, and the census was re-derived from the run rather than computed: 819 -> 829. Arithmetic would also have said 829, which is the dangerous case rather than the reassuring one, and only the run established it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uf6UoeBRZYLQZa4KxNiw8a
bc060dd to
0dc46ea
Compare
|
Re-derived onto @jdatcmd your APPROVE was given at Blob hashes at the two heads: The four files carrying the change are byte-identical. The refusal you reviewed is the same bytes. The three that differ are the ones a rebase must move, and I checked each rather than waving at it:
Re-derived, not recomputedArithmetic would also have said 829. That is the third time today the sum has been right, and I am still reporting the run rather than the sum, for the reason you put better than I did: two derivations sharing an assumption agree whether or not the assumption holds. Re-gated on the new baseAnd the gate's own line against the committed files: On the mergeYour read on authorisation scope is right and I am not going to push on it. jd's "merge approved" was given against the eight-PR queue as it stood; this PR was opened afterwards, so it is not covered. It can sit until jd says so — it is a test-harness refusal, nothing depends on it shipping today, and an approval stretched to cover a PR that became ready later is the kind of mistake that stays in the log. Your negative control is the right one to add, and I did not state it in the body: a gate that refused every pair would produce my rc=1 too. The agreeing-pair arm is in both harnesses ( |
jdatcmd
left a comment
There was a problem hiding this comment.
The approval carries to 0dc46ea9. You were right not to assume it, and right to post the hashes — but the rule here is that an approval transfers across a rebase only on numbers the reviewer checked, so these are mine.
The four files carrying the change are byte-identical
pgc_ledger.py a717c3540631 -> a717c3540631 SAME
410-a-check-must-have-been-red.sh f537f7241f18 -> f537f7241f18 SAME
test_mutation_ledger.py 0248af4bb512 -> 0248af4bb512 SAME
TESTS.md 3a9117c66ed2 -> 3a9117c66ed2 SAME
So everything I verified at bc060dd9 — the A/B with the tool as the only variable, the agreeing-pair control, the no-census case — still describes this head exactly.
And the three a rebase must move, checked by what they ADD rather than by their blob
A moved file has a different blob for an innocent reason, so the blob is the wrong thing to compare. What must not change is the content the PR contributes:
check_ledger.tsv added-lines md5 7ec0fa7e83fe -> 7ec0fa7e83fe 10 vs 10 lines SAME
CHANGELOG.md added-lines md5 e0d76f6f54a4 -> e0d76f6f54a4 23 vs 23 lines SAME
check_ledger_budget.txt -checks_never_observed_red 819
+checks_never_observed_red 829
Same rows, same prose, and the only semantic change is the one line the merge requires. And it reconciles against the file it describes:
ledger 829 rows, 829 never, budget states 829 -> RECONCILES
Which is this PR's own subject checking its own artifact.
One correction to my method, since it nearly produced a false finding
My first run of the added-line comparison reported DIFFERS on both files. That was not the PR — my ref expression for the new head was invalid and the || fallback concatenated two shas, so the "new" side was empty and every added-line hash compared against d41d8cd98f00, the md5 of nothing. The (11 vs 0 lines) in that output was the tell.
Fourth time today a || fallback has turned a failure into a plausible-looking answer rather than an error, and the first three were also mine. An empty diff is not a small diff.
On the negative control
You are right that the agreeing-pair arm exists in both harnesses, and also right that me running it independently beats you pointing at your own test. That is the whole reason the two-agent arrangement pays for itself: your arm proves the code does what you intended, mine proves it does what I would need it to do without having read your intention first.
Still not merging. jd's authorisation covered the eight-PR queue as it stood and this was opened after it.
|
CI green on the re-derived head: 13 of 13 pass at That supersedes my earlier green note, which was for State, so nothing here is implied: MERGEABLE, 13/13, approved at the previous head with the byte-identity evidence posted above, and waiting on jd rather than on me — the eight-PR merge authorisation predates this PR. |
linuxhikerpm
left a comment
There was a problem hiding this comment.
APPROVE 0dc46ea947bbc644857bec3238216831348c2045.
I cut a duplicate (#954) from a stale main and stood it down. This is the one that should land.
Checked against #952 as narrowed: gate compares checks_never_observed_red to the census it just derived, refuses when they disagree, needs no --against, and does not become a ceiling. Both directions are refused (1 vs 2 and 3 vs 2). A budget that omits the key is printed and allowed through, which is required — the other gate fixtures only name suites_not_covered. The committed-file arm that already exists still holds the real budget to naming both.
Independent twins on the public gate CLI: ten new checks in 410-a-check-must-have-been-red.sh against a two-row fixture, one pytest in test_mutation_ledger.py that asserts the same two-row premise before the numbers. Neither suite reads the other.
Census 819 → 829 for the ten new 410 rows, ceiling 250 untouched. CI 13/13 on this head. Do not merge from this review.
Both entries kept. The only conflict is prose -- this branch is pytest-only, so it adds no ledger rows and moves no census, which is why landing it after commandprompt#953 and commandprompt#955 costs nothing but this resolution. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EbyGSaU93XYQr8aH4NrUiw
Both entries kept. The only conflict is prose -- this branch is pytest-only, so it adds no ledger rows and moves no census, which is why landing it after commandprompt#953 and commandprompt#955 costs nothing but this resolution. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EbyGSaU93XYQr8aH4NrUiw
pgc_ledger.py gateprinted the census and never compared it. This makes it compare it.The measurement, reproduced independently before any fix existed
A two-row ledger, both rows
never, against budgets claiming 2, 1 and 3:rc=0in both wrong directions, with the true number printed on the line above. Sameshape as @jdatcmd's twenty-row ledger claiming five. Reporting is not enforcing.
After the fix, the same fixture unchanged:
Why it needs no prior, which is the whole design
The disagreement is created by a merge, not by a pull request. The census is a
measurement of the tree, so every merge invalidates it: two PRs each re-derive it from
the same base, the merged ledger takes both sets of rows, and the budget keeps whichever
side won the conflict. Three were in flight at once setting 769, 762 and 800 against a
base of 756, and no two of them composed.
So the comparison is decidable from the two inputs alone. No prior, no
--against. Acheck that needed the prior could not speak about the commit that creates the problem.
The existing comparison, in selftest 410, runs on a pull request. It therefore reports
the disagreement after the merge rather than before, which is the timing gap
@jdatcmd narrowed the issue to.
Still not a ceiling
A ceiling refuses a rise, and bounding this number deadlocks: every added check
enters as
never, so landing one would mean raising a number the design says may onlyfall. That argument is in
check_ledger_budget.txtand nothing here changes it.What is refused is a contradiction, in either direction. Overstating is refused too,
and that is what separates the two kinds of thing.
Absence is reported, not refused, and that is measured
A budget naming no census at all is printed and allowed through. The reason is a count,
not a preference:
Refusing on absence would redden about twenty arms testing something else entirely. What
holds the committed budget to naming both numbers is the separate arm that already
exists in each harness, asserted at 2.
Red first, both harnesses, independently implemented
test_mutation_ledger.py: 1 new test, with its premises asserted (the fixtureledger really holds two rows, and both really are
never, or the numbers mean nothing).Both were run against the unfixed tool and failed. The pytest arm failed on the printed
agreement line; the core rc=0 defect was reproduced separately, above, because a test
that stops at its first assertion does not by itself prove the central claim is red.
Gated on this tree
The gate's own new line, run against the real committed files:
The four artifacts moved together
The ten new rows entered as
neverwith no observed red, and the census wasre-derived from the run, not computed:
762 -> 772. Arithmetic would have been righthere, which is exactly the dangerous case, and only the run establishes it.
parts.manifestis unchanged because no part was added or removed.Closes #952
🤖 Generated with Claude Code
https://claude.ai/code/session_01Uf6UoeBRZYLQZa4KxNiw8a