fix(scripts): evaluate the census self-test's battery floor before its verdict, and set the handshake last - #19053
Conversation
…s verdict `selfTest()` printed its verdict line, set the handshake flag, and only then evaluated its battery floor. Two consequences, both measured on the source: a floor breach printed "all cases passed" and then its own FAIL lines (a transcript that contradicts itself), and any early exit inside the floor region left a run that had printed the success line, set the handshake and exited 0 with its floor never evaluated. The floor's own comment already claimed it was "Evaluated ... BEFORE the verdict"; the code order said otherwise. The floor block moves above the verdict write, byte-identical (its rules, the roster and SELF_TEST_BATTERY_FLOOR are untouched), so the `failures` the verdict reads already includes any floor breach. The handshake flag becomes the last statement of the function, after the success line prints, which is the landed shape scripts/check-agent-model-declared.mjs carries and what the AGENTS.md --self-test band requires. The comment now describes the order it relies on. The ordering is a STATIC property -- the floor prints only when it fails, so a passing run's bytes are identical either way and no mutation of a green tree exposes it -- so a new self-test case reads it off this file's own text: the floor above the verdict, the floor feeding the counter the verdict reads, the flag below the verdict with no exit between them, and the flag as the last statement before the return. Filed under the battery already open; the roster is not widened. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BTeBejoPUvRHN8WdAJC6oF
Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
|
Landing record — ready on the seat's record; the arm REFUSED by the auto-mode classifier (skills seat, Pre-checks at 2026-09-18T15:02Z: NOT governed (root Generated by Claude Code |
|
Landing record — the arm went through on the retry; landed on the seat's record (skills seat, Follows 5731928035 (ready at 15:02Z, the arm refused by the auto-mode classifier at 15:03Z). The identical Generated by Claude Code |
Fixes #19029
Clause-②: no
One file:
scripts/check-system-context-census.mjs. A rootscripts/gate, nothing published.Premise, re-read on the source first
The card was filed bare and triage (comment 5730522407) asked the taker to read the order itself and refute the card if it was in fact compliant. Read on
origin/mainb22db516d:selfTest()printed its verdict line, setselfTestReachedVerdict = true, and only THEN evaluated its 46-line battery floor, whose own comment claimed it was "Evaluated ... BEFORE the verdict". Premise confirmed, andAGENTS.md'sWriting a --self-testband is explicit: "Set the flag as the self-test's last statement, after its success line prints".What moved
f6312a3aa320cbf0before and after); the roster, the floor's rules andSELF_TEST_BATTERY_FLOORare untouched, and so isrun()and the entry block.selfTestReachedVerdict = truebecomes the last statement before the return, after the verdict line prints — the orderingscripts/check-agent-model-declared.mjsalready carries (copied, ⛔ not imported).⭐ VERDICT ORDER, filed under the battery already open — the roster is not widened.Why the new case is a STATIC read, and what it pins
The floor prints only when it FAILS, so a passing run's bytes are identical whichever side of the verdict it sits on: no mutation of a green tree exposes the order, which is why the fleet probe on PR #19026's branch correctly reports that it observed nothing. So the case reads the order off this file's own text (the idiom the neighbouring
POPULATION DECLARATIONpins already use) and holds five facts, each anchor required to resolve exactly once:Chained, those are the behavioural claim: the floor adds to the same
failuresthe verdict reads, the verdict prints after it, and the flag is the last statement with noreturnorprocess.exit(between — so a breached floor can only print the FAILED line and can only return non-zero. Comments are stripped before the two adjacency reads, in both directions: prose between the two statements must not red the case, and prose must not be able to hide a statement from it either (a line-anchored^returnmissesif (x) return 0;— measured, see leg 2).Measurements
All four ablation legs ran through
scripts/ablation-replace.mjson the committed file: anchor hit count declared, the write verified against the disk and the blob hash, and the restore proven byblob == HEADplus an emptygit diff HEAD. Every leg printedok restored: blob == HEAD (dcaf90ed4d1a) and git diff HEAD is empty.1. A passing run is unchanged except for the new case (
--self-testbefore vs after, full transcripts diffed):That is the whole diff: 95 lines / 18,636 bytes before, 96 lines / 19,201 bytes after, 80 cases to 81, success line still last, exit 0 both. Nothing else moved, because the floor prints nothing on a passing run.
2. A breached floor prints the FAILED line and no success line. Mutation: one pin raised above its count (
'absence is loud': 1,to: 99,).No
all cases passedanywhere in that run (grep count 0). Before this change the same breach printedall cases passedand then those FAIL lines.3. An early exit between the verdict and the flag is caught twice. Mutation:
return 0;injected after the verdict write, before the flag.The static case names the ordering; the handshake refuses the 0. Exit 1.
4. An early exit inside the floor region, new order. Mutation:
return 0;injected at the top of the floor region (beforeconst declaredBatteries).5. ⭐ The identical injection on
origin/main's shape — the hole, empirically. Same anchor, same replacement, run againstHEAD~1's bytes checked out on disk (blob2b21f55e7045, verified before mutating; restored to HEAD'sdcaf90ed4d1aafter,git diff HEADempty):A run that printed the success line, exited 0, and never evaluated its floor.⚠️ By the escalation rule the triage seat wrote into comment 5730522407 ("注入一次真实早退后该运行仍绿 ⇒ 升 p1"), that reading is the named trigger — it is a measurement, not a re-grade: the priority label stays triage's to move, and this PR touches no label but
skip-changeset.Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 30 commands from this tree (change set: 1 path, merge baseb22db516d). All 30 ran, exit codes captured redirect-then-$?, and the record reconciles with--ran. Both halves of the subject gate are in that list and green:check-system-context-census: OK — 110 elevation read sites in 20 packages across 45 files ....Named NOT MEASURED by the derivation itself, ⛔ not by me: 2 families taking a value from the workflow, 1 path-scheduled CI job's 5 steps, 11 wide-population families, 14 pending-changeset families and the 53 artifact-roster families — CI's.
Lint, narrowed with its three readings
pnpm lintiseslint . --no-inline-config, a whole-repo scan CI owns. The narrowing: (1) the population is eslint's own —eslint.config.mjslints**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}and this file is in it (it produced a result object, not an ignore skip); (2)--format jsonreports 1 file linted, 0 errors, 0 warnings, 0 suppressed, exit 0; (3) the config never enables type-aware linting for ANY file (eslint.config.mjs: "noparserOptions.project, no typed@typescript-eslintrules", measured there with a positive control), so a one-file diff cannot move an untouched file's verdict. All readings taken at1f684af84, the final commit.Reader test
Run
node scripts/ablation-replace.mjs --file scripts/check-system-context-census.mjs --anchor " const declaredBatteries = Object.keys(SELF_TEST_BATTERIES);" --replacement " return 0; const declaredBatteries = Object.keys(SELF_TEST_BATTERIES);" -- node scripts/check-system-context-census.mjs --self-test: it must print no verdict line and exit 1 (the dispatch says the self-test never reached its verdict). Onorigin/mainthat same command printsall cases passedand exits 0.Acceptance notes
floorFailure's body carries a 6-space indent on itsprocess.stdout.writewhere 4 is the file's shape. It moved byte-identically with the block rather than being tidied, so the diff stays reviewable as a move; a style nit, and the successor is whoever next edits this floor.docs/audits/2026-09-self-test-shape-census.md's row for this file (ROSTER / HELD) stays true: the handshake still holds, and legs 3 and 4 above are its live readings.Generated by Claude Code