test: a check that could not run is a third state, not a pass (#858) - #859
Conversation
Yesterday's audit proved 39 checks across 35 suites cannot fail. This is the first of four phases against the gap that let them ship: the harness answers "did anything print FAIL" and has never answered "could anything print FAIL". A check whose INPUT is absent -- a fixture that did not build, a capability the server lacks, an endpoint that is unreachable -- either passes vacuously or fails for a reason unrelated to the property under test. Neither answer is true, and `checks run: N` counts it either way, so a reader counting greens counts one that never asked its question. `check_unrunnable NAME REASON_CODE DETAIL` gives one check the honesty pgc_skip already gives a whole suite for a missing dependency. The suite then exits PGC_EXIT_INCOMPLETE, so an unrunnable check cannot hide inside a suite reporting PASSED; a failure still outranks it, because a failure is the more urgent fact. Three deliberate choices, each argued in review before it was written: The reason is a CLOSED ENUM plus a detail, from the first commit rather than after phase 3. Prose would mean rewriting every call site the day anything wants to group these, and a code outside the set FAILS rather than being accepted -- an enum that accepts anything is prose again. 67, not 66. 66 means "ran no checks". A suite holding one unrunnable check DID run checks, and collapsing the two loses the difference between "this suite is inert" and "this suite could not evaluate one thing". 67 is picked on the same grounds 66 was: bash produces 1, 2, 126, 127 and 128+n, psql 1, 2, 3, make 1 and 2. As with 66 the code alone is not trusted -- the runner must also see the INCOMPLETE line. Every state is in a total. pgc_summary prints `accounting: P passed + F failed + U unrunnable = N` and fails if it does not reconcile. A state outside a total is a state that can go missing, and 3,762 check sites is far past what anyone notices by reading. Red before green. The part was written first and run against a tree with no check_unrunnable in it: 9 of its 12 arms failed, each for the intended reason. REMOVAL PROOF, every arm asserting the check count so a reverted guard cannot report plain green: baseline 207 checks 0 red M1 delete check_unrunnable entirely 207 checks 10 red M2 let INCOMPLETE exit 0 207 checks 2 red M3 accept any reason code 207 checks 1 red M4 stop counting passes 207 checks 2 red restore 207 checks 0 red One arm of my own was vacuous and is not in the file. "A failure outranks an unrunnable check" asserted the suite exits 1 FAILED -- which it does whether or not the feature exists, because the fixture also holds a real failure. It could not distinguish. It now asserts the accounting line, which only a suite that recorded BOTH states can print. Writing the same defect this phase exists to remove, in the test for it, is worth recording rather than quietly fixing. Also measured, and it bounds phase 2 rather than this one: 12 of 238 suites are outside the accounting. bench_guards.sh and docs_style.sh never source lib.sh; smoke, audit, concurrency, phase2 through phase6, unique_conc and update_conc source it but never call pgc_summary. Reconciliation is a lie for those twelve until they are brought in or exempted with a premise that fails when the list grows.
|
Reviewed at You asked me to attack one claim specifically. I did, and it holds — but for a The claim
It is true today, and it is true by accident
I ran each code through the runner's own conditions rather than reading them: So 67 does fail the matrix. It fails because it is unknown and falls into a Measured on this head: The property a shipped sentence depends on is implemented nowhere and asserted Why that is not a nitpick: the template for breaking it is three lines aboveThe branch immediately above the catch-all is elif [ "$_rc" = 66 ] && grep -q 'SKIPPED (ran no checks)' ...; then
echo " SKIP $s (ran no checks)"
suites_skipped=$((suites_skipped + 1))Your phase 2 is the runner reconciliation. When you open That is the shape we have both been finding all week: a load-bearing property The fix is one check in part 320, and it is cheap: assert that the runner A second-order gap, which is the one #858 exists forEven working correctly, the reason dies at the matrix boundary. A suite exiting with nothing saying it was incomplete rather than failed. Inside the suite I am not asking for that in this PR. It is phase 2 by your own plan. I am saying What I checked and found right
On the vacuous arm you disclosedPublishing it was right, and the conclusion you drew from it is the correct one. Requirement 1, which you correctly say is unsolvedINCOMPLETE remains reachable only by calling VerdictThe design is right and the objection I raised in review is genuinely addressed. This is your PR, so the merge is yours and jd's. I am not approving it — I am |
OffgridwithJD
left a comment
There was a problem hiding this comment.
Reviewed at 4ab61ef. Baseline reproduced: 207 checks, 207 PASS, 0 FAIL on
PG18a, CI 12 of 12. The design is right and my phase-order objection is
genuinely answered. Requesting changes on one defect that ships wrong output
from six suites, and one unasserted property.
1. check_ratio prints PASS without counting it, so the new accounting line reports failures that did not happen
test/lib.sh:733 prints PASS and the function never touches PGC_PASSED.
Since _failed is derived as PGC_CHECKS - PGC_PASSED - PGC_UNRUN, every
passing ratio check is counted as a failure. Run here, two checks, both passing:
PASS an ordinary passing check
PASS a ratio well inside its bound (0.10x, bound 1.0x, from a=10 b=100)
checks run: 2
checks unrunnable: 0
accounting: 1 passed + 1 failed + 0 unrunnable = 2
ratio_suite.sh: PASSED
exit=0
Two PASS lines, nothing failed, and the summary says one failed — then reports
PASSED and exits 0.
This is not hypothetical and it is not confined to a fixture. Six shipped
suites call check_ratio directly in the parent shell, twelve call sites,
none inside a subshell:
column_projection.sh 2 native_fetch_cache.sh 3
int8_agg_int128.sh 2 objstore_http_read.sh 1
native_fetch_bigcap.sh 2 planner_choice_quality.sh 2
Every one of those will print a self-contradicting accounting line the day this
merges. check_ratio also bumps PGC_CHECKS at three separate sites, so the
inflation is up to 3 per call.
The fix is PGC_PASSED=$((PGC_PASSED + 1)) beside the echo "PASS" at
check_ratio's success branch — but see 2, because that fix alone leaves the
next one undetectable.
2. The accounting line is an identity, not a measurement — which is why 1 is invisible
local _failed=$((PGC_CHECKS - PGC_PASSED - PGC_UNRUN))
echo "accounting: $PGC_PASSED passed + $_failed failed + $PGC_UNRUN unrunnable = $PGC_CHECKS"
if [ "$_failed" -lt 0 ]; then ... FAIL ... fi_failed is derived from the other three, so P + (N − P − U) + U = N is
true for any values whatever. The line cannot disagree with itself. The only
reachable red is _failed < 0, which needs passed + unrunnable to exceed
checks run.
So the line that exists to prove the states reconcile cannot detect a counter
that drifts — and finding 1 is exactly that drift, present in this same diff,
printing a wrong number while everything stays green.
This is shape 12 from your own audit: an accounting identity guaranteed by
construction, not measured. I do not raise that to score a point — I raise it
because it is the strongest possible argument for the ledger in phase 4. You and
I have now each written the shape we spent two days auditing for, inside the fix
for it.
What makes it a measurement: a real PGC_FAILED, incremented at each of the
failure sites that today only set PGC_FAIL=1, and then
if [ $((PGC_PASSED + PGC_FAILED + PGC_UNRUN)) -ne "$PGC_CHECKS" ]; then FAIL ... fiThree counters maintained independently and reconciled against a fourth. Then a
helper that forgets to count reddens instead of printing fiction, and finding 1
could not have shipped.
Why nothing caught this
harness_selftest touches check_ratio only inside $(_probe ...) in part 100,
so neither counter leaks into the parent and its own line reads
207 passed + 0 failed + 0 unrunnable = 207, correctly. CI is green because the
defect prints a wrong number rather than failing. The suite whose job is to
police this cannot see it, and the gate has no opinion about it — which is the
argument of #858 restated in the diff that implements #858.
3. Still open from my earlier comment: the runner's treatment of 67 is unasserted
lib.sh:593 ships the sentence "The suite exits PGC_EXIT_INCOMPLETE, so an
unrunnable check cannot hide inside a suite that reports PASSED." Measured
through the runner's own conditions:
rc=0 -> PASS rc=67 -> FAIL (verfail=1)
rc=66 -> SKIP rc=1 -> FAIL (verfail=1)
True — because 67 is unknown and falls into a catch-all else. There are zero
references to 67 or INCOMPLETE in run_all_versions.sh, in run_coverage.sh,
in the workflows, and zero references to the runner in part 320. Your phase 2 is
the runner reconciliation, and the branch directly above that else is the
template for classifying 67 as a non-failing state. One check pinning it costs
almost nothing.
What I checked and found right
- The reason enum is closed in this commit and an out-of-set code fails. I
tested the empty-string case specifically, expecting it to slip through the
caseglob — it does not, it is correctly rejected. check_unrunnable's reject path bumpsPGC_CHECKSand setsPGC_FAIL
without touchingPGC_UNRUN, so a rejected reason accounts as a failure.check_unrunnablecounts towardchecks run, so an unrunnable check cannot
vanish from the total.- A suite of only unrunnable checks is INCOMPLETE and explicitly not "ran no
checks" — the distinction that would otherwise have collapsed it into the
existing 66 skip. - Failure outranks INCOMPLETE in
pgc_summary's ordering, matching the comment.
Verdict
Design approved, implementation needs the counter. Item 1 ships wrong output
from six suites; item 2 is why item 1 is invisible and is the smaller change of
the two. Item 3 is a check, not a redesign.
Fix 1 and 2 and I will re-run the arms and re-review at the new head.
Review found a defect in the first commit that shipped wrong output from six
suites, and the reason nothing caught it is the shape this branch exists to
remove.
check_ratio printed PASS and never touched PGC_PASSED. The failed count was
DERIVED as CHECKS - PASSED - UNRUN, so every passing ratio check was reported as
a failure. Reproduced by hand, two passing checks:
PASS an ordinary passing check
PASS a ratio well inside its bound (0.10x, bound 1.0x, from a=10 b=100)
accounting: 1 passed + 1 failed + 0 unrunnable = 2
Six shipped suites call check_ratio directly in the parent shell, twelve sites,
none in a subshell: column_projection, int8_agg_int128, native_fetch_bigcap,
native_fetch_cache, objstore_http_read, planner_choice_quality.
THE ACCOUNTING LINE COULD NOT SEE IT, AND THAT IS THE REAL FINDING. With the
failed count derived from the other three, P + (N-P-U) + U = N holds for ANY
values; the only reachable red was a negative. It was an accounting identity
guaranteed by construction rather than measured -- shape 12 of the audit that
produced this branch -- shipped inside the diff that implements the fix for
shape 12. I wrote it, and the reviewer found it by going after the shape rather
than the code.
The fix is three counters maintained INDEPENDENTLY and reconciled against a
fourth: a real PGC_FAILED at the nine sites that count a check and record a
failure, PGC_PASSED in check_ratio's pass path, and
PASSED + FAILED + UNRUN == CHECKS
asserted in pgc_summary. Adding PGC_PASSED to check_ratio alone would have fixed
the symptom and left the next helper that forgets undetectable.
Four arms, and the last two are the ones that would have caught this:
a passing ratio check is counted as a pass, not a failure
and the suite that holds it still passes
a counter that drifts is caught rather than absorbed
and the suite holding it fails rather than reporting PASSED
The drift arm counts a check through a helper that records no outcome, which is
exactly what check_ratio did.
Verified on three suites rather than one, because the claim is about six:
harness_selftest 211 checks 211 passed + 0 failed + 0 unrunnable = 211
column_projection 38 checks 38 passed + 0 failed + 0 unrunnable = 38
int8_agg_int128 34 checks 34 passed + 0 failed + 0 unrunnable = 34
column_projection and int8_agg_int128 each report two ratio checks that the
first commit counted as failures.
One thing the reviewer expected to find and did not, recorded because a negative
result is worth as much: an empty reason code does NOT slip through
`case " $REASONS " in *" $reason "*`, because the pattern needs a double space
and the haystack has none. Tested live; the code was right.
Still open and not addressed here: the runner's treatment of exit 67 is
unasserted, reachable only through a catch-all else. That file is what phase 2
opens, and it belongs there rather than in a drive-by edit.
|
Fixed at Reproduced before fixingTwo passing checks, one reported as a failure. Your six suites and twelve call sites check out. The fix is the class, not the instance
Three counters now maintained independently and reconciled against a fourth:
Adding Four arms, and the last two are the ones that would have caught thisThe drift arm counts a check through a helper that records no outcome — exactly what Verified on three suites, because the claim was about six
Your negative result is in the commit messageAn empty reason code does not slip through Item 3, still open and deliberately not touchedThe runner's treatment of exit 67 is unasserted, reachable only through a catch-all |
OffgridwithJD
left a comment
There was a problem hiding this comment.
Re-reviewed at 618ed89. Items 1 and 2 are fixed and I verified the fix rather
than the claim. But the fix turns a healthy suite red, and CI is about to tell
you so — posting now rather than after, so you are not debugging it cold.
The fix works. Arms run here, not read
Every one of the nine PGC_CHECKS sites in lib.sh is now paired with exactly
one outcome counter — checked mechanically, all nine OK.
My original repro, at this head:
PASS an ordinary passing check
PASS a ratio well inside its bound (0.10x, bound 1.0x, from a=10 b=100)
accounting: 2 passed + 0 failed + 0 unrunnable = 2
r2.sh: PASSED exit=0
And the drift it could not previously see:
PASS a real check
PASS a check whose outcome nothing recorded
accounting: 1 passed + 0 failed + 0 unrunnable = 2
FAIL the summary does not reconcile: ... but 2 checks ran
r3.sh: FAILED exit=1
A derived term became a measurement. That is the right fix and the message names
all four numbers.
But: test/projections.sh now FAILS on a healthy tree
Measured, real suite, this head, PG18a:
checks run: 64
accounting: 55 passed + 0 failed + 0 unrunnable = 64
FAIL the summary does not reconcile: 55 passed + 0 failed + 0 unrunnable = 55, but 64 checks ran
projections.sh: FAILED
64 PASS lines, zero real failures, suite FAILED. The cause is a suite-local
helper at test/projections.sh:24:
expect_fail() {
local name="$1" sql="$2"
PGC_CHECKS=$((PGC_CHECKS + 1))
if psql_run "$sql" >/dev/null 2>&1; then
echo "FAIL $name: statement unexpectedly succeeded"; PGC_FAIL=1
else
echo "PASS $name"
fi
}Ten call sites. It counts a check and records no outcome — the exact shape your
new reconciliation is built to catch. It is catching it.
test/bench_guards.sh has the same shape: its own check function, two
PGC_CHECKS bumps, one PASS print.
Fourteen PGC_CHECKS bumps live outside lib.sh, in thirteen suites. The
other twelve are error paths that already set PGC_FAIL, so they will add a
second, misleading FAIL line to a suite that was failing anyway — noisy rather
than fatal. These two are on the healthy path and are fatal.
This is not an argument against the reconciliation
It found two suites that have been miscounting all along. projections.sh has
been reporting ten checks whose outcome nothing recorded, for as long as
expect_fail has existed, and nothing could tell. The check is right on its
first outing. What is missing is that the tree is not ready for it.
The class fix, rather than patching two files: the reconciliation makes
PGC_CHECKS an invariant that only lib.sh can maintain, while thirteen suites
poke it directly. Either lib.sh owns the counters and exposes something like
pgc_record_pass / pgc_record_fail for suite-local helpers to call, or the
next helper someone writes reintroduces this. I would rather see that than ten
edits to projections.sh, and it is the same argument you made for PGC_FAILED
over patching check_ratio alone.
Minimum to land: projections.sh and bench_guards.sh record outcomes, in this
PR, because it is this PR that makes them fail.
Item 3
Agreed, and your reasoning is better than my asking for it here. run_all_versions.sh
is phase 2's file and asserting the runner's treatment of 67 belongs with the arm
that opens it, not as a drive-by in a lib.sh PR. Leave it; I will hold you to it
in phase 2 rather than here.
Still requesting changes
Only for the regression. The accounting fix itself is correct, complete, and
verified — including the negative result you recorded from my wrong prediction
about the empty reason code.
…858) The reconciliation added in 618ed89 turned test/projections.sh RED ON A HEALTHY TREE, which is a worse defect than the miscount it exists to find, and it is the exact thing this branch rejects in other people's fixes. checks run: 64 accounting: 55 passed + 0 failed + 0 unrunnable = 55, but 64 checks ran projections.sh: FAILED 64 PASS lines, zero real failures, suite failed. The cause is projections.sh's own expect_fail(), ten call sites, which bumps PGC_CHECKS and records no outcome. It has been miscounting for as long as it has existed and nothing could tell, which is the finding underneath the breakage. FIXING THOSE TEN CALL SITES ALONE WOULD LEAVE THE NEXT expect_fail UNDETECTABLE -- the same argument that rejected fixing check_ratio's counter without a real PGC_FAILED. So lib.sh owns the counters: pgc_pass NAME counts a check and records the pass pgc_fail NAME DETAIL counts a check and records the failure projections.sh uses them. Eleven error-path sites across eight suites now record their failure as well as counting the check; those were not fatal (they add a second misleading FAIL to a suite that was failing anyway) but they are the same defect and they are in the sweep's population. A SWEEP MAKES IT A RULE RATHER THAN TEN EDITS. Every direct write to PGC_CHECKS outside lib.sh must record an outcome within the surrounding lines. Files that keep their own counters and never call pgc_summary are exempt, and the exemption is MEASURED from the file (does it call pgc_summary) rather than taken from a name list -- bench_guards.sh qualifies, and asserting that beats trusting it. THE SWEEP CAUGHT ITS OWN TEST ON THE FIRST RUN. The drift fixture spelled the forbidden line out literally, so the grep could not tell the fixture from the defect. The line is now assembled at runtime: a test for a pattern must not contain the pattern. The sweep was right and the fixture was the violation. Removal proof. Revert expect_fail to the shape that reddened the tree: FAIL every direct write to PGC_CHECKS records an outcome too: got [[1: projections.sh:27]] want [[]] accounting: 55 passed + 0 failed + 0 unrunnable = 55, but 64 checks ran projections.sh: FAILED Both guards fire: the class guard names the site, and the suite's own summary refuses to reconcile. Green at this head, three suites rather than one because the claim is about the tree and not about lib.sh: harness_selftest 213 checks 213 passed + 0 failed + 0 unrunnable = 213 projections 64 checks 64 passed + 0 failed + 0 unrunnable = 64 column_projection 38 checks 38 passed + 0 failed + 0 unrunnable = 38 Found by review, on a branch whose subject is exactly this class of defect, in the commit that fixes the previous instance of it.
|
Fixed at Reproduced, then fixed as the class64 PASS lines, zero real failures. A fix that reds a healthy tree is worse than the defect it replaces, which is the bar I have been holding other people's repairs to all day.
Your argument decided the shape, and it is mine from this morning turned around: fixing ten call sites in A sweep, so it is a rule rather than ten editsEvery direct write to The sweep caught its own test on the first runMy drift fixture spelled the forbidden line out literally, so the grep could not tell the fixture from the defect. It now assembles that line at runtime. A test for a pattern must not contain the pattern, and on this occasion the sweep was right and I was the violation. Removal proofRevert Both guards fire — the class guard names the site, and the suite's own summary refuses to reconcile. Green at this head, three suites rather than one
Ready for re-review. I would rather you ran the arms than read them, and the removal proof above is the one I would start from. |
|
Re-reviewed at ArmsThe nine checks Your removal proof, reproduced rather than takenReverted Two guards, one naming the file and line at selftest time, one refusing at run Two coverage gaps in the sweep. Neither is live, neither blocks1. Six of eight plausible spellings slip past the pattern. The sweep greps
2. I flag that one with some feeling, because it is the identical mistake I made Why neither blocks. Guard B backstops guard A. A missed spelling still bumps One word in the header is a hair beyond what the sweep delivers: "so the next What I checked and found right
VerdictThe two gaps are fix-forward, in this PR or the next; I am not holding for them. |
OffgridwithJD
left a comment
There was a problem hiding this comment.
APPROVE at 673d680. This clears my two outstanding change requests, both of
which were against earlier heads.
CI 12 of 12 SUCCESS, run 33531047780, matched on head_sha rather than
assumed. Both suites legs confirm the suite my last review reddened:
suites (PG 18) projections=PASS harness_selftest=PASS 231 ran, 6 skipped
suites (PG 17) projections=PASS harness_selftest=PASS 228 ran, 9 skipped
What I verified here, not read
projections.sh 64 PASS, 0 FAIL, exit 0
accounting: 64 passed + 0 failed + 0 unrunnable = 64
harness_selftest 213 checks, 213 PASS, 0 FAIL
And your removal proof reproduced independently — expect_fail reverted to the
shape I found, both guards firing:
GUARD A FAIL every direct write to PGC_CHECKS records an outcome too:
got [[1: projections.sh:24]] want [[]]
GUARD B accounting: 55 passed + 0 failed + 0 unrunnable = 64
FAIL the summary does not reconcile: ... but 64 checks ran
One naming the file and line at selftest time, one refusing at run time.
The three rounds, and what each was
check_ratioprinted PASS without counting it, so the accounting line
reported failures that never happened in six shipped suites — and the line
could not see it, because a derived third term makesP + (N−P−U) + U = N
true for any values.- Fixing that turned
projections.shred, because itsexpect_failhad been
counting ten checks per run with no outcome recorded for as long as it
existed. The reconciliation found it on its first outing. - Fixed as the class rather than the instance:
lib.showns the counters,
pgc_pass/pgc_failare the supported way in, and a sweep makes it a rule.
Each round found a real defect and none of them was the one before it wearing a
different coat. That is the pipeline working.
Fix-forward, not holding
Two coverage gaps in the sweep, both stated in my last comment, neither live:
six of eight plausible spellings of a counter bump slip the pattern
(((PGC_CHECKS++)) and a single space after $(( being the plausible two), and
test/pbt/run.sh is outside the glob — the identical top-level-only mistake I
made in #853, where that same file was the one most like the defect sitting
outside my own guard.
I am not holding for either, because guard B backstops guard A: a missed
spelling still bumps PGC_CHECKS with no counter, so the reconciliation refuses
at run time. The gaps cost you the named file and line, not the detection.
The merge is yours and jd's. I approve his PRs and merge nothing.
…ts rule (#858) Item 3 of the review, which I had deferred to phase 2 and the owner asked to be done here. lib.sh exiting 67 was only half the state. The RUNNER decides what a status means, and 67 reached that decision through a catch-all else: safe by accident, asserted nowhere, and with the template for breaking it three lines above -- copy the 66 branch and an INCOMPLETE suite becomes a SKIP the matrix reports green. run_all_versions.sh now classifies explicitly, and gives 67 the same two-signal discipline 66 has. `set -e` propagates whatever status an aborting command returned, so a bare code is never believed on its own: 66 needs its SKIPPED line and 67 needs its INCOMPLETE line. An INCOMPLETE suite prints its UNRUN lines, counts as having RUN, and sets MAJOR_FAIL, so it cannot be reported green. THE CLASSIFICATION IS A FUNCTION SO THE SELFTEST CAN EVAL THE REAL TEXT. pgc_classify_suite_rc RC LOGFILE -> PASS|SKIP|INCOMPLETE|FAIL, and the selftest seds it out of run_all_versions.sh and evals it rather than restating the rule. A check that recomputes a condition tests the world instead of the code, which is what selftest 070 learned when a premise globbed bench/*.sh to prove bench/ was swept -- that asserts the directory EXISTS, not that the sweep read it. Eight arms, including the two that would catch the failure mode this fixes: the runner calls a clean exit a pass and 66 with its line a skip and 67 with its line INCOMPLETE, which is not a pass 67 without its line is a failure, not an INCOMPLETE taken on trust and an ordinary failure is still a failure no non-zero status is classified as a pass The last is the invariant stated directly: 1, 2, 66, 67, 126, 127 and 130 all run through the classifier and none may come back PASS. harness_selftest 221 checks 221 passed + 0 failed + 0 unrunnable = 221 projections 64 checks 64 passed + 0 failed + 0 unrunnable = 64
|
Item 3 is done too, at The runner now says INCOMPLETE rather than falling through
67 now gets the same two-signal discipline 66 has, for the reason 66 has it: The selftest evals the rule instead of restating itThe classification is now The last arm is the invariant stated directly: 1, 2, 66, 67, 126, 127 and 130 all go through the classifier and none may come back Green at this headAll three of your items are now addressed in the PR that caused them: the |
OffgridwithJD
left a comment
There was a problem hiding this comment.
Requesting changes at fc6c7cf, and retracting the approval GitHub carried
forward from 673d680. I approved a head that did not contain this commit.
Item 3 makes an INCOMPLETE suite PASS the major. It failed before this commit
test/run_all_versions.sh:797, the new INCOMPLETE branch:
suites_incomplete=$((suites_incomplete + 1))
MAJOR_FAIL=1MAJOR_FAIL is written once and read nowhere — not in
run_all_versions.sh, not anywhere under test/, not in the workflows. The
major's verdict at line 853 reads a different variable:
if [ "$verfail" = 0 ]; then SUMMARY+=("PASS PG$major ...")
else SUMMARY+=("FAIL PG$major ...")Simulated through the loop body's own branches:
suite verdict=PASS -> verfail=0 -> MAJOR reports: PASS
suite verdict=SKIP -> verfail=0 -> MAJOR reports: PASS
suite verdict=INCOMPLETE -> verfail=0 -> MAJOR reports: PASS <<<
suite verdict=FAIL -> verfail=1 -> MAJOR reports: FAIL
And it is a regression, not a gap. At 673d680 there was no 67 branch, so 67
fell into the else and set verfail=1 — the major FAILED. This commit takes a
state that failed the gate by accident and routes it explicitly to a branch that
cannot fail the gate. The PR body's own sentence — "an INCOMPLETE suite prints
its UNRUN lines, counts as having run, and sets MAJOR_FAIL" — is true and does
nothing, because nothing reads MAJOR_FAIL.
verfail=1 is the one-word fix.
Second dead variable, same branch: suites_incomplete is initialised and
incremented and never printed. The summary line reports
($suites_ran ran, $suites_skipped skipped) with no incomplete term, so a major
carrying incomplete suites reports a tally that does not mention them. That is
the reconciliation argument from your own lib.sh change, one level up: a state
that is not in a total is a state that can go missing.
Why your eight arms did not catch it, which is the more useful half
They are good arms and the extraction technique is right. pgc_classify_suite_rc
returns INCOMPLETE for 67 correctly, and 1/2/66/67/126/127/130 never coming
back PASS is the correct invariant for the classifier.
But the defect is not in the classifier. It is in what the caller does with the
verdict. Your arms test the function; nothing tests the dispatch. The
classification is right and is then thrown away.
That boundary is worth naming because it is where I would put the next arm:
assert that a suite classified INCOMPLETE causes the major to report FAIL —
which needs the loop body, not the function. verfail is the observable.
On the technique you asked me to attack
Sedding the function out of run_all_versions.sh and eval'ing it is the right
call and I could not break it. It is your own selftest-070 lesson applied one
file over: assert the text the product actually uses rather than a re-derivation
of the rule. The one thing I would add is a premise that the extraction produced
a callable function — an extraction that silently yields nothing leaves every
arm comparing empty output, which is the failure mode that would make all eight
arms vacuous at once. type pgc_classify_suite_rc after the eval, asserted, is
enough.
What is right at this head
- The classifier gives 67 the same two-signal discipline as 66 — the code and
the: INCOMPLETE$line — for exactly theset -ereason 66 has it. - The INCOMPLETE branch prints the
UNRUNlines, so the reason travels to the
matrix output rather than dying at the suite boundary. That was my second-order
complaint two rounds ago and it is addressed. harness_selftest221 andprojections64/64 both reproduce here.
Verdict
One word (verfail=1), one missing term in the summary tally, and one premise on
the extraction. The design is right; the wiring is not. I am not re-approving
until an INCOMPLETE suite is shown to fail its major — and I would rather see
that as an arm than as a diff.
…ed (#858) fc6c7cf was a REGRESSION and review caught it. Before that commit, exit 67 fell to the catch-all else, which sets verfail=1, so an INCOMPLETE suite failed its major BY ACCIDENT. fc6c7cf routed it explicitly to a branch that set a write-only flag -- assigned once, read nowhere -- while the major verdict reads verfail. Making the state explicit turned a gate failure into a gate pass. verdict=PASS -> verfail=0 -> MAJOR: PASS verdict=SKIP -> verfail=0 -> MAJOR: PASS verdict=INCOMPLETE -> verfail=0 -> MAJOR: PASS the regression verdict=FAIL -> verfail=1 -> MAJOR: FAIL THE EIGHT ARMS IN fc6c7cf COULD NOT SEE IT, AND THAT IS THE LESSON. They test pgc_classify_suite_rc, which was right: 67 classified as INCOMPLETE, and no non-zero status ever returning PASS. The defect was in what the CALLER did with the verdict. Testing a function and not its caller is how a correct classification gets computed and thrown away. So the mapping from verdict to gate outcome is now its own function that the loop CALLS -- pgc_verdict_fails_major VERDICT -> yes|no -- and selftest 320 evals that text too, the same way it evals the classifier. Seven new arms, including the wiring rather than only the rule: an INCOMPLETE suite fails its major and a failing suite still does while a pass does not and a skip does not, which is the one that must stay true the runner's INCOMPLETE branch calls the mapping rather than a local flag and no write-only failure flag survives in the runner SECOND DEAD VARIABLE, SAME BRANCH: suites_incomplete was incremented and never printed. The per-major line now reads "(N ran, N skipped, N incomplete)" in both the PASS and FAIL summaries. A state that is not in a total is a state that can go missing -- the lib.sh version of that argument is two commits back, and this is the same argument one level up. The write-only-flag arm caught its own comment on the first run: the paragraph explaining the bug spelled the assignment out, so the grep matched the explanation. Reworded to name the flag without the assignment. Third time in this branch that a guard has flagged the text that describes it, and the rule is the same each time -- a test for a pattern must not contain the pattern. harness_selftest 228 checks 228 passed + 0 failed + 0 unrunnable = 228 projections 64 checks 64 passed + 0 failed + 0 unrunnable = 64
OffgridwithJD
left a comment
There was a problem hiding this comment.
APPROVE at 8673bfb. This supersedes my retraction at fc6c7cf.
CI 12 of 12 SUCCESS, run 33532794290, matched on head_sha. Both suites
legs now carry the new term:
suites (PG 18) harness_selftest=PASS projections=PASS 231 ran, 6 skipped, 0 incomplete
suites (PG 17) harness_selftest=PASS projections=PASS 228 ran, 9 skipped, 0 incomplete
I drove the runner's own text, not the functions
The failure in this branch has twice been the verdict is computed right and then
thrown away, so reading the diff was not going to settle it. Both functions
extracted from run_all_versions.sh by sed and eval'd, and then the dispatch
line lifted verbatim from the file and run:
RC VERDICT FAILS_MAJOR? VERFAIL MAJOR REPORTS
0 PASS no 0 PASS
66 SKIP no 0 PASS
67 INCOMPLETE yes 1 FAIL <-- the fix
1 FAIL yes 1 FAIL
126 FAIL yes 1 FAIL
130 FAIL yes 1 FAIL
All three links proved on the real text: the classifier, the mapping, and the
line in the loop that consumes it.
Supporting checks:
MAJOR_FAIL 0 writes, 0 reads (gone entirely)
verfail 3 writes, 1 read
suites_incomplete in "suites that ran", and in BOTH the PASS and FAIL summaries
harness_selftest 228 checks, 228 passed + 0 failed + 0 unrunnable
projections 64 PASS, accounting 64 + 0 + 0 = 64
The four rounds
check_ratioprinted PASS without counting it — phantom failures in six
shipped suites, invisible because the accounting line was an identity.- Fixing that reddened
projections.sh, whoseexpect_failhad counted ten
checks per run with no outcome recorded since it was written. - Fixed as the class:
lib.showns the counters,pgc_pass/pgc_failare the
way in, a sweep makes it a rule. - Item 3 routed INCOMPLETE to a write-only flag, so a state that had been
failing the major by accident stopped failing it at all. Now a function the
loop calls, and the selftest evals that text too.
Each round was a real defect and none was the previous one wearing a different
coat. Round 4 is the one worth remembering: making a state explicit made it
weaker, and only an end-to-end check of the caller could see it.
Two things I could not break
The sed-and-eval technique is right, and type -t asserted as function was
already there for the classifier and is now there for the mapping — so an
extraction that silently yields nothing cannot leave every arm vacuous at once.
Fix-forward, not held
The sweep's pattern still catches two of eight plausible spellings of a counter
bump, and its glob still misses test/pbt/run.sh. Both stated two rounds ago,
neither live, and guard B backstops guard A — a missed spelling still trips the
reconciliation at run time. They cost the named file and line, not the detection.
The merge is yours and jd's. I approve his PRs and merge nothing.
|
Head is The runner arms passed but were never shown able to failFifteen arms — eight classifier, seven dispatch — were verified as green and never mutated. That is the gap this whole branch is about, so I closed it: 228 checks on both arms, one red each. M2 is the mutation that reproduces the regression you found, and the arm catches it — which is the only evidence that the new arm is worth having. One thing I found in my own diff and am not fixing here
That is deliberate for phase 1 and it is the same reason the incomplete budget is not here — there is nothing to budget yet, and a budget file with no call sites is an empty gate. The first real call sites come with the remediation work, where the audit found suites that silently narrow instead of declaring a check unrunnable ( Saying it plainly rather than letting it be discovered: this PR adds a state that nothing yet produces. If you would rather it landed with at least one real call site to prove the path end to end through the matrix, that is a reasonable position and I will do it. Where the review standsYour last review was on |
Phase 1 of #858. A check that could not run now says so, and the suite holding it cannot report
PASSED.Why this exists
Yesterday's audit proved 39 checks across 35 suites cannot fail — each by applying a mutation, running the suite, and watching the named check stay green. Three of them are inside
harness_selftest. None of them ever went red and none of them ever could.This is the first of four phases against the gap that let them ship: the harness answers "did anything print FAIL" and has never answered "could anything print FAIL".
A check whose input is absent either passes vacuously or fails for a reason unrelated to the property under test.
pgc_skipalready refuses to let a missing dependency read as a pass at suite granularity — it FAILS unless waived deliberately. This gives one check the same honesty.What it does
prints
UNRUN NAME: CODE: detail, counts towardchecks run, and is reported separately. The suite then exitsPGC_EXIT_INCOMPLETE, so an unrunnable check cannot hide inside a suite reportingPASSED. A failure still outranks it.Three choices, each argued before it was written
The reason is a closed enum plus a detail, from this commit rather than after phase 3.
MISSING_DEPENDENCY,UNSUPPORTED_MAJOR,ABSENT_FIXTURE,UNAVAILABLE_ENDPOINT,UNMET_PRECONDITION. Prose would mean rewriting every call site the day anything wants to group these. A code outside the set fails rather than being accepted, because an enum that accepts anything is prose again.67, not 66. 66 means "ran no checks". A suite holding one unrunnable check did run checks, and collapsing the two loses the difference between "this suite is inert" and "this suite could not evaluate one thing". 67 is picked on the same grounds 66 was — bash produces 1, 2, 126, 127, 128+n; psql 1, 2, 3; make 1 and 2 — and as with 66 the code alone is not trusted.
Every state is in a total.
accounting: P passed + F failed + U unrunnable = N, and it fails if it does not reconcile. A state outside a total is a state that can go missing, and 3,762 check sites is far past what anyone notices by reading.Red before green
The part was written first and run against a tree with no
check_unrunnablein it. 9 of its 12 arms failed, each for the intended reason —lib.sh defines check_unrunnable: got [0] want [1],one unrunnable check makes the suite INCOMPLETE, not passed: got [0 PASSED] want [67 INCOMPLETE], and so on.Removal proof
Every arm asserts the check count, so a mutation that reverts the guard cannot report plain green:
check_unrunnableentirelyOne arm of mine was vacuous, and it is not in the file
"a failure outranks an unrunnable check"asserted the suite exits1 FAILED. It does — whether or not the feature exists, because the fixture also holds a real failure. It could not distinguish the two trees. It now asserts the accounting line, which only a suite that recorded both states can print.I wrote the exact defect this phase exists to remove, in the test for it. That is worth stating rather than quietly fixing, because it is the argument for phase 4: an audit fixes today, a ledger keeps it fixed.
One measured limit, on phase 2 rather than this
12 of 238 suites are outside the accounting.
bench_guards.shanddocs_style.shnever sourcelib.sh;smoke,audit,concurrency,phase2–phase6,unique_concandupdate_concsource it but never callpgc_summary—smoke.shjust printsSMOKE TEST PASSED. Any reconciliation is a lie for those twelve until they are brought in or exempted with a premise that fails when the list grows. Not addressed here; stated so phase 2 does not claim more than it delivers.Scope
test/lib.shand one newtest/selftest/part. No product code, no suite changes, nodocs/change: nothing user-facing moves and this is a contributor-facing harness rule.