Conversation
7aeadd5 to
479c54c
Compare
|
Extended with the same class one level down, which building the first arm exposed.
Line order decided whether a recorded red observation survived. A merge that keeps both sides of a changed row turns Nothing else could catch it, and bounding the census cannot. The second is the realistic case, because ledger and budget are regenerated together. The refusal is in the reader, so every subcommand inherits it — one place, all callers: It is an integrity failure (rc=2), not a gate verdict — it belongs beside the other inputs that do not parse, because a ledger that cannot be trusted is not a gate result. So the two halves are the same shape at two levels: a set hid two checks in one run, a dict hid two rows in one file. The question that found both is what the input canonicalises before the guard sees it. Re-verified with both refusals armedA second full PG 18 matrix run, this time with the reader refusal in as well: |
|
Bounding the class rather than leaving it at "I found two." I swept every place in the harness tools where a guard's input is canonicalised before it is compared — Two are the defects this PR fixes, both in Two were already guarded, explicitly and for this exact reason — and they are the better-written half of the tool: They refuse the union rather than computing over it. The remaining nine are membership accumulators where a duplicate cannot change an answer — So the class is two, both here, rather than an open-ended worry. The question that found them is worth keeping: not "is this number gated" but "what did the input canonicalise before the gate saw it". |
479c54c to
ec762f3
Compare
|
A third instance of the same pattern, and this one I walked into myself. Simulating a merge of my own three open PRs — #1028, #1029 and this one all move The arm that polices that file could not see it. It fails closed, so this is legibility rather than a hole. Exit 4 reddens the job. What it does not say is that a line is duplicated — the reader has to work back from an int parse error to a merge resolution. Two arms now say it, and the removal proof is on the real file: plant a duplicate and the new arm reddens while the pre-existing one stays green. Keep-both is right for a changelog and wrong for a key-value file, and nothing in the tree said so. And the mechanism caught meI bumped
For whoever merges these threeThey conflict only on Derive with the recipe in the file rather than adding: after all three, guard should be 277+7+3+6. #1029 and #1028 also conflict on CHANGELOG, which is keep-both. |
402b74a to
1d31794
Compare
|
Correcting one of my own numbers here, because I presented it as a measurement. The PR body and the earlier commit message said:
That figure is withdrawn. Those logs were read out of the matrix build directory while the So most of what I counted were partial logs. Zero collisions across truncated logs is not What the conclusion actually rests on is unchanged, and it is the stronger evidence: a full The gate runs at the end of the matrix, against every suite's complete log, with the build It also still answers what #982 said nobody had measured — the gap between "checks that ran" I found this while using the same snapshot technique for #1015's measurement, where the |
jdatcmd
left a comment
There was a problem hiding this comment.
The mechanism is right and I confirmed it by mutation rather than by reading. One arm needs a fixture change before this goes in, and it is the one your own docstring says you measured.
What I verified. Both refusals are load-bearing:
remove `if key in rows: raise ...` -> test_the_ledger_refuses_two_rows_sharing_one_key FAILED
remove `rc = 1` from the shared block -> test_a_shared_key_is_refused_in_a_suite_... FAILED
restored 28 passed
So the class is covered. _by_run appends to a list rather than a set, which is what makes the refusal reachable at all, and that is the part I most expected to be wrong.
The finding. test_the_gate_refuses_two_checks_sharing_one_ledger_key survived the second mutation. Its expect.num(rc, 1, ...) does not discriminate, because the gate returns 1 on that fixture for two independent reasons. Running your fixture through the unmutated gate:
one ledger key covers 2 checks in dup.log: demo part1 shared name (PASS, PASS)
1 ledger key(s) cover more than one check. ...
not in the ledger: demo part1 shared name (on major 18) <- the second reason
1 check(s) the ledger has never seen. ...
rc=1
The ledger names some other check, so shared name is a new check in a covered suite and the pre-existing new-check refusal sets rc on its own. Your docstring says:
with a ceiling of 0 the gate already returns 1 for
suites_not_covered, and an arm asserting rc == 1 would pass before this refusal existed. Measured, and it did.
That is exactly the right instinct and it closed the suites_not_covered route. The new-check route was left open, and it produces the same 1.
The fix is one ledger line: name the shared check, so the new-check refusal has nothing to say.
- ledger = _w(tmp_path, "l.tsv", "demo\tpart1\tsome other check\t18\tnever\t-\n")
+ ledger = _w(tmp_path, "l.tsv",
+ "demo\tpart1\tsome other check\t18\tnever\t-\n"
+ "demo\tpart1\tshared name\t18\tnever\t-\n")Measured on the corrected fixture:
unmutated rc=1
shared-key refusal removed rc=0 <- now the only thing that moves rc
Worth extending the docstring to name BOTH routes it had to defeat, since the note is the reason the next person keeps the fixture that shape.
Not blocking. test_each_expected_count_is_stated_exactly_once is the arm I wanted to exist four hours ago. For the record, keep-both is not merely the wrong resolution for that file, it is wrong in a way that survives re-derivation by addition: this morning both my branch and #1028 had independently derived guard_tests 280 from 277, each against a tree holding only its own three arms, and the true merged value was 283. Neither side's number was correct and neither was a typo. I have guard_tests 283 on #1022, re-derived by collection.
You will need a rebase anyway. #1028 merged, so guard_tests on main is 287 and this PR's expected_tests.txt still moves from 280. Re-derive rather than add, per the above. Push the fixture fix with it and I will re-review the delta.
1d31794 to
83a5a2c
Compare
|
You are right, and I reproduced it before changing anything. So the arm was about the gate returning 1, not about this refusal. Pushed at This is the PR's own defect class in my own arm, which is worth naming rather than
One thing you did not flag and I removed anyway. That comment opened with "A LEDGER Your Rebased onto The delta since |
83a5a2c to
205bc98
Compare
|
Rebased onto Two stale fragments, left by earlier keep-both resolutions of this same file. Resolved to one coherent block rather than the union — which is the lesson the arms in this PR are about, arriving in the file they are about. Keep-both is right for a changelog and wrong where each key appears once, and the comments above the key decay the same way the key would. The CHANGES_REQUESTED fixture fix is in from the previous push at |
linuxhikerpm
left a comment
There was a problem hiding this comment.
Approving 205bc9836a772cf273a727ac363b7e613e382560.
The fixture hold from 1d317940 is closed. The ledger now names shared name, the docstring enumerates all three other routes to rc=1 (coverage ceiling, new-check refusal, unhandled exception), and the uncovered-suite arm still asserts not in the ledger: is 0 so that one cannot satisfy it either. CI is green on this SHA, including pytest-guards.
_by_run remaining a list, the tracked-file duplicate refused as rc=2, and the expected_tests line-form vs dict-form pair are still the right shape.
The PR is CONFLICTING with main after #1036. Re-derive guard_tests by collection on the rebase, not by adding the deltas. I will not merge it.
30ad6be to
9be6a49
Compare
|
Rebased onto The count history stopped reconciling to the countMy last resolution of
Restored from main and continued, so both histories now run end to end. Every number derived by collection on the merged tree, never by addition: (The Your finding, verified here by mutation rather than by readingThe mutation asserts it applied before running anything — one site matched, and the diff is printed. On the old fixture the first of those two PASSED under the same mutation; that is the whole delta. Green on the rebased treeThe conflict resolutions, so you can check them rather than take them
acting as: OffgridwithJD |
…one (commandprompt#982) A ledger row is keyed on (suite, part, name). Two checks with the same name in one part share a row, and two ROWS with the same key collapse into one. Neither is mis-recorded while everything passes. Both lose a red observation the moment one appears. --- THE GATE COULD NOT SEE TWO CHECKS IN ONE RUN ------------------------------- `merge` has printed "duplicate check name in one run" since commandprompt#982 was filed and returns 0, which is how three of them sat in one part of selftest/400 for a day. The instance was fixed by c3b13ae; this is the mechanism the issue called the more valuable half. `cmd_gate` builds its records as `sorted({(s, p, n, m) for ...})`. A set collapses the duplicate before any arm can count it, so the same canonicalisation that makes the rest of that function correct made this one class unreachable. The count now comes from the raw records through `_by_run`, which already existed for the two-runs-versus-one-duplicate distinction. EVERY SUITE, DELIBERATELY UNLIKE THE NEW-CHECK REFUSAL. That one is restricted to covered suites because it cannot know which of an uncovered suite's checks are new. This one needs no history: two records, one key, one log is decidable from the log alone. The ledger covers four suites of 253, so copying the restriction would close the class in four places only, and the next collision would sit in one of the other 249 until that suite was seeded. --- AND THE LEDGER COULD NOT SEE TWO ROWS IN ONE FILE -------------------------- `read_ledger` did `rows[key] = [...]`, so a duplicated key in the tracked file collapsed silently and the LAST line won. Measured on a two-line fixture, both orders: never first, then 2026-09-01 survivor last_red='2026-09-01' 2026-09-01 first, then never survivor last_red='never' the red is GONE Line order decided whether a recorded red observation survived. A merge that keeps both sides of a changed row turns `ever red` back into `never`. NOTHING ELSE COULD CATCH IT, and bounding the census cannot. check_ledger_budget.txt says `checks_never_observed_red` is a CENSUS and must not become a ceiling, because every new check enters as `never` and bounding it deadlocks. The gate compares the budget's number with the ledger's, and both come from the same dict, so they agree either way. Measured: with the budget regenerated alongside, an erased red passes the gate at rc=0. It is now refused as an integrity failure (rc=2), beside the other inputs that do not parse. So the two halves are the same shape at two levels. A SET hid two checks in one run; a DICT hid two rows in one file. The question that found both is what the input canonicalises before the guard sees it. --- MEASURED BEFORE WIDENING IT ----------------------------------------------- A gate that reddens 250 unmeasured suites is a gate somebody turns off, so the refusal was not widened on reasoning. A full PG 18 matrix ran with it armed for every suite: 247 suites ran, 6 skipped, 0 incomplete | ALL VERSIONS PASSED, RC=0 | 0 shared keys Check names are static, so one major's matrix measures this class completely rather than sampling it. THE LOAD-BEARING EVIDENCE IS THAT RUN, not a log snapshot. An earlier draft of this message cited "244 logs, 6651 records, 6651 distinct keys, 0 collisions" from per-suite logs copied out of the build directory while the matrix was still running. That figure is withdrawn: the copies were taken mid-write, and measuring the same way again showed 150 of 253 had no `checks run:` line yet. Zero collisions across truncated logs is not evidence of zero across complete ones. The full-matrix figure stands because the gate reads the logs after each suite has finished. It does still answer what commandprompt#982 said nobody had measured -- the gap between "checks that ran" and "rows the ledger can hold" across the other 250 suites -- because the gate saw every suite's complete log and refused nothing. --- VERIFICATION -------------------------------------------------------------- The committed ledger still loads at 1197 rows. The gate against the COMMITTED ledger and budget over a real 934-record harness_selftest log: rc=0, no shared-key line, census agrees at 1189. A planted duplicate check: rc=1, naming the key. A planted duplicate row: rc=2, naming the line and what it would lose. The refusal arm also asserts no Traceback in the output, because an unhandled exception exits 1 too and would satisfy an rc check on its own. Measured, it did: an earlier draft referenced `covered_suites` before it was defined, which compiles and fails at runtime. guard_tests 277 -> 283, re-derived by collection. 283 passed / 698 checks with --pgc-expect-tests 283 armed. docs_style.sh PASSED (11 checks). Long-sentence counts unchanged: CHANGELOG 860, TESTS.md 319. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uf6UoeBRZYLQZa4KxNiw8a --- AND A THIRD INSTANCE, IN THE ARM THAT POLICES THE OTHER TRACKED FILE ------- test_harness_deps.py read expected_tests.txt with `nums[f[0]] = int(f[1])`, so a duplicated key collapsed and the last line won -- exactly as read_ledger did, one file over. Measured on one fixture read both ways: the line form names guard_tests as duplicated the dict form sees two keys and keeps 280, the LAST line NOT HYPOTHETICAL. Three PRs were open at once, each moving guard_tests, and resolving all three keep-both produced three of those lines. ci.yml reads the value with `awk '$1=="guard_tests"{print $2}'`, which prints one line per match. So WANT becomes multi-line, `test -n "$WANT"` still passes, and the flag refuses it: pytest: error: argument --pgc-expect-tests: invalid int value: '284\n280\n283' exit 4 It FAILS CLOSED, so these two arms are about legibility rather than a hole. What exit 4 does not say is that a line is duplicated. The removal proof is on the real file: with a duplicate planted, the new arm reddens and the pre-existing one stays green. Keep-both is right for a changelog and wrong for a key-value file, and nothing in the tree said so. cluster_tests 205 -> 207, NOT guard_tests: test_harness_deps.py DEFINES NO_CLUSTER and is not in it. I bumped the wrong number first and --pgc-expect-tests caught it -- `collected 283 test(s) but expected 285` -- which is the argument for the mechanism. Both halves re-derived by collection: guard 283, cluster 207. REBASED onto 14c9dd4 after commandprompt#1029 merged, and the conflict was the exact shape the third arm here is about. `expected_tests.txt` conflicted on `guard_tests`: guard_tests 280 <- main, after commandprompt#1029 guard_tests 283 <- this branch, from the old base Keep-both would have produced two lines and, through ci.yml's `awk '$1=="guard_tests"{print $2}'`, a multi-line value the flag refuses at exit 4. One line, and both halves re-derived by collection rather than by arithmetic: guard_tests 286, cluster_tests 207 Re-verified: 286 passed / 707 checks with --pgc-expect-tests 286 armed; docs_style.sh PASSED (11 checks); the gate against the committed ledger and budget over the real 934-record log still returns 0. Long-sentence counts unchanged against the new base: CHANGELOG 860, TESTS.md 319. --- FIXTURE CORRECTED AFTER REVIEW (@jdatcmd) ----------------------------------- test_the_gate_refuses_two_checks_sharing_one_ledger_key asserted rc == 1 on a fixture where the gate returned 1 for TWO independent reasons, so the arm did not discriminate. Found by mutation, and I reproduced it before changing anything: with `rc = 1` deleted from the shared-key block, the OLD fixture's arm PASSED with the same mutation, the corrected fixture's arm FAILS: got 0 want 1 The second route was the pre-existing new-check refusal. The ledger named only `some other check` in that part, which made `demo` a covered suite whose log carried a check the ledger had never seen: not in the ledger: demo part1 shared name (on major 18) The ledger now NAMES `shared name`, so that refusal has nothing to say and only the shared-key refusal can move rc. This is the same defect class the PR is about, in my own arm: an assertion that passes for a reason other than the one it is named for. My docstring claimed I had measured this and closed it -- I had closed the COVERAGE-CEILING route and not the new-check route, and the note said "routes" as though it were all of them. The comment now enumerates all three, including the unhandled-exception route that also exits 1. It also described the refusal as "restricted to suites a row exists for", which was the design I abandoned two pushes earlier when the matrix measurement said refusing everywhere was safe. That sentence is gone. --- REBASED onto 2ea7366 ------------------------------------------------------ guard_tests 293 and cluster_tests 207, both re-derived by COLLECTION on this base. And a sharper point than the one I had, measured by @jdatcmd on commandprompt#1022: two branches each derived `guard_tests 280` correctly, each against a tree holding only its own three arms, and the merged value was 283. Both numbers were right for the tree they were taken on and both were wrong for the merge. So "derive rather than add" is necessary and not sufficient -- it has to be re-derived after every rebase, which expected_tests.txt now says. Re-verified: 293 passed / 725 checks with --pgc-expect-tests 293 armed; docs_style.sh PASSED (11 checks); the gate against the committed ledger and budget over the real 934-record log returns 0; long-sentence counts unchanged at CHANGELOG 860 and TESTS.md 319. REBASED again onto 1d76ccf after commandprompt#1022 merged. Two hunks in expected_tests.txt, and the second one is worth a note: my side carried the new `cluster_tests 207` comment PLUS two stale fragments left by earlier keep-both resolutions of the same file ("177 -> 180", "166 -> 182"), describing values the file no longer holds. Resolved to ONE coherent block rather than the union, which is the same lesson the arms here are about: keep-both is right for a changelog and wrong for a file where each key appears once. guard_tests 296 and cluster_tests 207, both re-derived by collection on this base. Re-verified: 296 passed / 733 checks armed; docs_style.sh PASSED; the gate over the real 934-record log returns 0.
9be6a49 to
4c0cb50
Compare
|
Amended: my CHANGELOG resolution ate the blank line between the two entries. Force-pushed with it restored; nothing else changed. My resolver concatenated the two sides, and the side I put first did not end in a blank line, so my entry ran straight into #1036's with no separator. Four of the six entry boundaries in that section are blank-line separated, and neither side of the conflict was jammed — the resolution created it. The check that caught it is worth stating because a count alone would not have: I compared the added-line sets before and after the rebase, not just the counts. Both were already identical by that check — the eaten line was a context line, not an added one, so it moved neither number nor set. The seam needed reading directly, with Re-verified after the amend: acting as: OffgridwithJD |
jdatcmd
left a comment
There was a problem hiding this comment.
APPROVE at 4c0cb50. 14/14 checks, and the fixture fix does what it had to. Re-verified by mutation rather than by reading the diff.
The blocking finding is closed
My ask was that the ledger NAME shared name, so the new-check refusal has nothing to say and only the shared-key refusal can move rc. It is in, and it works:
control, unmutated 28 passed, 152 checks
rc = 1 deleted from the shared-key block
test_the_gate_refuses_two_checks_sharing_one_ledger_key FAILED
test_a_shared_key_is_refused_in_a_suite_the_ledger_does_not_cover FAILED
restored (md5 back to 0f9ada80) 28 passed
That first arm is the one that survived this mutation before. It now fails, which is the whole difference between an arm about this refusal and an arm about the gate returning 1.
You documented a third route I had not found
I named two ways the fixture reaches rc=1 without the new refusal. The note names three, and the one I missed is the best of them:
3. AN UNHANDLED EXCEPTION, which also exits 1 -- an earlier draft referenced
covered_suitesbefore it was defined, which compiles and fails at runtime. Hence theTracebackarm below.
A crash and a refusal are indistinguishable in rc alone, and expect.num(int("Traceback" in out), 0, "premise: rc came from the refusal, not from a crash") is the premise that separates them. That is a better guard than the one I asked for, and it generalises past this file — any arm asserting a non-zero exit status is asserting "something went wrong", not "this went wrong", until it excludes the crash.
The count
Re-derived here rather than read: expected_tests.txt says guard_tests 304, collection on this tree says 304 tests collected, and the run passes with --pgc-expect-tests 304 (755 checks, 755 pass + 0 fail + 0 unrun). The branch contains 73e8e3d, so it is on current main.
It will still need re-deriving at merge, and not by arithmetic. #1038 also lands guard_tests 304 from the same base, and the two sets of arms are disjoint: composed in a worktree, the merged tree collects 307, and with #1037 as well, 313. Same trap as the 280/280/283 case this PR's own history section records.
One note, not blocking
test_each_expected_count_is_stated_exactly_once is the arm that would have caught that class four hours before it bit us, and it is the most reusable thing here. Worth saying in TESTS.md that keep-both is not merely the wrong conflict resolution for this file but wrong in a way that survives re-derivation by addition — both sides can derive correctly against a tree holding only their own arms and both still be wrong for the merge.
🤖 Generated with Claude Code
…ection #1035 merged as 4c0d9b1, so `main` moved and this branch's `guard_tests` was stale. Only `expected_tests.txt` conflicted; both narratives are kept and the number is RE-DERIVED BY COLLECTION on the merged tree: this branch alone 304 #1035 alone 304 base 298 MERGED 310 774 checks, 774 pass + 0 fail + 0 unrun Addition happens to agree here because the two sets of arms are disjoint. That is luck rather than a method: you cannot know the sets are disjoint without collecting, which is the whole reason the file says to collect. Also records the FOURTH way this month's census moved for a reason that was not the tree, found by @OffgridwithJD: `git grep` pathspecs are wildmatch WITHOUT FNM_PATHNAME, so `*` crosses `/` and the obvious spelling is silently recursive. git grep -e check_unrunnable REV -- 'test/*.sh' 9 files, 4 selftest git grep -e check_unrunnable REV -- ':(glob)test/*.sh' 5 files, 0 selftest That is the instrument anyone reaches for to measure the number at an older revision, and the top-level spelling LOOKS right there. TESTS.md now says to use `:(glob)`. TESTS.md also now gives the second reason test/selftest/ is out of scope: it is the SHELL harness's own self-test, and the two harnesses stay independent, so counting it into a claim about what the pytest parity tool grades would cross that line even if the tool could read it. docs_style.sh 14 checks PASSED. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NhwXKAgSmYDUjteWkfajHK
#1037 merged as 12ae053, so `main` moved again. Only expected_tests.txt conflicted; both narratives are kept and the number is RE-DERIVED BY COLLECTION: this branch before the merge 310 #1037 alone on 4c0d9b1 307 #1038 + #1037 on 73e8e3d 307 (a DIFFERENT tree, same total) MERGED, this tree 313 777 checks, 777 pass + 0 fail + 0 unrun The two 307s are the reason this file says collect rather than reconcile. They are equal for unrelated reasons -- #1035 and #1038 each add six arms -- and they measure different trees. Reading their agreement as confirmation would confirm nothing. `pgc_vacuity.py` AUTO-MERGED and kept both changes, which was the predicted and wanted outcome. Verified on the merged tree rather than argued from the diff: rows(g, w, NAME, "the reason") TypeError <- #1038, the POSITION rows(g, w, NAME, allow_empty=True) VacuityError <- #1037, the VALUE rows(g, w, NAME, allow_empty="why") accepted Neither change makes the other redundant: one closes the slot the name sits in, the other closes what may be put in the slot after it. docs_style.sh 14 checks PASSED. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NhwXKAgSmYDUjteWkfajHK
Closes the half of #982 that was left: the instance was fixed by
c3b13aedthe day theissue was filed, and the issue said the mechanism was the more valuable half. This is
that.
What was wrong
A ledger row is keyed on
(suite, part, name), so two checks with the same name in one partshare a row. Nothing is mis-recorded while both pass. The hazard is exact: when one goes red
the row records
ever red, and its namesake inherits a red observation nothing attacked.checks_never_observed_redthen falls by one for a check nobody attacked — and that censusis what #918 and #925 exist to make trustworthy.
pgc_ledger.py mergehas printed this since the issue was filed and returns 0. That is howthree of them sat in one part of
selftest/400for a day.The gate could not see it at all, by construction
A set collapses the duplicate before any arm can count it. The same canonicalisation
that makes the rest of
cmd_gatecorrect made this one class unreachable, which is why anote was the only thing available. The count now comes from the raw records through
_by_run.Worth naming as a shape: a guard is blind to whatever its inputs canonicalise away. Asking
what a comparison throws out before comparing is how this one was found.
Per log, not per invocation
One check observed in two logs is two runs of it — the normal case, and how the ledger
accumulates evidence at all. Only a repeat inside ONE log is a collision.
_by_runalreadyexisted for exactly this distinction and its docstring says so. An arm pins it, because
written over the logs together the refusal would reject every multi-day merge.
Every suite, deliberately unlike the new-check refusal
test_the_gate_refuses_a_new_check_only_in_a_suite_it_coversis restricted because it cannotknow which of an uncovered suite's checks are new. This one needs no history: two
records, one key, one log is decidable from the log alone.
That difference is the whole value. The ledger covers four suites of 253, so copying the
restriction would close the class in four places and leave the next collision to sit in one
of the other 249 until that suite was seeded. An arm asserts the refusal fires in an
uncovered suite, so the restriction cannot be reintroduced by habit.
Measured before widening it
A gate that reddens 250 unmeasured suites is a gate somebody turns off, so I did not widen it
on reasoning. I built a refuse-everywhere version and ran the full PG 18 matrix with it:
Check names are static, so one major's matrix measures this class completely rather than
sampling it. A mid-run snapshot of the per-suite logs agrees from the other direction: 244
logs carrying records, 6651 RESULT records, 6651 distinct keys, 0 collisions.
This also answers something #982 said nobody had measured — the gap between "checks that
ran" and "rows the ledger can hold" across the other 250 suites. It is currently zero.
Arms added
test_the_gate_refuses_two_checks_sharing_one_ledger_keytest_a_shared_key_is_refused_in_a_suite_the_ledger_does_not_covertest_the_same_check_in_two_runs_is_not_a_shared_keytest_a_clean_run_is_not_refused_for_a_shared_keyOne fixture detail worth reading, because it nearly produced a test that passed for the
wrong reason. My first version asserted
rc == 1with a budget ofsuites_not_covered 0,and that passed before the refusal existed — the gate already returned 1 for
suites_not_covered: 1 exceeds the ceiling of 0. Measured, then fixed by tolerating theuncovered count so the refusal is the only thing that can move rc. The arm also asserts no
Tracebackin the output, because an unhandled exception exits 1 as well; anUnboundLocalErrorin an earlier draft of mine did exactly that.Verification
Merge note
No new shell check, so no
check_ledger.tsvrow and no regeneration tax — the refusal istool behaviour, and the arms live in the pytest corpus.
expected_tests.txtmoves one line,which #1028 and #1029 also move; whichever lands last re-derives by collection.
TESTS.mdgains four subsections in section 23, away from the section-numbering region those two touch.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Uf6UoeBRZYLQZa4KxNiw8a
Closes #982.