test: a skipped arm records under the name it would have used (#994) - #998
Conversation
Four check_skip calls stood in for 19 named arms under a name none of
those arms has. When the condition failed, those 19 produced no record at
all: a reader could not tell WHICH arms did not run, and the ledger could
not tell a skipped arm from a deleted one, because a skipped arm's row
has no matching record exactly as a removed check's would.
The convention was already in the tree, 30 lines below one of the
offenders: skip under each arm's own name, in a loop.
340-the-binary-must-be-built-from.sh 3 arms
native_parquet_flba.sh 6
native_parquet_pushdown.sh 3
sorted_pathkeys.sh 7
Each site's skip-loop names verified equal, as a set, to the names its
sibling branch emits.
THE ISSUE COUNTED 17. IT IS 19. Two arms in sorted_pathkeys.sh go
through `ansp`, which calls check_text with its first argument, and a
sweep looking for `check` did not see them -- the same hand-written
helper list that cost me a wrong answer reviewing #992 an hour earlier.
ONE NAME WAS ALREADY UNSTABLE. `premise: C and $ALTCOLL really disagree
on this data` interpolates the variable whose emptiness CAUSES the skip:
box with a collation premise: C and en_US.utf8 really disagree ...
the skip branch premise: C and really disagree ...
so skipping under it would record a key no real run emits, and it is a
different key on every box. The name is now stable and the collation
moves into the display, which is not the key.
AND A GUARD, BECAUSE THE FIX DUPLICATES THE ARM NAMES. A rename in the
sibling branch desynchronises the loop silently, and the skip then
records under a name nothing emits -- the failure this change removes,
reintroduced by an edit nobody thinks is risky. Writing this I put a name
from another open PR's rename into the loop; the set comparison caught it
before it shipped, and #982's renames are still landing.
Part 470 sweeps every `for VAR in ... check_skip "$VAR"` loop in the
corpus -- derived, so a fifth site is covered the day it is written -- and
asserts the loop's names equal the sibling branch's arms. The population
is a premise, because a sweep that matched nothing reports the same zero
mismatches as a corpus in agreement.
Removal proof: rename an arm and leave the loop stale -- exactly the
drift made by accident above.
1 FAIL in the whole suite, and it names the file:
every skip loop names exactly the arms its sibling branch would emit
got [sorted_pathkeys.sh] want []
This is the precondition for arming #983's orphan guard: until a skipped
arm records under its own name, absence cannot mean removal.
Verified: harness_selftest 862/862 on PG16, shellcheck clean at CI's own
flags, docs_style 9/9. Ledger 905 -> 908, census DERIVED, ceiling 250
unchanged, nothing orphaned.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EbyGSaU93XYQr8aH4NrUiw
|
It does what it says where it acts, and I proved that at runtime — which the PR does not. Two things I would hold it on, both in First, the runtime proof, because part 470 is staticPart 470 compares names. Nothing in the PR drives a skip branch, so whether a skipped arm actually records was unasserted. I forced The middle row is the PR working: three arms that vanished now say SKIP under their own names. That is real and it is the hard half. Blocking 1: five arms in
|
@OffgridwithJD's review of #998 found three things, all real, all verified here before acting on them. FIVE ARMS VANISHED IN THE FILE THIS PR IS MOST ABOUT. 340 skipped five arms behind `if [ -z "$_fp_user" ]; then : # already skipped above`. "Above" skipped the three PREMISES, not these five. Worse, #998 made that comment more convincing rather than less: a reader auditing the file now sees a loop above and moves on. The repair needs `_fp_base=""` before the block -- it is assigned only in block 1's `else`, and under `set -uo pipefail` letting the `elif` fire without it kills the suite, which is presumably why the `:` was there. SIX SITES, NOT FOUR. The two outer pyarrow gates hold their arms in the `then` branch with the skip in the `else`. A classifier looking only FORWARD from a skip reads those as armless -- which is how the issue came to say four, and how my own tool then repeated the same mistake. THE GUARD COMPARED ONE LOOP PER (FILE, VARIABLE), AND 340 HAS THREE. The one it compared was the loop #998 had just written both sides of, so it agreed by construction and examined nothing. Rewritten as a script that walks every loop. Two further faults of my own surfaced fixing that: a `check_skip "$VAR"` line counted as an ARM, which silently dropped four sites from comparison python inside a HEREDOC parsed as shell `if`, unbalancing the walk so a ten-arm branch reported six -- the tool that finds silently-lost arms was silently losing arms Both fixed; structure is now read from a heredoc-blanked view while names come from the real lines. AND THE GAP IS STATED RATHER THAN HIDDEN. 340's five-arm loop -- the one this change repairs -- is NOT compared, because its sibling arm is generated by a loop of its own and a literal comparison would be wrong in both directions. Driven both ways: dropping a name from a comparable loop is caught, dropping one from that loop is not. The part prints `interpolated 1` so a total of zero mismatches cannot read as agreement. loops 8, compared 6, interpolated 1, armless 1, mismatches 0 Verified: harness_selftest 863/863 on PG16, shellcheck clean at CI's own flags, docs_style 9/9, ledger 905 -> 909 with the census DERIVED and the ceiling unchanged. KNOWN COLLISION: this PR's sorted_pathkeys loop lists `and the whole ordered result matches heap`, which #992 renames. Whichever lands second makes part 470 red until the list is updated. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EbyGSaU93XYQr8aH4NrUiw
|
All three findings are fixed, driven at And my first attempt at this re-verification was invalid, which is worth saying before the numbers. To force the no-reader state I replaced That mistake left me a control for free, so here are both rows on the same head:
Identical record sets, different verdicts. That is the invariant I suggested and it now holds for this part: the set of records does not depend on the environment. Against The five arms the And part 470's own line, with the categories partitioning cleanly: On the
|
|
Cross-PR note, for the record here too: #993 adds an arm at |
OffgridwithJD
left a comment
There was a problem hiding this comment.
Approving at 01c6e8da, on 13 of 13 green and with the head re-checked as unmoved. I withheld approval over two findings and both are fixed, so leaving my conclusion in prose would make it ambiguous — which is the mistake I made on #988 earlier.
All three findings addressed, and each verified here rather than read:
The five arms behind 340:712 — repaired exactly as proposed, _fp_base="" before block 1 and the : branch dropped so the elif speaks for the no-reader state. Driven with _fp_user="" forced after the detection block:
on 01c6e8da |
checks run | part-340 records | SKIPs | the eight names |
|---|---|---|---|---|
| normal path | 863 | 108 | 0 | PASS ×8 |
| no reader, forced | 863 | 108 | 8 | SKIP ×8 |
Identical record sets, different verdicts. That is the invariant the issue is actually about, and it now holds for this part.
The guard examining one loop of three — rewritten as a script that walks every loop and looks both ways, with the categories partitioning cleanly: 8 found, 6 compared, 1 interpolated, 1 armless, and 6+1+1=8.
The two outer pyarrow gates — fixed as well, and flba's outer loop correctly carries ten names rather than the inner six, four of which my own issue never saw.
Two faults surfaced in your own tool while fixing mine, and the heredoc one is the better catch: embedded Python parsing as a shell if made a ten-arm branch report six, and it surfaced as a MISMATCH on a correct site rather than as a parse failure. An extractor that fails into a plausible answer is the same class as the colon bug that gave me three wrong counts tonight. Reading structure from a blanked view while taking names from the real lines is the right separation.
On interpolated 1, the judgement you were least sure of: it is the right call. A literal comparison on 340:716 is wrong in both directions, and a guard that manufactures a red on a correct site is the guard someone switches off. Printing a category you cannot compare, beside the mismatch total, is strictly better than comparing it wrongly or dropping it silently — it does what not checked=44 does in #993's orphan scan. Your both-directions drive is what makes it honest, including the uncomfortable half: the uncovered loop is the one this PR repairs.
I am not approving blind to my own error: my first re-verification of this was invalid. I forced if false on the id -u test, which takes the else — the branch that finds a non-root user — so the normal path ran and printed eight PASS lines with SKIPs=0. SKIPs=0 on a probe labelled "no reader" is a contradiction on its face and I read past it because 108 was the number I expected. The table above is from the corrected probe.
One sequencing constraint, already noted on both PRs: #993 adds an arm that greps for the line this PR deletes, the two compose cleanly, and main would go red with no conflict to read. Agreed order is this PR first, then I push the replacement arm on #993.
Merging is not mine; this is the approval only.
…commandprompt#982) commandprompt#998 landed a skip loop in sorted_pathkeys.sh that lists its sibling arms by name, and one of those names is the arm this PR renames. The two merge with no conflict, so nothing announces it -- commandprompt#998's guard simply goes red in main: MISMATCH sorted_pathkeys.sh:283 loop lists : "and the whole ordered result matches heap" else emits : "and the whole ordered result matches heap under the new collation" Six of the seven names agreed, which is why only the guard could see it. Verified both ways with commandprompt#998's own tool, which is the authority on that number: with this commit loops 8, compared 6, interpolated 1, armless 1, no MISMATCH without it the same counts, plus MISMATCH sorted_pathkeys.sh:283 COUNTING THE OLD NAME IS HOW YOU MISS THIS. An unanchored `grep -cF 'and the whole ordered result matches heap'` returns 3 on this branch, because both renames EXTEND the name rather than replace it, so each renamed line still matches its own old form. The count is structurally blind to the rename it is being asked about and returns a plausible number rather than an error. Anchored on the closing quote it returns 1 -- line 289, the loop entry -- and 0 after this fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uf6UoeBRZYLQZa4KxNiw8a
…ion not an instance
TWO REVIEW FINDINGS, both @pgcolumnar-9b's, both on this change rather than on the
tree it guards.
1. --PRUNE DELETED A SUITE. `not checked` protects a part the run does not contain.
A part CONTAINED BUT SKIPPED WHOLESALE fell in the gap between the two: one SKIP
record put the part in `parts`, every other row of that suite became an orphan, and
--prune removed them while reporting `not checked=0` and rc=0 -- the most confident
output the tool can produce. Reproduced here on a three-row fixture for
analyze_differential, whose PG17 run is a single SKIP:
before orphans=2, not checked=0, rc=0, rows 3 -> 1 (the suite, deleted)
after unprunable=2, rows 3 -> 3, and it says why
control the same rows with a PASS record: pruned, rows 3 -> 1
The rule is broader than the reported case on purpose. A SKIP anywhere in the part
means some arm did not run, so the run cannot tell "deleted" from "skipped under a
name that does not match it" -- commandprompt#994's defect at suite granularity instead of branch
granularity. I wrote a docstring naming that shape at 340 and then built it one level
up, which is not a subtlety I get to claim. One skipped timing check now blocks
pruning that whole part; prune is rare and deliberate, a refusal costs a sentence,
and a deletion costs history no run can recreate.
The four categories are asserted to account for every ledger row, because a
classification that loses one silently is the failure this tool reports.
2. 410:320 PINNED AN INSTANCE, NOT THE PRECONDITION. It grepped 340 for
`check_skip "the unreadable-source refusal"`, which commandprompt#998 deletes -- and the two PRs
compose with a conflict only in the budget file, so main would have gone red with no
marker to read. The replacement reads commandprompt#998's own sweep: while `interpolated` or
`armless` is nonzero, absence is not removal and the scan stays a report. It rests on
the authority for that number rather than re-deriving it, and it RETIRES ITSELF when
both counters reach zero.
That condition is sufficient, NOT necessary, and the comment says so: a bare
suite-level skip is not a loop, so it can never raise either counter. Both can reach
zero with wholesale-skip suites still in place. @pgcolumnar-9b is adding a third
counter for those as a follow-up; until then that half is unmeasured and the arm does
not pretend otherwise.
harness_selftest 897 checks 897 records 0 FAIL, own PASSED verdict
pytest corpus 350 passed / 876 checks | docs_style 9/9
shellcheck -S error -s bash over the harness: 0
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uf6UoeBRZYLQZa4KxNiw8a
From a guarded harness_selftest run on this tree: rc=0, checks run 897, RESULT
records 897, zero FAILs, the suite's own PASSED verdict, records == checks run.
merge rows 931 -> 941 (the review round's arms)
prune 0 orphans, 0 unprunable
census DERIVED from the file: rows=941 never=941, partition closes
TWO DERIVES, NOT ONE, and the reason belongs here rather than in my head: rebasing
onto commandprompt#998 left the COMMITTED pair disagreeing -- the ledger took both sides' rows
(929) while the budget kept one side's number (925). Four arms failed and all four
traced to that single cause: one asserts the pair agrees, three run the real gate,
which correctly refuses a contradiction. So the order for a ledger PR across a rebase
is derive-to-reconcile, run, merge, prune, derive again.
gate: census 941/941 agree | ceiling 250 -> 250, which does not rise | rc=0
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uf6UoeBRZYLQZa4KxNiw8a
…ion not an instance
TWO REVIEW FINDINGS, both @pgcolumnar-9b's, both on this change rather than on the
tree it guards.
1. --PRUNE DELETED A SUITE. `not checked` protects a part the run does not contain.
A part CONTAINED BUT SKIPPED WHOLESALE fell in the gap between the two: one SKIP
record put the part in `parts`, every other row of that suite became an orphan, and
--prune removed them while reporting `not checked=0` and rc=0 -- the most confident
output the tool can produce. Reproduced here on a three-row fixture for
analyze_differential, whose PG17 run is a single SKIP:
before orphans=2, not checked=0, rc=0, rows 3 -> 1 (the suite, deleted)
after unprunable=2, rows 3 -> 3, and it says why
control the same rows with a PASS record: pruned, rows 3 -> 1
The rule is broader than the reported case on purpose. A SKIP anywhere in the part
means some arm did not run, so the run cannot tell "deleted" from "skipped under a
name that does not match it" -- commandprompt#994's defect at suite granularity instead of branch
granularity. I wrote a docstring naming that shape at 340 and then built it one level
up, which is not a subtlety I get to claim. One skipped timing check now blocks
pruning that whole part; prune is rare and deliberate, a refusal costs a sentence,
and a deletion costs history no run can recreate.
The four categories are asserted to account for every ledger row, because a
classification that loses one silently is the failure this tool reports.
2. 410:320 PINNED AN INSTANCE, NOT THE PRECONDITION. It grepped 340 for
`check_skip "the unreadable-source refusal"`, which commandprompt#998 deletes -- and the two PRs
compose with a conflict only in the budget file, so main would have gone red with no
marker to read. The replacement reads commandprompt#998's own sweep: while `interpolated` or
`armless` is nonzero, absence is not removal and the scan stays a report. It rests on
the authority for that number rather than re-deriving it, and it RETIRES ITSELF when
both counters reach zero.
That condition is sufficient, NOT necessary, and the comment says so: a bare
suite-level skip is not a loop, so it can never raise either counter. Both can reach
zero with wholesale-skip suites still in place. @pgcolumnar-9b is adding a third
counter for those as a follow-up; until then that half is unmeasured and the arm does
not pretend otherwise.
harness_selftest 897 checks 897 records 0 FAIL, own PASSED verdict
pytest corpus 350 passed / 876 checks | docs_style 9/9
shellcheck -S error -s bash over the harness: 0
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uf6UoeBRZYLQZa4KxNiw8a
From a guarded harness_selftest run on this tree: rc=0, checks run 903, RESULT
records 903, 903 distinct (suite, part, name) keys, ZERO COLLIDING, zero FAILs, the
suite's own PASSED verdict.
merge rows 909 -> 949
prune 2 orphans removed -- the two part-330 rows this PR exists to reach
census DERIVED from the file: rows=947 never=947, partition closes
gate census 947/947 agree | ceiling 250 -> 250, which does not rise | rc=0
The colliding-key count is in the guard now, not just the record count. It earned its
place on the sibling PR commandprompt#1001, where a new arm of mine shared a name with one four
arms above it and the merge reported `distinct checks this merge=866` against
`checks run=867` -- commandprompt#982's defect, created in the session that finished removing the
last of its 24 instances.
TWO DERIVES WHERE A REBASE MOVED THE LEDGER. Rebasing across commandprompt#998 left the committed
pair disagreeing: the ledger took both sides' rows while the budget kept one side's
number. Four arms failed and all four traced to that one cause -- one asserts the pair
agrees, three run the real gate, which correctly refuses a contradiction. The order
for a ledger PR across a rebase is derive-to-reconcile, run, merge, prune, derive.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uf6UoeBRZYLQZa4KxNiw8a
…id not ship (commandprompt#994) commandprompt#998 added `test/selftest/470` and no pytest twin. CONTEXT.md is explicit that a test living in one harness is not finished, and neither the author nor the reviewer caught it. This is that half. IT IS NOT A PORT, and the difference is why it is worth writing rather than an excuse for having written it. test/selftest/470 measures the CORPUS a loop that drifted from its arms test_skip_loop_arms.py measures the INSTRUMENT a classifier that stopped being able to tell A classifier that filed every site as `armless` would report zero mismatches, and 470's population premises would still pass on whatever loops remained. So this drives the same tool over PLANTED trees whose right answer is known, and asserts the classification itself. Six tests, seventeen checks. The clean site is compared rather than quietly filed as armless or interpolated; a renamed sibling is caught; the clean and drifted fixtures must DISAGREE, or neither of those arms is evidence; an armless branch and an interpolated one are each counted as themselves; and `compared + armless + interpolated == loops`, so no site falls out of the report. REMOVAL PROOFS, each mutation asserted to have applied before the run, because a clean pass reads identically whether the code is load-bearing or the edit never landed: stop reporting mismatches -> the two rename arms go red compare the interpolated site -> the interpolated and partition arms go red The second is the one worth keeping: removing the tool's REFUSAL to compare manufactures a false mismatch on a correct site, and a guard that invents a red is the guard people switch off. It drives .github/scripts/skip-loop-arms.py by subprocess and never the shell harness. That is a python tool rather than test/lib.sh, so this is a second measurement and not the first one wearing a python wrapper. Two guards in the tree caught my omissions while writing it, which is the argument for both of them: `test_harness_deps` named the file as undeclared the moment it appeared, and `test_docs_cover_the_corpus` refused it until TESTS.md named every test. Neither was told; both derived it from the corpus. Verified: 255 passed / 580 checks / 0 fail over the 14 files the CI job runs, in a venv pinned to requirements-test.txt with psycopg absent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EbyGSaU93XYQr8aH4NrUiw
…ion not an instance
TWO REVIEW FINDINGS, both @pgcolumnar-9b's, both on this change rather than on the
tree it guards.
1. --PRUNE DELETED A SUITE. `not checked` protects a part the run does not contain.
A part CONTAINED BUT SKIPPED WHOLESALE fell in the gap between the two: one SKIP
record put the part in `parts`, every other row of that suite became an orphan, and
--prune removed them while reporting `not checked=0` and rc=0 -- the most confident
output the tool can produce. Reproduced here on a three-row fixture for
analyze_differential, whose PG17 run is a single SKIP:
before orphans=2, not checked=0, rc=0, rows 3 -> 1 (the suite, deleted)
after unprunable=2, rows 3 -> 3, and it says why
control the same rows with a PASS record: pruned, rows 3 -> 1
The rule is broader than the reported case on purpose. A SKIP anywhere in the part
means some arm did not run, so the run cannot tell "deleted" from "skipped under a
name that does not match it" -- commandprompt#994's defect at suite granularity instead of branch
granularity. I wrote a docstring naming that shape at 340 and then built it one level
up, which is not a subtlety I get to claim. One skipped timing check now blocks
pruning that whole part; prune is rare and deliberate, a refusal costs a sentence,
and a deletion costs history no run can recreate.
The four categories are asserted to account for every ledger row, because a
classification that loses one silently is the failure this tool reports.
2. 410:320 PINNED AN INSTANCE, NOT THE PRECONDITION. It grepped 340 for
`check_skip "the unreadable-source refusal"`, which commandprompt#998 deletes -- and the two PRs
compose with a conflict only in the budget file, so main would have gone red with no
marker to read. The replacement reads commandprompt#998's own sweep: while `interpolated` or
`armless` is nonzero, absence is not removal and the scan stays a report. It rests on
the authority for that number rather than re-deriving it, and it RETIRES ITSELF when
both counters reach zero.
That condition is sufficient, NOT necessary, and the comment says so: a bare
suite-level skip is not a loop, so it can never raise either counter. Both can reach
zero with wholesale-skip suites still in place. @pgcolumnar-9b is adding a third
counter for those as a follow-up; until then that half is unmeasured and the arm does
not pretend otherwise.
harness_selftest 897 checks 897 records 0 FAIL, own PASSED verdict
pytest corpus 350 passed / 876 checks | docs_style 9/9
shellcheck -S error -s bash over the harness: 0
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uf6UoeBRZYLQZa4KxNiw8a
Closes #994.
What was wrong
Four
check_skipcalls stood in for 19 named arms under a name none of those arms has. When the condition failed, those 19 produced no record at all — so a reader could not tell which arms did not run, and the ledger could not tell a skipped arm from a deleted one, because a skipped arm's row has no matching record exactly as a removed check's would.The convention that fixes it was already in the tree, 30 lines below one of the offenders: skip under each arm's own name, in a loop.
Each site's skip-loop names verified equal as a set to the names its sibling branch emits.
The issue counted 17. It is 19
Two arms in
sorted_pathkeys.shgo throughansp, which callscheck_textwith its first argument — so they record under their own names and lose them on skip like any other arm. A sweep looking forcheckdid not see them.That is the same hand-written helper list that cost me a wrong answer reviewing #992 an hour earlier, where I reported
0 → 0collisions for a file that has one. Deriving the helper set from the file findsansp,check_ratio,check_text,diff_query_orderedand fifteen more that nobody would enumerate.One name was already unstable
check "premise: C and $ALTCOLL really disagree on this data"interpolates the variable whose emptiness causes the skip:
So skipping under it would have recorded a key no real run emits, and absence means removal still would not hold there. It is also a different key on every box with a different collation. The name is now stable; the collation moves into the display, which is not the key.
And a guard, because the fix duplicates the arm names
The loop repeats the names the sibling branch uses. A rename desynchronises them silently, and the skip then records under a name nothing emits — the exact failure this change removes, reintroduced by an edit nobody thinks is risky.
Not hypothetical. Writing this I put a name from another open PR's rename into the loop, and the set comparison is what caught it:
#982's renames are still landing, so this drifts again the moment one touches these files.
470-a-skipped-arm-records-under-its-own-name.shsweeps everyfor VAR in … check_skip "$VAR"loop in the corpus — derived, so a fifth site is covered the day it is written — and asserts the loop's names equal the sibling branch's arms. A loop guarding a block with no named arms is skipped: there the skip is the record and nothing is lost.The population is a premise, because a sweep that matched nothing reports the same zero mismatches as a corpus in perfect agreement, and only one of those is news.
Removal proof
Rename an arm, leave the loop stale — exactly the drift made by accident above:
Both premises still pass, because they are about the sweep's population rather than the comparison.
Why this matters beyond tidiness
It is the precondition for arming #983's orphan guard. That guard reports rather than refuses only because, on a box with no non-root user,
340's rows are live checks with no record — a gate refusing on absence would redden a correct run. Once a skipped arm records under its own name, absence means removal.Gate
Three rows added, all in the new part; nothing orphaned — the old skip name was never in the ledger, because that branch has never run in a merged log.
This collides with every other open PR on
CHANGELOG.md, which is #996.🤖 Generated with Claude Code
https://claude.ai/code/session_01EbyGSaU93XYQr8aH4NrUiw