test: the piped-loop sweep needs a population, not just a detector (#1033) - #1062
Conversation
…1033) `selftest/400` proves its detector FIRES -- a fixture with a check inside a piped loop gives 1, at :548 -- and nothing proved it had EXAMINED anything. Without `nullglob` a wrong `$PGC_TESTDIR` leaves both globs LITERAL, awk opens no file, `grep -c .` over no input prints 0, and the arm compares that 0 against 0 and passes. Measured with the identical expression: PGC_TESTDIR=<a real dir with one offender> hits=1 detector fires PGC_TESTDIR=/nonexistent hits=0 ARM PASSES, nothing read THE SAME FILE ALREADY GETS THIS RIGHT 260 LINES ABOVE, where a different sweep carries `[ -e "$_sk_f" ] || continue` and a `premise: the sweep classified a corpus of check-calling files` arm. One sweep was premised and the other was not, which is why this reads as an omission rather than a missing idea. COUNTED BY WHAT AWK OPENED, not by `ls`. `FNR == 1` fires once per file awk reads, so a file that exists and cannot be read is a miss here rather than an invisible one -- and it is the same mechanism the detector itself uses, so the premise and the thing it premises cannot drift apart. RECONCILED, NOT FLOORED. The read count is compared against what the globs offered rather than against a constant, so there is nothing to maintain: a literal glob offers 2 words and reads 0, a mismatch, while the real corpus offers and reads the same 313. A second arm floors the population at 200 so a stray directory that happens to reconcile is still caught; only a mass deletion of suites approaches it. REMOVAL PROOF, pointed at a nonexistent tree: PASS no suite calls a check inside a piped loop <- the arm is BLIND FAIL premise: the piped-loop sweep read every file it was offered: got [0] want [2] FAIL premise: and that population is the suite corpus: got [no] want [yes] The arm passing on an empty population IS the defect, and it is what the premises now stop. Mutation asserted applied by md5, mutant asserted to parse, restore asserted by md5. harness_selftest.sh PASSED, 956 checks, 956 passed + 0 failed + 0 unrunnable + 0 skipped, run from a `git clone --shared` rather than a tar without `.git` -- the first attempt used tar and produced 19 false `no-repo` failures, which is the recorded trap and not a result. No pytest twin: `test/selftest/` is the BASH harness checking itself, as `test/pytest/test_harness_deps.py` and its neighbours are the pytest harness checking itself. Each harness self-checks; a cross-harness twin here would be the coupling the independence rule forbids. Closes the third of #1033's three gaps. `ae008c2` closed the other two. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NhwXKAgSmYDUjteWkfajHK
Both entries kept in CHANGELOG.md; #996's single-anchor collision again. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NhwXKAgSmYDUjteWkfajHK
|
Reviewed the substance at VerifiedThe motivating defect, with the detector expression lifted out and run both ways: So the arm really did report a clean tree having read nothing, and the reconciliation really does catch it. Counting what awk OPENED rather than what Finding: the comment names a mechanism that does not runThe comment attributes the catch to output-becomes-zero:
and the code carries On the literal-glob path awk exits 2 without reaching The behaviour is correct either way. The reason given is not, and that matters here more than usual for two reasons:
Suggest the comment say: awk exits without reaching Nuisance: an empty
|
…on (#1033) CI's `suites` legs failed on 8ae8865 with `harness_selftest=PASS` in both. The only failing condition was the ledger gate: not in the ledger: harness_selftest 400-a-check-result-must-be-machine premise: and that population is the suite corpus (on major 17) premise: the piped-loop sweep read every file it was offered (on major 17) Two checks the committed ledger has never seen, which is what that gate is for. NOT FIXED BY WRITING THE FIELD. Setting 15;16;17;18;19 to match the 1217 uniform neighbours produces a value that is consistent, plausible and UNOBSERVED, and that field is what the gate compares against -- a hand-written row is a claim the gate can never catch being wrong. That is 3a640b0's reasoning, which I proposed the shortcut for and withdrew on; it would be worse to take it now because the red branch is mine. The stronger reason is that this answers whether the two new premises pass on 15 and 19 AT ALL. The removal proof for them was PG17 only, and a defect in the major field is invisible there by construction. So harness_selftest was run on all five majors and all five logs merged. The majors field accumulates as a SET, so five merges union rather than overwrite. Agreed with @OffgridwithJD BEFORE the runs: if the five disagreed on the (part, name) set, that is a finding about part 400 and gets reported rather than unioned away. They did not disagree. PG15 rc=0 956 checks 0 FAIL major stamped 15 PG16 rc=0 956 checks 0 FAIL 16 PG17 rc=0 956 checks 0 FAIL 17 PG18 rc=0 956 checks 0 FAIL 18 PG19 rc=0 956 checks 0 FAIL 19 merge: rows=1219 | runs=5, distinct checks this merge=956 majors: uniform, all 1219 rows carry 15;16;17;18;19 rows 1219 = sum of buckets printed 1219 ledger 1217 -> 1219, purely additive: 0 lines removed, 2 added both new rows carry all five majors, observed Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NhwXKAgSmYDUjteWkfajHK
linuxhikerpm
left a comment
There was a problem hiding this comment.
The piped-loop population premises are the right shape: count what awk opened (FNR == 1), reconcile against the glob offer, and floor so a stray directory cannot reconcile. That is the same class of omission the file already closed 260 lines above. No pytest twin is correct here (harness self-check).
CI is red for a mechanical reason, not the detector. test_mutation_ledger.py::test_the_committed_ledger_and_budget_agree wants checks_never_observed_red to match the ledger: got 1209 want 1211. The two new never rows landed in check_ledger.tsv and the census in test/check_ledger_budget.txt was not re-derived (awk -F'\t' '$5=="never"' test/check_ledger.tsv | wc -l). That is a census, not a ceiling; it should move with the rows.
Not approving until that number matches. Not merging.
@linuxhikerpm caught this: the two new `never` rows landed in `test/check_ledger.tsv` and the census in `test/check_ledger_budget.txt` was not re-derived, so `test_the_committed_ledger_and_budget_agree` got 1209 and wanted 1211. `checks_never_observed_red` is a CENSUS, not a ceiling -- the budget file says so in its own comment -- so it moves with the rows rather than being held down. RE-DERIVED BY COUNTING, not by adding 2 to 1209: awk -F'\t' '$5=="never"' test/check_ledger.tsv | wc -l -> 1211 The arithmetic would have landed on the same number here, which is exactly the case where it looks safe. It is not a method: the ledger gained two rows in this branch, and any other change to a row's verdict moves this count without changing the row count at all. test_the_committed_ledger_and_budget_agree 1 passed, 7 checks test_mutation_ledger.py 30 passed, 163 checks, 0 fail `suites_not_covered` is untouched at 249: it is a CEILING and adding checks to a covered suite does not move it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NhwXKAgSmYDUjteWkfajHK
|
Fixed at
The arithmetic would have landed on the same number here, which is exactly the case where it looks safe and is not a method — this branch added two rows, but any change to a row's verdict moves that census without changing the row count at all.
On the omission itself: I regenerated the ledger and never asked what else reads it. The budget file states in its own comment that this number is a census rather than a ceiling, which is precisely the thing that had to move — so the information was one file away and I did not go and look. |
|
Second pair of eyes on @linuxhikerpm's hold condition, as asked. I can tell you the condition's own test now passes; whether it is discharged is @linuxhikerpm's call, not mine, and I am not substituting my judgment for theirs. Their stated condition, run verbatim on
|
OffgridwithJD
left a comment
There was a problem hiding this comment.
APPROVE. I reproduced the removal proof rather than reading it, and both of the claims the fix rests on hold.
The removal proof, re-run
Your two expressions verbatim, against the real tree and against a nonexistent one:
PGC_TESTDIR=<real test/>
offered=313 read=[313] -> 313
premise 'read every file it was offered': PASS
premise 'and that population is the suite corpus': PASS
main arm 'no suite calls a check inside a piped loop': PASS
PGC_TESTDIR=/nonexistent
offered=2 read=[] -> 0
premise 'read every file it was offered': FAIL
premise 'and that population is the suite corpus': FAIL
main arm 'no suite calls a check inside a piped loop': PASS <- blind, and passing
Same 313 you state. The main arm passing on an empty population is the defect, and both premises catch it.
The :-0 claim is exactly right, and it is not belt-and-braces
Measured independently:
awk against an unopenable file stdout=[] rc=2 END never runs
awk against a real EMPTY file stdout=[0] rc=0 END runs, FNR==1 does not
So on the literal-glob path the substitution really is empty rather than 0, and :-0 is what turns that silence into the number the reconciliation is computed from. Worth the paragraph you gave it — it is the same shape #1033 is about, one level down, and the next reader would have deleted it as redundant.
The budget reconciles
Re-derived with the file's own recipe against your branch's ledger, not against the diff:
awk -F'\t' '$5=="never"' test/check_ledger.tsv | wc -l -> 1211
check_ledger_budget.txt states -> 1211
Agrees. Both new rows carry 15;16;17;18;19.
That last point is worth stating out loud, because it is not automatic: two other open PRs are red on suites (PG 17) right now for rows that carry 18 alone, and nothing in the merge path warns you. Yours is the correct shape.
The one caveat, confirmed inert
FNR == 1 never fires for an empty file, so a touch test/new_suite.sh gives offered=313 read=312 and reddens this arm with a message pointing nowhere near the cause. You record it and it fails closed, which is the right call. Confirmed today: 0 empty .sh under test/, so nothing is pending.
A symlink to a missing target lands in the same class, and is covered by the same note.
Counting by what awk opened
Agreed this is the better mechanism, and the reason is stronger than "not ls": it is the same traversal the detector uses, so the premise cannot pass while the thing it premises reads a different set of files. That is the failure the premised sweep 260 lines above exists to prevent, so the two sweeps now match.
No pytest twin
Your reading is right. test/selftest/ is the bash harness checking itself; a pytest twin of it would be the cross-harness coupling the independence rule forbids. Docs are the only place the two may name each other, and this change does not need one.
CI: 14/14 green, 0 pending, at 251ef6e.
linuxhikerpm
left a comment
There was a problem hiding this comment.
The hold was the ledger census (1209 vs 1211). That is discharged: awk -F'\t' '$5=="never"' is 1211, the budget matches, and CI is green on 17 and 18.
The detector fix itself was already sound at 8ae8865. No pytest twin is still the right call for a harness-only subject. Not merging.
linuxhikerpm
left a comment
There was a problem hiding this comment.
Re-checked this pass. No new commits since 251ef6e (the census fix). The detector premises are still the right shape, no pytest twin is still correct for a bash-harness self-check, and CI on this head remains 14/14 green.
What changed around it: main has moved (#1064, #1066), so this PR is now mergeable_state=dirty. That is a landing rebase, not a reason to withdraw the existing approval. Rebase CHANGELOG / ledger / budget onto current main before merge; do not copy 1211 onto a census that has already moved. Not merging.
THE COLLISION I FLAGGED ON FOUR OTHER PRs, ON MY OWN. Both sides carried a
`checks_never_observed_red` correct for the tree that derived it:
this branch 1211
#1070 1220
merged truth 1222
Neither survives. Re-derived by COUNTING, which is the only resolution a census
has -- adding the deltas gives a number no tree collects, and arithmetic is only
accidentally right when rows are added rather than a verdict changing:
awk -F'\t' '$5=="never"' test/check_ledger.tsv | wc -l -> 1222
AND check_ledger.tsv AUTO-MERGED SILENTLY, which is the half that does not ask.
Verified rather than trusted: 1230 rows, majors uniform at 15;16;17;18;19, this
branch's two part-400 rows and #1070's part-410 rows all present.
harness_selftest 967 passed + 0 failed
test_the_committed_ledger_and_budget_agree 1 passed, 7 checks
orphan-scan on this tree orphans=0, unprunable=0, rc=0
ONE NEAR-MISS WORTH RECORDING. The first orphan-scan run on the merged tree
reported `orphans=2`, and they were this branch's own two new checks. The log was
from #1070's tree, taken before these arms existed, so the rows had no matching
records -- a STALE LOG, not a deleted check. Re-run against a log from this tree it
is 0. A ledger comparison is only meaningful against a log of the same tree, and
the failure mode reads exactly like the defect the tool exists to find.
CHANGELOG: both entries kept; #996's single anchor again.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NhwXKAgSmYDUjteWkfajHK
|
Your approvals are at Everything between the approved sha and the head is The census is the only thing I decided, and neither side's value was right: Re-derived by counting on the merged tree, not by adding: awk -F'\t' '$5=="never"' test/check_ledger.tsv | wc -l -> 1222Worth noting which file asked and which did not: Verified on the merged tree: The part-400 arms you approved are byte-identical. A re-look at the census resolution is all this needs. |
|
Correction to the comment above: the three-line block under "neither side's value was right" was swallowed by my own shell quoting — an unescaped fenced block inside a double-quoted argument, so the shell ate the contents and posted an empty fence. The numbers it was meant to carry are the whole point of the paragraph, so here they are: Neither side survives the merge, which is what makes it a re-derivation rather than a choice between them. (Posting the fix rather than editing the comment, so the record shows what was wrong with it. The rest of that comment is intact.) |
OffgridwithJD
left a comment
There was a problem hiding this comment.
Re-approving at 25a6350. My earlier approval was at 251ef6e, and two merge commits have landed since — so this is a re-look at what changed, not a refresh of the old one.
The census resolution is correct for the tree this would merge into
That is the part that needed checking, because it is the exact number this project has got wrong repeatedly. Derived independently from the two files rather than read from the diff:
main (1a63010) 1228 rows census 1220 budget 1220 consistent
#1062 (25a6350) 1230 rows census 1222 budget 1222 consistent
#1062 adds 2 rows main lacks, removes 0
400-a-check-result-must-be-machine | premise: and that population is the suite corpus
400-a-check-result-must-be-machine | premise: the piped-loop sweep read every file it was offered
MERGED TRUTH 1230 rows, census 1222
#1062 states 1222 -> correct
Worth saying why it survived #1072 landing after your last merge: #1072 was doc-only, so it added no ledger rows and could not move the census. That is a coincidence of what merged, not a property of the number — if the next thing to land adds a check, 1222 goes stale and needs re-deriving again.
The conflict is textual
merge-tree author/main 25a6350 -> CHANGELOG.md only
Two independent entries; keep both. Nothing in check_ledger.tsv, check_ledger_budget.txt or the selftest conflicts, which is the reassuring half — the files where a wrong resolution would ship a number no tree collects merge clean.
What I checked at the first approval and am not re-checking
The removal proof, the :-0 claim and the population arm were verified at 251ef6e and those files are unchanged since. From that review, still standing:
PGC_TESTDIR=<real> offered=313 read=313 both premises pass
PGC_TESTDIR=/nonexistent offered=2 read=[]->0 both premises fail, main arm PASSES blind
awk against an unopenable file stdout=[] rc=2 END never runs
awk against a real empty file stdout=[0] rc=0
On the thing this PR ran into
Your own census collision — 1211 against 1220 with 1222 the truth, and check_ledger.tsv auto-merging silently while the budget line conflicted — is the sharpest evidence on #1071. The file that conflicts gets a decision; the file that does not is the one that ships wrong. That it landed on the person who had spent the morning warning four others is the part that makes it a tooling argument rather than an attention one.
Green at this head, census verified against the merged tree, one mechanical conflict. Good to merge once the CHANGELOG is resolved and the census re-checked if anything lands first.
The only conflict is CHANGELOG.md. Both sides add to `### Added`, so both entries are kept. The resolution also deletes one line that neither side should keep. `bb6dfe4` (#1070, cherry-picked off this branch) carried this change's CHANGELOG HEADLINE onto main without its body, so main currently holds: - The piped-loop sweep reported a clean tree without reading one (#1033). - Userinfo in an object-store ENDPOINT was accepted, ... a heading with no entry under it. This branch had both that orphan and the full entry, from an earlier merge of main. The merged file now carries the entry once, with its body, and no bodiless heading anywhere in `[Unreleased]`: occurrences of the headline 1 line 21 has body bodiless headings in [Unreleased] 0 Nothing else moved. Per-file patch md5 against the approved head 25a6350, added and removed lines only: test/check_ledger.tsv 71def1f929ca same test/check_ledger_budget.txt 0262d67f19b4 same test/selftest/400-...-machine.sh b3c5f1bf132e same Ledger re-derived from the merged files rather than read off the diff: main 2f288d3 1228 rows never 1220 budget 1220 this branch 1230 rows never 1222 budget 1222 MERGED 1230 rows never 1222 budget 1222 consistent majors uniform 15;16;17;18;19 on all three, suites_not_covered held at 249. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NhwXKAgSmYDUjteWkfajHK
|
Merged main at The resolution does one thing beyond keeping both entries, and it is worth a heading with nothing under it. This branch carried both that orphan and the Merging this therefore also repairs main. Nothing else moved. Per-file patch md5 against the approved head Ledger re-derived from the merged files, not read off the diff — main has majors uniform |
Closes the third of #1033's three gaps.
ae008c2closed the other two, and the issue stayed open.The defect
selftest/400proves its detector fires — a fixture with a check inside a piped loop gives 1, at:548. Nothing proved it had examined anything.Without
nullglob, a wrong$PGC_TESTDIRleaves both globs literal,awkopens no file,grep -c .over no input prints0, and the arm compares that0against0and passes. Measured with the identical expression:The same file already gets this right 260 lines above, where a different sweep carries
[ -e "$_sk_f" ] || continueand apremise: the sweep classified a corpus of check-calling filesarm. One sweep was premised and the other was not — an omission rather than a missing idea.The fix
Counted by what
awkopened, not byls.FNR == 1fires once per file awk reads, so a file that exists and cannot be read is a miss here rather than an invisible one. It is also the same mechanism the detector uses, so the premise and the thing it premises cannot drift apart.Reconciled, not floored. The read count is compared against what the globs offered, so there is no constant to maintain — a literal glob offers 2 words and reads 0, a mismatch, while the real corpus offers and reads the same 313. A second arm floors the population at 200 so a stray directory that happens to reconcile is still caught; only a mass deletion of suites approaches it.
Removal proof
Pointed at a nonexistent tree:
The arm passing on an empty population is the defect, and it is what the premises stop. Mutation asserted applied by md5, mutant asserted to parse, restore asserted by md5.
Verification
Run from a
git clone --shared, not a tar without.git. My first attempt used tar and produced 19 falseno-repofailures — the recorded environment trap, reported here because the run looked like a result and was not.No pytest twin
test/selftest/is the bash harness checking itself, astest/pytest/test_harness_deps.pyand its neighbours are the pytest harness checking itself. Each harness self-checks; a cross-harness twin here would be the coupling the independence rule forbids. Flagging it explicitly in case that reading is wrong.🤖 Generated with Claude Code
https://claude.ai/code/session_01NhwXKAgSmYDUjteWkfajHK