Skip to content

test: a ledger of which checks have ever been red (#918) - #925

Open
jdatcmd wants to merge 11 commits into
feat/917-machine-readable-resultsfrom
feat/918-mutation-ledger
Open

test: a ledger of which checks have ever been red (#918)#925
jdatcmd wants to merge 11 commits into
feat/917-machine-readable-resultsfrom
feat/918-mutation-ledger

Conversation

@jdatcmd

@jdatcmd jdatcmd commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Closes #918 — phase 4 of #858, the last of the three. Stacked on #923; base is feat/917-machine-readable-results, so review in order.

The honest version, and why that is the finding

Nothing recorded whether a check had ever been red. That is the gap that let 39 checks across 35 suites ship unable to fail, three of them inside the suite whose whole purpose is to stop exactly that.

This ledger records that a named check was observed red in a recorded run. It does not claim the check is proven able to fail. That is a stronger statement, it needs a named mutation applied deliberately, and conflating the two would put a claim in the ledger that nothing measured — the defeated: 0 shape from VACUITY_MODES §1, a number that reads as evidence and is not.

So every entry currently reads never. #918 asks "nothing records whether a check has ever been red", and what this ships answers "and now something records that almost nothing has". A ledger of 608 rows, none ever observed red, is a measurement of how much of the corpus has never been attacked — and that measurement is worth having on day one.

What fills it

Not only deliberate mutation runs. Every real CI red fills it, every flake, every bisect — and those arrive whether anyone remembers to run something or not. A mutation run is the deliberate accelerator, not the only source. Worth stating, because "only a mutation run can retire debt" invites someone to build a mutation gate before it is needed.

The format

suite <TAB> check name <TAB> last observed red <TAB> mutation

The mutation column exists from v1 with nothing filling it automatically, because adding a column later means rewriting every entry. If an entry can record which mutation reddened a check, the catalogue a mutation gate would need builds itself out of work people already do by hand.

Two losses that are reported rather than prevented

A rename. The ledger is keyed by check name, and names here are prose that gets rewritten freely — most of why #917 exists. So a rename loses the check's history and reads exactly like a brand-new check that has never been red: the one state this ledger exists to distinguish. It cannot be prevented without a synthetic id someone would have to maintain, and this repository removed a hand-maintained list today for that reason. So a name that appeared while another disappeared is named. Both directions are required — reporting a rename on every added check is noise that gets it ignored.

A duplicated name. Two checks with the same name in one suite share a row, so one going red would mark both as observed red. Reported for the same reason. The corpus carries four today, which is how it was noticed: 612 records reduce to 608 rows.

The debt is two tracked files

Per #858's own constraint. test/check_ledger.tsv and test/check_ledger_budget.txt are in the tree, so a change to either is a diff a reviewer sees. PGC_SKIP_TIMING is the precedent for why this is not an environment variable: set in two workflow files, it suppressed whole suites for months and no diff ever showed it. Both numbers may only go down.

checks_never_observed_red 608
suites_not_covered        250

The second is the one that is easy to forget, and it is a real limit. The ledger can only be seeded from suites whose logs exist, and the matrix does not preserve them — so 250 of 251 suites have no rows at all, and the gate cannot refuse a new check in a suite it has never seen. Counting it separately stops "we ledger 608 checks" reading as "we ledger the corpus". It burns down as suites are seeded.

The gate refuses a check the ledger has never seen, so a new one cannot enter as silent debt — while the existing 608 are grandfathered, because a gate that fails on 3,762 unledgered sites is one somebody disables under deadline, and then we are back at PGC_SKIP_TIMING with extra steps.

Evidence

selftest    exit=0  FAILs=0   612 checks
            ledger: inputs=608 | observed red=0, never=608 | sum=608
gate        rc=0 against the committed files
            rows=608 | never observed red=608, budget=608, new=0
            registered=251 | covered=1, not covered=250, budget=250
pytest      27 passed
shellcheck  rc=0    docs_style  PASSED

What I did not build, deliberately

A harness-driven mutation gate. It needs a mutation catalogue per check to be anything other than random line-deletion, and that catalogue does not exist in machine-readable form yet — it is being hand-written, nine to eleven mutations per change, in the vacuity work happening now. This ledger is the right place for it to start accumulating, which is why the column is here. Building the gate first would be building the machine before the input.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Unuuvh3fRR67SceiGpfeeK

jdatcmd and others added 4 commits September 10, 2026 07:33
Closes #918, phase 4 of #858, and the honest version of it.

Nothing recorded whether a check had ever been red. That is the gap that let 39
checks across 35 suites ship unable to fail, three of them inside the suite whose
whole purpose is to stop exactly that. The gate answered "did anything print FAIL"
and had never answered "could anything print FAIL".

WHAT THIS LEDGER CLAIMS, AND WHAT IT DOES NOT. It records that a named check WAS
OBSERVED RED in a recorded run. It does NOT claim the check is proven able to
fail: that is a stronger statement, it needs a named mutation applied
deliberately, and conflating the two would put a claim in the ledger that nothing
measured -- the `defeated: 0` shape from VACUITY_MODES section 1, a number that
reads as evidence and is not.

SO EVERY ENTRY CURRENTLY READS `never`, and that is the finding rather than an
embarrassment. #918 asks "nothing records whether a check has ever been red"; the
answer this ships is "and now something records that almost nothing has". A ledger
of 608 rows, none ever observed red, is a measurement of how much of the corpus
has never been attacked, and that measurement is worth having on day one.

WHAT FILLS IT. Not only deliberate mutation runs. Every real CI red fills it,
every flake, every bisect, and those arrive whether anyone remembers or not. A
mutation run is the deliberate accelerator, not the only source. Raised by
OffgridwithJD, and it matters because "only a mutation run can retire debt"
invites someone to build a mutation gate before it is needed.

THE MUTATION COLUMN EXISTS FROM v1 with nothing filling it automatically, because
adding a column later means rewriting every entry. If an entry can record WHICH
mutation reddened a check, the catalogue a mutation gate would need builds itself
out of work people already do by hand -- the vacuity branches are writing nine to
eleven per change, each chosen to revert one property.

A RENAME IS REPORTED, NOT SILENTLY ABSORBED. The ledger is keyed by check name,
and names here are prose that gets rewritten freely -- which is most of why #917
exists. So a rename loses the check's history and reads exactly like a brand-new
check that has never been red, the ONE state this ledger exists to distinguish.
It cannot be prevented without a synthetic id someone would have to maintain, and
this repository removed a hand-maintained list today for that reason. So a name
that appeared while another disappeared is NAMED. Both directions are required:
reporting a rename on every added check is noise that gets it ignored.

A DUPLICATED NAME SHARES ONE ROW, so one of the two going red would mark BOTH as
observed red -- a claim about a check nothing attacked. Also reported rather than
prevented. The real corpus carries four today, which is how it was noticed at
all: 612 records reduce to 608 rows.

TWO TRACKED FILES CARRY THE DEBT, per #858's own constraint. check_ledger.tsv and
check_ledger_budget.txt are in the tree, so a change to either is a diff a
reviewer sees. PGC_SKIP_TIMING is the precedent for why this is not an
environment variable: set in two workflow files, it suppressed whole suites for
months and no diff ever showed it. Both numbers may only go down.

The second number is the one that is easy to forget: suites_not_covered is 250 of
251, because the ledger can only be seeded from suites whose logs exist, and the
matrix does not preserve them. Counting it separately stops "we ledger 608
checks" reading as "we ledger the corpus". It is a real limit, stated rather than
hidden, and it burns down as suites are seeded.

The gate refuses a check the ledger has never seen, so a new check cannot enter as
silent debt -- while the existing 608 are grandfathered, because a gate that fails
on 3,762 unledgered sites is one somebody disables under deadline.

Evidence: selftest exit 0, 612 checks, 0 failures; the gate rc=0 against the
committed files; 27 pytest; shellcheck rc=0; docs_style PASSED.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Unuuvh3fRR67SceiGpfeeK
…onvention (#918)

Reported by OffgridwithJD, who found it by asking whether their own six branches
added duplicate check names. All six did.

THE FOUR DUPLICATES I REPORTED ARE NOT FOUR INSTANCES, THEY ARE A CONVENTION.
`premise: the pytest layer is where THIS PART thinks it is` says "this part"
precisely so the sentence can be copied into any part, and main already carries
two copies of it and two of `premise: the harness library is where this part
thinks it is`. The count is the count TODAY and grows with every part anyone adds.

So a ledger keyed on (suite, name) is not a ledger of checks. It is a ledger of
check NAMES, and the two differ by however many parts share a boilerplate premise
-- with the consequence this ledger cannot have: one sharer going red marks every
other as observed red, a claim about a check nothing attacked.

THE KEY IS NOW (suite, part, name). The part comes from pgc_record, derived from
BASH_SOURCE, which #917 adds in the commit below this one. Not a convention
change, so the next part written the same way is keyed correctly without anyone
remembering, and it closes a blind spot in the rename detector: a premise moving
between parts was indistinguishable from a rename and is now an appearance and a
disappearance in two different parts, which the detector does not pair.

Measured over a real run, 583 records:

    distinct (suite, name)         579
    distinct (suite, part, name)   582

One duplicate survives, and it is a GENUINE one rather than a convention artifact:
340-the-binary-must-be-built-from asks `premise: the fixture fingerprints at all`
twice within the same part. Naming that precisely, instead of losing it among
three copied premises, is the point.

The reviewer also renamed the premises in all six of their own in-flight branches
so each names its own subject -- thirteen renames, each part now contributing zero
duplicated names -- which is why this number does not grow by six the moment those
land. They left main's copies alone as this PR's scope, which is right: this
change reports rather than prevents.

Re-seeded from a green run, keyed the new way: 613 rows, none ever observed red.

Evidence: selftest exit 0, 614 checks, 0 failures; gate rc=0 against the committed
files; 140 pytest passed; shellcheck rc=0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Unuuvh3fRR67SceiGpfeeK
…tor caught the rename (#918)

OffgridwithJD looked at the collision that survived the (suite, part, name) key and
found it is not a repeat of one question. At 340:268 and 340:894,
`premise: the fixture fingerprints at all` is asked once of the SOURCE-PARTITION
fixture and once of the LOCALE SWEEP:

    268   _fs_before="$(pgc_source_fingerprint "$_fs_rp")"
          -> did the a.c/b.c partition fixture produce a fingerprint at all
    894   _lc_vals="$_lc_vals $(LC_ALL=... pgc_source_fingerprint "$_lc/tree")"
          -> did every locale in the sweep produce one

Two questions about two different code paths, sharing a sentence. That is the case
where a shared ledger row is WRONG rather than merely imprecise: one failing marks
the other's premise as observed red, and they are not about the same code.

I FIXED IT RATHER THAN REPORTING IT, which departs from this PR's stance, and the
distinction is worth stating. "Report rather than prevent" is right about the
CONVENTION -- `premise: the pytest layer is where THIS PART thinks it is` is
phrased to be copied, rewriting the corpus's shared premises is churn, and the
tool exists to surface them. This is not that. It is one file, two lines, and the
two premises are about different subjects. Naming them apart takes the corpus to
ZERO collisions: 614 records, 614 distinct keys.

AND IT IS THE RENAME DETECTOR'S FIRST TEST ON REAL DATA rather than fixtures. Run
against the ledger seeded before the rename:

    possible rename: premise: the fixture fingerprints at all
                  -> premise: every locale produced a fingerprint
       (in harness_selftest/340-the-binary-must-be-built-from, history: last red never)
    rename scan: appeared=2, vanished=1

appeared=2 against vanished=1 is honest: the two premises shared ONE row before, so
one of the new names has no predecessor to pair with.

Re-seeded: 614 rows, none ever observed red.

Evidence: selftest exit 0, 614 checks, 0 failures, 0 collisions; gate rc=0 against
the committed files; shellcheck rc=0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Unuuvh3fRR67SceiGpfeeK
@OffgridwithJD

Copy link
Copy Markdown
Collaborator

Reviewed 671a9460. One finding, in the rename detector, and it defeats the detector in exactly the case it exists for. Everything else I could attack held up, and I am listing what I verified so nobody re-checks it.

The finding: zip pairs by position, so one unrelated appearance hides a genuine rename

cmd_rename_scan builds two sorted lists and pairs them positionally, then discards pairs whose parts differ:

for (s_a, p_a, n_a), (s_v, p_v, n_v) in zip(appeared, vanished):
    if (s_a, p_a) != (s_v, p_v):
        continue

So an appearance in an earlier-sorting part consumes the slot and the rename in a later part is never examined. Measured, with a control:

ARM 1  partB renamed (old-b -> zzz-renamed, history: last red 2026-01-01)
       AND partA gained an unrelated check
         rename scan: appeared=2, vanished=1
         rc=0                                   <- nothing reported

ARM 2  the SAME rename, unrelated appearance removed
         possible rename: old-b -> zzz-renamed (in selftest/partB, history: last red 2026-01-01)
         rename scan: appeared=1, vanished=1
         rc=1                                   <- correctly reported

The rename is detectable; what hides it is an unrelated check appearing in a part that sorts earlier. partB's history then resets to never silently, which is the one state this ledger exists to distinguish — and the more parts move in a single run, the more renames are lost.

Worth saying plainly: your own real-data run reported its rename because both appeared entries happened to be in part 340, so zip's first pair matched. The ordering was lucky, not load-bearing.

The fix is to group before pairing rather than filtering after:

from collections import defaultdict
app, van = defaultdict(list), defaultdict(list)
for s, p_, n in appeared: app[(s, p_)].append(n)
for s, p_, n in vanished: van[(s, p_)].append(n)
for key in sorted(set(app) & set(van)):
    for n_v, n_a in zip(sorted(van[key]), sorted(app[key])):
        ...

And the arm that would have caught it is the one the current tests lack: a fixture where two parts move in the same run. Every existing arm moves one part at a time, which is why ARM 2 passes and ARM 1 does not.

I also confirmed the inherent false positive is still there and still acceptable: an unrelated add and delete in one part reports possible rename: deleted-check -> brand-new-unrelated, carrying the deleted check's last red 2025-12-31. That is unavoidable without a synthetic id, you say "possible rename" rather than "rename", and the alternative is silently resetting — so it is the right trade. It is worth one sentence in the docstring that the pairing is positional within a part and therefore a guess between two movers.

What I verified and found sound

Idempotence. Merging the same run twice leaves the ledger byte-identical. A later date on an already-red check advances it (2026-09-10 → 2026-09-11), and a subsequent GREEN run does not erase the history. Those are the three semantics "last red" needs and all three hold.

The gate has teeth. A record with no ledger row is refused by name — not in the ledger: selftest partA SNEAKY-NEW, rc=1 — with the control at rc=0 when only the known check is present. So "the gate cannot refuse a new check in 250 of 251 suites" is a coverage limit, not an inert gate.

The key. 614 records, 614 distinct keys, zero collisions. Deriving the part from BASH_SOURCE rather than from a declaration is the better half of that fix, because it cannot go stale the way a convention can. And the part derivation adds no fork: five command substitutions inside pgc_record, all five $(( )) arithmetic, at 3,762 call sites.

The 340 rename you took from my finding reads correctly now — 268 is the partition fixture's premise and 894 is the locale sweep's, and they are two different sentences about two different code paths rather than one sentence shared.

Two smaller things

test/selftest/410-a-check-must-have-been-red.sh collides with a part of mine. My sentinel branch already holds 410, gated, and four others hold 420-460. A duplicate numeric prefix breaks nothing — the parts are sourced from a sorted glob of distinct files — so this is tidiness. But moving one file in an unreviewed PR is cheaper than re-gating six branches, so I would rather you took 470 and I kept 410-460. If you would rather not, say so and I will shift mine at rebase; I am not going to argue about a prefix.

The suites_not_covered wording is right now. Stating the operational consequence — the gate cannot refuse a new check in 250 of 251 suites — is what stops "614 checks" reading as "the corpus". Keep the count separate from the row count exactly as it is.

Not approving yet, on the rename finding alone. It is a six-line fix plus the two-parts-move arm, and I will approve as soon as both are in — the rest of this PR is in good shape, and seeding from a green run rather than from a half-built feature's reds was the right call for the reason you gave.

@linuxhikerpm linuxhikerpm left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking at exact head 671a946059ddaf88b3289d28ee891c20ab8fdb0d: the ledger is not yet an operational or fail-closed gate.

  1. pgc_ledger.py is never invoked by the runner or CI. No workflow consumes gate, merge, or rename-scan, and runner logs are deleted. CI reds therefore cannot populate the ledger and the gate cannot reject a new check.

  2. Missing/malformed inputs false-green. read_records() ignores unreadable files, short records, and unrecognized content. Reproduced rc=0 for a nonexistent log, an empty log, and a known record missing its verdict. Require nonempty reconciled input and fail closed on read/parse errors.

  3. Mutation attribution is invocation-wide. One --mutation value is copied to every failure in every input log; two independent failures merged with MUTATION_A both acquired that mutation. The issue requires per-check attribution derived from the run.

  4. Multi-log handling is incorrect. Two ordinary logs containing the same check are reported as a duplicate. A rename detected with the after-log alone disappears when before+after are supplied. Global positional zip(appeared, vanished) also misses a real rename when unrelated movement in other parts shifts ordering; group by (suite, part) before pairing and distinguish runs when checking duplicates.

  5. The tracked budgets are not monotonic. Debt=2 passes with budget 2 and still passes after increasing the committed budget to 999. suites_not_covered can also be bypassed by omitting optional --registered. Compare against the prior tracked ceiling and require registry input for coverage claims.

  6. git diff --check reports 632 trailing-whitespace errors, including all 614 ledger rows because an empty mutation is encoded as a trailing tab.

The (suite, part, name) key improvement is sound and fixes cross-part collisions, but the paired tests use only valid single logs and do not exercise these failure modes. Please add both shell and pytest red/control arms for each integrity boundary and wire the resulting gate into retained runner/CI artifacts.

@linuxhikerpm

Copy link
Copy Markdown

Two additional exact-head findings from the full adversarial pass:

  1. last red can move backward or become unknown. cmd_merge blindly overwrites the prior date for every FAIL. Reproduced by merging observations dated 2026-09-10, then 2026-09-01: the ledger regressed to 2026-09-01; a subsequent merge without --date replaced it with unknown. Validate observation dates and retain the maximum known timestamp.

  2. The required shell twin can disappear without detection. Deleting test/selftest/410-a-check-must-have-been-red.sh does not red another gate: harness_selftest.sh sources whatever glob entries remain, while the pytest half is not itself run by current CI. Add a mechanically reconciled manifest/twin check so removing either behavioral half fails.

These supplement the changes-requested review at the same head; the existing six blockers still stand.

@OffgridwithJD

Copy link
Copy Markdown
Collaborator

Adversarial pass on 671a9460. Three findings, in descending order of how much they matter. The rename-pairing defect reported earlier is separate and still stands.

1. The mutation column silently overwrites, so it cannot accumulate a catalogue

That column exists so the mutation catalogue builds itself out of work people already do by hand. It keeps only the last one:

merge --date 2026-09-10 --mutation "M1 first"    ->  selftest partA c1  2026-09-10  M1 first
merge --date 2026-09-11 --mutation "M2 second"   ->  selftest partA c1  2026-09-11  M2 second

No warning, no append, no record that M1 ever existed. A check that two different mutations reddened ends up claiming one of them, and which one depends on run order. That defeats the stated purpose rather than limiting it: six branches of mine carry 55 one-property mutations with their reddened arms named, and merging those runs one at a time would leave a single mutation per check.

The fix is a decision, not code: either the column is a set (append, deduplicated) or it is explicitly "the most recent mutation that reddened this check" and the docstring says so. I would take the set — the value is in the accumulation — but either is honest and the current state is neither.

2. Drift is one-directional: a ledger row whose check no longer exists is kept silently

ledger:  selftest partA still-here        never
         selftest partA DELETED-LONG-AGO  2025-01-01
records: RESULT selftest partA still-here PASS

gate  -> ledger gate: rows=2 | never observed red=1, budget=614, new=0     rc=0
merge -> the DELETED-LONG-AGO row is still there, still carrying 2025-01-01

The gate reports new (records with no row) and refuses them. It never reports the other direction, and merge never prunes, so dead rows accumulate and rows drifts upward from the corpus. That matters because the budget numbers are counted over rows: a dead row that was once red makes checks_never_observed_red look better than the corpus warrants, and a dead row that was never red inflates the debt. Either way the number stops being about the corpus.

rename-scan does compute vanished, so the information exists — it is just not on the gate's path and nothing prunes. Reporting dead rows (rather than failing on them, for the reason you gave about a gate that reddens when someone fixes something) would be consistent with how you handled stale debt.

3. A stale number inside the file whose whole subject is stale numbers

test/check_ledger_budget.txt explains suites_not_covered with: "Counted separately so that 'we ledger 605 checks' cannot read as 'we ledger the corpus'." The file's own value two lines below is checks_never_observed_red 614. The illustrative number is the pre-rename count.

Trivial to fix and worth fixing precisely because of where it is.

On the budget file being typed at all

I went looking for this as a defect and came away agreeing with it, so I will say so. checks_never_observed_red 614 and suites_not_covered 250 are hand-maintained numbers, which is normally the thing #908 spent a day removing — but the file argues its own case and the argument is right: a tracked file makes a change to the debt a diff a reviewer sees, where an environment variable would not, and PGC_SKIP_TIMING suppressing whole suites for months with no diff is the precedent. The "both numbers may only go down" rule is what makes a typed number safe here, because raising one has to read as adding debt. Keep it.

Verified and sound

Merging one run twice is byte-identical. A later date advances last-red; a subsequent green run does not erase it. A mutation supplied on a run where the check stayed green is correctly not recorded. The gate refuses a record with no ledger row by name, with a control at rc=0. 614 records give 614 distinct keys after the 340 rename, and the key's part comes from BASH_SOURCE rather than a convention.

One merge note, recorded here rather than only in messages: test_mutation_ledger.py is database-free, so once #921 is in main this PR needs "test_mutation_ledger.py" in NO_CLUSTER in its own diff.

…annot deadlock (#918)

jd's direction: shipping the recording half alone would be shipping something
that does nothing. A ledger nothing feeds and nothing reads is not a staged
feature, it is a data file with no producer and no consumer -- the `defeated: 0`
shape one level up. So the gate is fixed rather than dropped.

Ten findings, from @linuxhikerpm and OffgridwithJD, every one reproduced first.

IT IS NOW FED AND IT NOW REFUSES. Nothing in the repository called the tool: zero
references in .github/, zero in the runner, so "the gate refuses a check the
ledger has never seen" was false as written. run_all_versions.sh now runs it
before it removes the build directory, which is the only place a matrix run can
reach every suite's log. CI verifies; humans commit the ledger, because a ledger
CI rewrote by itself would be a file nobody reads changing under everybody.

THE DEADLOCK WAS THE DESIGN, NOT THE NUMBER. Bounding
`checks_never_observed_red` means every added check breaks the gate: a new check
enters as `never`, so the only way to land one was to raise a number the file says
in capitals may only fall. It shipped at 614 rows, 614 never, ceiling 614.

The two numbers are different kinds of thing and the file now says so.
`checks_never_observed_red` is a CENSUS, asserted to match the ledger in both
harnesses so it cannot drift. `suites_not_covered` IS a ceiling, because adding a
check to a covered suite does not move it, and the gate compares it against the
previously committed value and refuses an increase -- so "may only fall" is
mechanism rather than prose.

AND THE REFUSAL IS RESTRICTED TO SUITES THE LEDGER COVERS, which is the MEANING
of that ceiling rather than a softening of the gate. Without it the gate refuses
every check of all 250 uncovered suites and reddens the whole matrix on its first
run, which is a gate somebody turns off within the week. It tightens on its own as
suites are seeded, and an arm pins that a new check in a NOW-covered suite is
refused again.

FAIL CLOSED. A nonexistent log, an empty one and a record missing its verdict each
returned rc=0. An integrity failure that reads as a clean run is worse than no
gate because it certifies. They return 2, distinguishable from a real refusal at
1, and --registered is required rather than silently skipped.

THE MUTATION COLUMN ACCUMULATES a set rather than overwriting, because keeping
only the last one records the most recent attack rather than the catalogue the
column exists to become. One --mutation cannot be attributed across several logs
at once.

MULTI-LOG HANDLING was wrong in two directions. The same check in two logs is two
RUNS and was reported as a duplicate; the same name twice in ONE log is the
duplicate, and is what is reported now. Renames are grouped by (suite, part)
before pairing, because a global positional zip misses a real rename whenever
unrelated movement elsewhere shifts the ordering -- and a before-log and an
after-log together are refused rather than silently finding nothing, since the
vanished name is present in the union.

614 rows ended in a tab, because an empty mutation was an empty last field. An
absent mutation is now `-`; `git diff --check` reports nothing.

Evidence: selftest exit 0, 638 checks, 0 failures; 9 pytest; shellcheck rc=0;
docs_style PASSED; git diff --check clean. Both harnesses carry red-and-control
arms for every integrity boundary above, including the deadlock as its own arm.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Unuuvh3fRR67SceiGpfeeK
…s' into feat/918-mutation-ledger

# Conflicts:
#	CHANGELOG.md
@OffgridwithJD

OffgridwithJD commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Treated as new, as you asked. I ran the thing the redesign turns on — a real PG 17 matrix on 2289f10d — and the central claim holds. One finding, about the sentence the design is most confident in, and one blocker that is not about the code at all.

The blocker: CI is not running, and will not start

mergeStateStatus=DIRTY, mergeable=CONFLICTING. GitHub does not run workflows on a conflicted PR, so the newest run on this branch is for 671a9460 — the old head. 2289f10d has no run.

The cause is the base. This PR targets feat/917-machine-readable-results, not main, and that branch moved when #923 gained its CHANGELOG entry at dddee7fc. Measured:

merge 2289f10d into main                               CLEAN
merge 2289f10d into feat/917-machine-readable-results  CONFLICT (content): CHANGELOG.md
merge 2289f10d into feat/916-reconcile-accounting      CLEAN

One file, one region: your entry and #923's under the same heading.

It is fed, and I can prove it from a real run

Full test/run_all_versions.sh on /usr/local/pg17a, tree asserted clean at 2289f10d, under the lock, with ci.yml's environment (PGC_SKIP_TIMING=1 PGC_JOBS=4 PGC_REQUIRE_ISOLATION=1):

  ledger census: rows=638 | never observed red=638, ever red=0, new this run=0
  ledger coverage: registered=251 | covered=1, not covered=250, ceiling=250
  suites that ran: 242 of 251 (skipped: 9, incomplete: 0)
ALL VERSIONS PASSED
RUN_ALL_VERSIONS_RC=0

So the gate reached the real logs of a real matrix, and new this run=0 says the committed ledger already covers every check that ran. Note not covered=250 sits exactly on the ceiling of 250, which is the tight place to be: one newly registered suite that nobody seeds reddens it.

And the refusal works. Driven with a record the ledger has never seen, in a covered suite:

    not in the ledger: harness_selftest	030-assertions	a check nobody has ever written
    1 check(s) the ledger has never seen. Regenerate it with: ...
  rc=1

Fail-closed holds where you said it does — a nonexistent log, an empty log, a malformed record, and a missing --registered are all rc=2, distinguishable from the rc=1 refusal. rename-scan groups by (suite, part): two parts each renaming a check in one run produces two correctly paired renames rather than the positional mis-pairing, and a before+after union in one call is refused with rc=2. The ledger is LC_ALL=C sorted, so an added check inserts at a content-decided point — the #554 lesson, and it matters now because every PR that adds a harness_selftest check regenerates this file.

The finding: "the ceiling may only fall" is true of the tool and false of the wiring

check_ledger_budget.txt says "the gate compares this value against the previously committed one and refuses an increase, so widening the debt is an edit a reviewer sees AND a gate refuses." The second half does not happen in any wired invocation.

The monotone block is guarded by if args.against:. run_all_versions.sh:1336-1340 passes --ledger, --budget, --registered and the logs. No --against. Measured, with suites_not_covered edited from 250 to 9999 in the tracked file:

invocation rc
the runner's exact form, no --against 0 the raise is not refused
--against HEAD, absolute path (what the runner passes) 0 no budget at HEAD, so there is no prior ceiling to compare
--against HEAD, repo-relative path 1 suites_not_covered was raised from 250 to 9999: the ceiling may only fall

So the tool is right, and 410's scratch-repo arms prove it. What nothing does is compare the real committed budget against git.

And adding the flag at that call site does not fix it. _committed_budget runs git show <ref>:<path>, the runner passes "$builddir/test/check_ledger_budget.txt", and git cannot resolve an absolute path as a pathspec — it returns None and the gate prints a line that reads like a pass. That is row two of the table, which is the form the fix would actually take.

The arm that closes it belongs where the repository is rather than where the build copy is: compare the working budget against git show HEAD:test/check_ledger_budget.txt, repo-relative, in the real tree, and redden on an increase. This is the same shape as the finding one level down — a gate nothing invokes — with "nothing invokes it with the argument that makes it a gate" in place of "nothing invokes it".

What I checked and am not relitigating

The runner does invoke the gate, it runs before rm -rf "$builddir", and a refusal sets verfail=1; I read all three and the static arms at 410:231-237 pin them. The committed census matches the committed ledger — 638 rows, 638 never, and 410:251-252 asserts that against the real files, not fixtures. Every row has five fields and none ends in a tab.

On shape, since you asked

The census/ceiling split is right, and the reason is stated correctly: every new check enters as never, so bounding it makes landing a check require raising a number the file says may only fall. That is a deadlock, not a budget. I also think jd's refusal of the half-landing was right, and the restriction of the refusal to covered suites is the meaning of suites_not_covered rather than a softening — your own arm that a new check in a now-covered suite is refused again is what makes that a tightening.

One consequence worth naming out loud: today the ledger covers one suite, so the gate refuses unknown checks in harness_selftest and nowhere else. That is the acknowledged debt, and the ceiling is what forces it down. It also means my #926 will redden your gate the moment both land, because it adds six checks to selftest 080 — intended workflow, reviewable diff, mentioned only so the merge order surprises neither of us.

A main-tree defect this run surfaced, which is not yours to fix here

The same report prints two different "accounted" totals:

  population reconciliation: registered=251 | accounted=237, not dispatched=4, known debt=10, unaccounted=0 | sum=251
  of those, 235 accounted for their checks and 7 did not

237 and 235, differing by exactly 2. The population line uses the wide reader (pgc_log_shows_any_accounting) and the breakdown line derives from the narrow one (pgc_log_shows_accounting), so the same question gets two answers three lines apart and a reader cannot tell which is the claim. The gap of 2 is the two suites that keep their own tally rather than lib.sh's — bench_guards and docs_style — which is the count I measured independently earlier today from the other direction. This came in with #922 and is on main now, so I will raise it there rather than add it to your pile.

…ng (#918)

Reported by OffgridwithJD, measured on the shipped form. I wrote that the gate
"now refuses to see the ceiling raised above its previously committed value".
That was true of pgc_ledger.py and false of run_all_versions.sh:

    the runner's exact invocation, no --against    rc=0   the raise is not refused
    --against HEAD, absolute path                  rc=0   "no prior ceiling to compare"
    --against HEAD, repo-relative path             rc=1   correctly refused

THE MIDDLE LINE IS THE ONE THAT MATTERS. `git show REF:PATH` needs a
repo-relative path and the runner passes an absolute one inside a copied build
directory, so _committed_budget returned None and the gate printed a note that
READS LIKE A PASS while the ceiling it was asked to enforce went unchecked. Asked
to compare, unable to compare, is not the same as nothing to compare -- and that
is the fail-open shape this whole change is about, in the code that closes it.

So the tool resolves the path itself, through the budget's own git toplevel, and
every failure to resolve it is an ERROR. The caller no longer has to know.

WHICH REF IS NOW A DECISION RATHER THAN A DEFAULT. `--against HEAD` compares a
committed file against ITSELF: for any change already committed the working
budget and HEAD's are identical, so it catches only an uncommitted raise. The
property that matters is that a branch may not raise the ceiling relative to
MAIN. The runner prefers origin/main, falls back to HEAD, and PRINTS the fallback
and what it costs, because a silent fallback is a gate quietly enforcing less
than it claims.

THE SCRATCH-REPO ARMS WERE NECESSARY AND NOT SUFFICIENT, which is the
gate-nothing-invokes finding one level down: they proved the tool while no wired
invocation exercised it. There are now arms in the REAL tree at the REAL path --
an absolute path resolves rather than shrugs, a budget git has never seen is an
integrity failure rather than a note, and raising the tracked ceiling in place is
refused, with the file restored byte-exact.

Also in this commit: the merge of #923's base, whose CHANGELOG entry conflicted
with this one. Both entries are kept, #917 then #918, since they describe two
changes under one heading. #925 was CONFLICTING against its base, which is why no
CI had run on it.

Evidence: selftest exit 0, 678 checks, 0 failures; 145 pytest passed (the 35
errors are /usr/local/pg18a absent on this host, identical on main); shellcheck
rc=0; docs_style PASSED; git diff --check clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Unuuvh3fRR67SceiGpfeeK
Reported by OffgridwithJD, and it is my own sentence one level up.

`origin` is not a fixed thing. In a contributor's clone it is their FORK -- they
measured theirs 446 commits behind upstream -- so `--against origin/main` compared
the ceiling against a 16-day-old main and printed that it had compared.

THE DIRECTION IS WHAT MAKES IT FAIL OPEN. The ceiling may only fall, so an older
main carries one that is higher or equal, and a raise passes whenever the stale
prior is high enough. Never falsely red, silently weaker, with a line that reads
like the enforcement happened. "Asked to compare, unable to compare, printing a
note that reads like a pass" was the previous finding; this is the same shape with
"compared against the wrong thing" in place of "could not compare".

AND THE FALLBACK WAS THE SAME SHAPE AGAIN. When origin/main did not resolve the
runner printed the cost and proceeded with `--against HEAD`, which compares a
committed file against itself and therefore catches nothing for any change under
review. A fallback that enforces less while saying so is still a gate enforcing
less, and one level down I had already made an unresolvable prior an error.

SO THE PRIOR IS RESOLVED, AND NEVER GUESSED. `--against auto` takes
GITHUB_BASE_REF, which in CI names the PR's target and IS the prior by definition,
or the local main's configured upstream outside CI, which is the per-clone answer
to "which main is mine". Neither available is rc=2. The ref used is printed, so a
reader can see which prior the comparison actually made.

AND CI MUST FETCH THAT BASE. actions/checkout takes one ref at depth 1 and the
suites job set no fetch-depth, so the base branch is absent and `auto` would stop
the run -- correctly, but for a reason the workflow owns rather than the author.
The suites job now fetches it at depth 1, guarded on github.base_ref so a push
build does not fail on it. Only the file at that commit is read.

Arms for all of it, including the two that would have caught me: no base ref and
no upstream is an integrity failure with its reason named, and a GITHUB_BASE_REF
whose ref is absent says the checkout needs to fetch it rather than falling back.
Three arms testing the design this replaces were deleted rather than left to pass
against nothing.

Evidence: selftest exit 0, 686 checks, 0 failures; 9 pytest; shellcheck rc=0;
docs_style PASSED.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Unuuvh3fRR67SceiGpfeeK
@OffgridwithJD

Copy link
Copy Markdown
Collaborator

The CI half of the origin/main finding is now measured rather than argued, from this PR's own run.

suites (PG 17) of run 34502198282, at fb872370, verbatim:

  origin/main does not resolve here, so the ledger ceiling is
  ledger census: rows=675 | never observed red=675, 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

So in CI origin/main did not resolve, the fallback to HEAD fired, and the comparison was 250 -> 250 — a committed file against itself, which catches nothing for any change under review. The ceiling protection was inert in the only place this gate runs for real, with a line above it that reads like enforcement. I had called my local reproduction a construction rather than proof; this is the proof, and it is stronger than the prediction.

Fixed at 3d880f49 (--against auto: GITHUB_BASE_REF in CI, main@{upstream} outside, an error if neither), with the fallback removed and ci.yml fetching the base at depth 1.

One residual on the non-CI path, measured in the clone the new docstring cites:

main@{upstream}    = origin/main
resolves to        = 93a2eda   2026-08-25
branch.main.remote = origin            (my fork)
author/main        = f0f1f40   2026-09-10
origin/main is 446 commits behind author/main

main@{upstream} is origin/main here, because git push -u origin main is what writes that config and origin is the contributor's fork. So auto outside CI resolves to the same stale ref the hardcoded version did — the route changed, the destination did not. It is materially better, because the ref used is now printed and a reader can see origin/main; what a reader cannot see is that it is 446 commits behind, and the direction is still the permissive one.

I am not arguing for a different ref — "the configured upstream of the local main" is the right answer to "which main is mine" and I cannot see a better one that does not guess. I would add the distance:

git rev-list --count <prior>..HEAD

printed beside the ref. "comparing against origin/main (446 commits behind HEAD)" tells the reader what the comparison is worth; "comparing against origin/main" does not. Same move as printing the ref, one step further, and it costs nothing when the number is zero.

Everything else in my earlier review stands as written, including the matrix evidence on 2289f10d — the gate reached the real logs of a real run, which is the claim the redesign turns on.

…anged and the destination did not (#918)

Reported by OffgridwithJD, who measured it in their own clone rather than
predicting it.

`main@{upstream}` is the per-clone answer to "which main is mine", and in a
contributor's setup it resolves to their FORK -- `git push -u origin main` is what
sets that config. Theirs is 446 commits behind upstream, so `--against auto`
outside CI lands on exactly the ref the hardcoded `origin/main` did. The route
changed; the destination did not.

It is not the same defect: the ref used is printed, so a reader can see
`origin/main`. But a reader cannot see that it is 446 commits stale, and the
direction still fails open -- the ceiling may only fall, so an older main carries
a higher one and a raise passes whenever the stale prior is high enough.

There is no better ref to pick that does not guess, and I am not going to guess.
So the weakness is made VISIBLE instead:

    ceiling against origin/main (446 commits behind HEAD): 250 -> 250

Naming the ref told a reader WHICH prior was used. This tells them what the
comparison is worth. It costs nothing when the number is zero, where the label is
omitted entirely.

AND THE CI HALF IS NOW MEASURED RATHER THAN CONSTRUCTED, from my own run 34502198282 on fb87237:

    origin/main does not resolve here, so the ledger ceiling is
    ceiling against HEAD: 250 -> 250, which does not rise

The fallback fired, the prior was HEAD, and the comparison was a committed file
against itself -- catching nothing for any change under review, in the only place
this gate runs for real, with a line above it that reads like enforcement. The
previous commit removed that fallback and made CI fetch the base; this is the
evidence it needed to be removed rather than kept with a warning.

Evidence: selftest exit 0, 690 checks, 0 failures; 9 pytest; shellcheck rc=0;
docs_style PASSED.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Unuuvh3fRR67SceiGpfeeK
@OffgridwithJD

Copy link
Copy Markdown
Collaborator

CI is red on 380f9f68, both suites jobs, and the cause is structural rather than a slip: the PR that introduces the budget file cannot read a prior ceiling, because at the prior the file does not exist yet.

From the suites (PG 17) job of run 34503924812:

  ledger census: rows=687 | never observed red=687, ever red=0, new this run=0
  ledger coverage: registered=251 | covered=1, not covered=250, ceiling=250
    ledger integrity failure: --against refs/remotes/origin/feat/917-machine-readable-results
    was given, but test/check_ledger_budget.txt does not exist at
    refs/remotes/origin/feat/917-machine-readable-results, so there is no prior ceiling
    to compare against
  PG17 has a check the ledger has never seen, which is not a pass

I reached the same failure independently before seeing this. A full matrix run on 3d880f49 in my own clone returned RUN_ALL_VERSIONS_RC=1, resolving auto to origin/main — my 446-commit-stale fork, where the file also does not exist:

    ledger integrity failure: --against origin/main was given, but
    test/check_ledger_budget.txt does not exist at origin/main
    SOME VERSIONS FAILED

So auto resolves exactly as intended. Resolving correctly turns out not to be sufficient.

Three states, not two

The distinction needed here is the one this branch already drew one level down:

condition what it means what it should do
the prior ref does not resolve asked to compare, unable to compare error
the prior ref resolves, the file is absent there nothing could have been raised relative to a file that did not exist no prior
the prior ref resolves, the file is present a real comparison compare

The middle row is the first-landing case, and it is genuinely nothing to compare rather than could not compare. It is also self-clearing: once the file is on main, every later base carries it. That makes it a one-time condition — but it has to be expressed as the property (absent at the prior) rather than as a flag or a date, or it becomes the exemption nobody removes.

A second, smaller one in the same four lines

The runner prints

  PG17 has a check the ledger has never seen, which is not a pass

for an integrity failure. The gate takes care to distinguish rc=1 (a real refusal: a check the ledger has never seen) from rc=2 (it could not do its job), and the runner collapses both into the rc=1 sentence. That sends the reader to regenerate the ledger, which will not help — nothing is missing from it, and new this run=0 says so two lines above. Branching on the status the tool went to the trouble of distinguishing also removes the contradiction of new this run=0 sitting three lines from "has a check the ledger has never seen".

The half that is verified

Both of those runs reached the gate with real logs from a real matrix, printed the census and the coverage, and reported new this run=0. So the feeding half — the claim the redesign turns on — is now confirmed three times: my run on 2289f10d, my run on 3d880f49, and CI on 380f9f68.

jdatcmd and others added 2 commits September 10, 2026 11:09
…wo failure kinds are different (#918)

THE GATE CAUGHT ITS OWN BOOTSTRAP the first time it ran in CI, which is the right
outcome for a rule that could not be satisfied. From run 34503924812:

    ledger integrity failure: --against refs/remotes/origin/feat/917-machine-readable-results
    was given, but test/check_ledger_budget.txt does not exist at that ref

#925's base is #923's branch, where the budget does not exist because THIS change
adds it. So `auto` resolved the base correctly, fetched it, found no prior, failed
closed, and reddened the matrix -- and a PR introducing the file could never pass
its own gate. OffgridwithJD hit the identical failure independently in their own
clone, resolved to their fork, where the file is also absent.

THREE STATES, NOT TWO, which is the distinction this change already draws one
level down:

    the prior ref does not resolve            ERROR. Asked to compare, unable to.
    the ref resolves, file absent there       NO PRIOR. Nothing could have been
                                              raised relative to a file that did
                                              not exist.
    the ref resolves, file present            COMPARE.

The middle one is the first-landing case and it is genuinely "nothing to compare".
It is expressed as the PROPERTY -- absent at the prior -- rather than as a flag or
a date, so it clears itself: once the file is on main every future base carries
it, and there is no exemption left for anyone to forget to remove.

It is not a hole. Deleting the budget on a branch and re-adding it higher does not
reach it, because the file still exists at the prior and the comparison happens.

AND THE RUNNER COLLAPSED THE TWO FAILURE KINDS. The gate distinguishes rc=1, a
real refusal whose fix is to regenerate the ledger, from rc=2, the gate unable to
do its job at all. The runner reported both as "has a check the ledger has never
seen" -- sending the reader at a repair that cannot help, three lines below the
gate's own "new this run=0", which says the opposite. Also OffgridwithJD, from the
CI log of this branch. It now branches, and both arms still fail the major.

THE ARMS FOR IT WERE WRONG TWICE, both times in the same way. They counted
occurrences inside `grep -A6`, `-A8` and `-A12` windows, and every one broke the
moment the call site gained a comment: a window's size is a fact about formatting.
The block is now EXTRACTED and tested, as selftest 320 already does with the
runner's classifier. Then the extraction counted the block's own explanation as a
second occurrence of the sentences it was counting, so comments are stripped --
selftest 080's control problem, met in a fifth file tonight.

One arm was deleted rather than fixed: it read a variable defined ninety lines
below it, and the block that defines the variable already asserts the same thing.

Evidence: selftest exit 0, 696 checks, 0 failures; 145 pytest passed; shellcheck
rc=0; docs_style PASSED.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Unuuvh3fRR67SceiGpfeeK
Reported by OffgridwithJD, who scoped it precisely rather than leading with the
headline: they checked the production path FIRST and confirmed it was sound, so
"the fail-open hole is back" would have been wrong.

The three states this round exists to draw had collapsed back to two on one path.
The ref-resolution check lived inside the `auto` resolver, so it covered the
production call site and nothing else. An EXPLICIT ref that did not resolve fell
through to the file-absent branch:

    --against refs/heads/no-such-ref-xyz  ->  rc=0
      "no budget at refs/heads/no-such-ref-xyz (distance from HEAD unknown):
       this change introduces it, so there is no prior ceiling it could have raised"

Two things wrong in one line. The verdict is "nothing to compare" where the truth
is "could not compare". And it asserts a change introduces a file at a ref that
does not exist, one clause after saying the distance from HEAD was unknown -- the
code knew it could not resolve the ref and contradicted itself inside one
sentence.

Resolving now belongs to the reader, where every caller passes through, so the
file-absent branch describes only what it claims: a file missing at a ref that IS
there. All three states are pinned by arms against one scratch repo carrying a
branch with the budget and a branch without it -- unresolvable is rc=2 and never
says "introduces", absent-at-an-existing-ref is rc=0 and does, and present
compares. Without the last two the fix would be satisfied by refusing everything.

Not reachable from the runner, which always passes `auto`. It was a trap for these
arms, for anyone driving the tool by hand, and for whoever later passes a concrete
ref because `auto` was inconvenient.

AND THE PREVIOUS HEAD WENT 12/12 IN CI, with the gate's own output confirming
every piece of the redesign:

    ledger census: rows=693 | never observed red=693, ever red=0, new this run=0
    ledger coverage: registered=251 | covered=1, not covered=250, ceiling=250
    no budget at refs/remotes/origin/feat/917-machine-readable-results
      (1 commit behind HEAD): this change introduces it

Fed from real matrix logs, refusing nothing because nothing is unledgered, the
prior's distance printed beside it, and the bootstrap recognised rather than
fatal.

Evidence: selftest exit 0, 704 checks, 0 failures; 9 pytest; shellcheck rc=0;
docs_style PASSED.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Unuuvh3fRR67SceiGpfeeK

@OffgridwithJD OffgridwithJD left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving at exact head 71a7c9bcd02a57201d9e010bbfb94abd11835071 — 12/12 SUCCESS, mergeStateStatus=CLEAN.

Four rounds of findings from me, every one fixed, and I re-measured the last round on this head rather than reading the diff.

The three states, measured on this head in my own clone

--against refs/heads/no-such-ref-xyz   rc=2   "that ref does not resolve here, so the
                                               prior ceiling cannot be read"
--against auto (origin/main lacks it)  rc=0   "no budget at origin/main (464 commits
                                               behind HEAD): this change introduces it"
--against HEAD (this tree carries it)  rc=0   "ceiling against HEAD: 250 -> 250, which
                                               does not rise"

Three distinct verdicts, three distinct messages. The one that was wrong is right in the way that matters most: the unresolvable ref no longer claims "introduces". A message asserting something the code cannot know was the real defect there, more than the exit status was.

Why the chain is worth recording, since the final state hides it

round the finding how it failed
1 the monotone ceiling was unwired — if args.against: and the runner never passed it with the absolute build-dir path it would have printed "no prior ceiling to compare" and returned 0
2 the fix named origin/main, which is 446 commits stale in my clone the ceiling only falls, so a stale prior is always higher: fails open while printing that it compared
3 auto resolved correctly and the budget cannot exist at the prior, because this PR introduces it CI red on both suites
4 an explicitly passed unresolvable ref read as "no prior" said "this change introduces it" about a ref that does not exist

Round 2's CI half was settled by this PR's own log — origin/main does not resolve here then ceiling against HEAD: 250 -> 250, a committed file compared against itself — which is the evidence that the fallback needed removing rather than keeping with a warning.

The claim the redesign turns on, verified three times

The gate is fed from real matrix logs, not fixtures:

my run on 2289f10d    ledger census: rows=638 | never observed red=638, new this run=0
                      ledger coverage: registered=251 | covered=1, not covered=250, ceiling=250
                      ALL VERSIONS PASSED, rc 0
my run on 3d880f49    same census and coverage, then the bootstrap failure (round 3)
CI on 380f9f68        rows=675, and the fallback line that proved round 2

not covered=250 sits exactly on the ceiling, which is the tight place to be: one newly registered suite nobody seeds reddens it.

Fail-closed holds where it should — a nonexistent log, an empty log, a malformed record and a missing --registered are all rc=2, distinguishable from the rc=1 refusal. rename-scan groups by (suite, part), so two parts each renaming in one run pair correctly instead of positionally, and a before+after union in one call is refused. The ledger is LC_ALL=C sorted, so an added check inserts at a content-decided point rather than at the end — the #554 conflict lesson applied, and it matters because every PR adding a harness_selftest check will regenerate this file.

On shape

The census/ceiling split is right and the reason is stated correctly: every new check enters as never, so bounding that number makes landing a check require raising a number the file says may only fall. That is a deadlock, not a budget. Restricting the refusal to covered suites is the meaning of suites_not_covered rather than a softening, and the arm that a new check in a now-covered suite is refused again is what makes it a tightening.

Putting the ref check in the reader rather than in auto is also right: a fact belongs where every caller passes through it, and a policy belongs where it can change without touching the fact.

What I did not verify

I did not run the PG 18 matrix locally for this head; CI's suites (PG 18) is green and that is the evidence for it. My runs were PG 17 on an assert build. And the census of 693 never rows means nothing has been observed red yet — the ledger's value arrives as checks are attacked; what is verified today is that it refuses an unknown check and cannot be quietly widened.

@linuxhikerpm your block stands at 671a9460, four heads back. The monotone enforcement you would have been looking at then did not exist in working form — it was unwired at that commit, which is finding 1 above.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants