fix(scripts): give 24 more self-tests a verdict handshake at their dispatch (#13798 batch 2) - #14853
Conversation
…spatch Batch 2 of the #13798 remedy: the boundary-only PR #13797 shape, transplanted to the 14 files the probe still reads DEFEATED and released by their holding PRs, the six that hold today only by accident (a downstream TypeError or a usage error, not a handshake), the three that also call their self-test on the production path and discard the result, and check-workspace-manifest-cycles.mjs (runSelfTest() shape, NOT MEASURED by the census for want of an ENTRY_BY_HAND row, defeated in fact). Two shapes, one per dispatch spelling, both from batch 1 verbatim: - sentinel: the self-test returns SELF_TEST_VERDICT only after its verdict line, and the dispatch refuses anything else (where the return value is discarded); - module-level `selfTestReachedVerdict` flag (where the self-test's own exit code is load-bearing, or where it ends in process.exit and an appended sentinel would be dead code). Nothing inside any self-test body changes beyond the verdict marker on the line after the success line. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LAwHpn4uVuf4N1geBcD5i3
…ng it
The handshake was placed after `const { passed, lines } = selfTest()`, so an
early return made the destructuring throw a TypeError before the check could
run — the very accident that made this gate read HELD without a handshake. The
census now reports the named refusal instead of a stack trace.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LAwHpn4uVuf4N1geBcD5i3
…lf-test-handshake-b2
|
PM status ( Generated by Claude Code |
|
PM action ( Generated by Claude Code |
Two conflicts, both against main commits that landed after this branch's merge base 5ff5f95: - .claude/skills/pm-dispatch/references/lanes/services.md: main's 365141f (#14856) edited the very bullet this branch deletes (the bullet moved to references/platform-readings.md). Settled to OUR side: the bullet stays deleted and the move stands. The FACT main corrected in that bullet (the i18n gates answer PREREQUISITE NOT MET with exit 3, not 1) is carried into the moved text by the follow-up commit, so this merge commit's non-conflict content stays a pure merge of the two parents. - scripts/check-ratchet-remedy-authority.mjs: main's a610775 (#14896) added the self-test battery roster and floor at the spot where this branch's section (21) sits. Settled by keeping BOTH: section (21) opens with battery('(21) ...'), is declared in SELF_TEST_BATTERIES at its case count (2), and sits before the floor-evaluation block; the roster's own size pin SELF_TEST_BATTERY_FLOOR moves 20 -> 21 by the header's own convention (the roster size is pinned at the declared count). The exception phrase stays assembled, never spelled, in author-facing text, and the gate's run line is byte-identical to main's. scripts/pm/check-skill-line-ratchet.mjs auto-merged with main's c47a5ac (#14853) verdict handshake; the two run-path verdict lines this branch adds are outside that handshake, which covers selfTest()'s sentinel return only. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
Part of #13798 (batch 2). Batch 1 is PR #14479, merged.
node scripts/measure-self-test-floor.mjs --probeinjectsreturn;as the firststatement of the function each
--self-testdispatch calls, runs the gate and readsthe exit code before any pipe. Exit 0 is the defect: a self-test that never finished,
reported as one that passed.
What this batch does
The same boundary-only handshake batch 1 landed, transplanted to 24 more files. Two
shapes, one per dispatch spelling, both taken verbatim from the PR #13797 precedent:
selfTest()returnsSELF_TEST_VERDICTonly after itsverdict line, and the dispatch refuses anything else. Used where the dispatch discards
the return value. 10 files take it for the first time; the other 3 are the
production-path additions below, on files that already carry the sentinel from batch 1.
selfTestReachedVerdictflag (11 files) — used where theself-test's own exit code is load-bearing (
process.exit(selfTest()),return selfTest(), the ternary form) or where the self-test already ends inprocess.exitand an appended sentinel would be dead code.Nothing inside a self-test body changes beyond the verdict marker on the line after
the success line. The census's hole-1 floor classification is identical for all 165
files before and after — the mechanical proof the remedy is boundary-only.
The probe, before and after
Measured on this branch's own merge base, not re-derived by hand.
2d40f9146)The 2 that remain are asserted equal as a set to the two files held by an open PR
and deliberately excluded here:
scripts/check-type-check-coverage.mjs(PR #14805)and
scripts/pm/bare-root-worklist.mjs(PR #14800). Nothing else is left defeated.14 of the 24 flipped
DEFEATED -> HELDin the census. The other 10 are shapes thecensus reads HELD or NOT MEASURED for reasons that are not a handshake, and each
carries its own evidence below.
The six that held only by accident
The card's own note: "5 gates hold today only by luck — a downstream TypeError or a
usage error, not a handshake." The ACCEPT ruled them into this batch (Q1 → A). The
census verdict does not move for these (HELD before, HELD after), so the evidence is
the first line the mutated run prints — the accident replaced by the named refusal:
check-osv-exemptions.mjsfile:///…(TypeError stack)typecheck-configs.mjsfile:///…(TypeError stack)check-exported-any-returns.mts/home/…(TypeError stack)checklist-select.mjsusage: node scripts/checklist-select.mjs …(exit 2)run-with-stall-guard.mjsrun-with-stall-guard: unknown option --self-testcheck-page-declaration-shape.mjscheck-osv-exemptions.mjsneeded one extra line beyond the transplant, in its owncommit: its dispatch destructures the result (
const { passed, lines } = selfTest()),so on an early return the destructuring threw before the handshake could run — the
accident survived the first attempt, and the census said so. The result is now read
into a local first and destructured after the handshake.
Three traps this surface carries
} else if (argv.includes('--self-test')) selfTest();followed by
else run();needs braces, or the trailingelsere-binds.scripts/run-with-stall-guard.mjsdispatches at the topof the file, above the self-test definition; the flag is declared above the
dispatch, not next to the function.
neverreturn type.check-exported-any-returns.mtsdeclaresfunction selfTest(): never. It takes the flag rather than the sentinel precisely sothe annotation does not have to move —
check-test-typecheck.mtsin batch 1 is theprecedent for how a sentinel return drifts a shrink-only DEBT ledger, and raising a
ledger is maintainer-only.
The production-path half (ACCEPT Q2 → A)
check-filter-alias-parity.mjs,check-meta-type-normalized.mjsandcheck-test-completeness.mjsalso run their self-test on the production path anddiscarded the result there — the same silence one seam over, and one the census never
drives because it only runs
--self-test. Each now compares the sentinel on that pathtoo. Ablated by hand with the census's own injector, run with no
--self-testonthe command line: baseline exit 0 / 0 / 3, mutated exit 1 / 1 / 1, each printing the
named refusal.
check-workspace-manifest-cycles.mjsis fixed but still NOT MEASUREDIts dispatch calls
runSelfTest(), which wraps aselfTest()that returns a failurelist. The census picks an entry only when a file defines exactly one self-test-shaped
function or has an
ENTRY_BY_HANDrow; this file has two and no row, so it readsNOT MEASURED (ambiguous entry) both before and after — the handshake is real, the
instrument just cannot see it.
Ablated by hand instead, with the census's own
probeEarlyReturn:runSelfTest(what the dispatch actually calls): baseline exit 0, mutatedexit 1, head
✗ check-workspace-manifest-cycles self-test: runSelfTest() returned without reaching its verdict,— HELD.selfTest(what the census would pick if the ambiguity were resolved thewrong way): mutated exit 1 with a TypeError stack — an accident, not a handshake.
'scripts/check-workspace-manifest-cycles.mjs': 'runSelfTest'row thatwould make this measurable lives in
scripts/measure-self-test-floor.mjs, which isoutside this batch's declared file surface. It is not edited here; it is filed as
#14842 instead, with the two readings that say which entry the row must name.
Local verification
origin/mainmerged once more (a real merge, no rebase) before opening this PR. The gateunion was re-derived AFTER the final commit and run on that head,
61850abbf:node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands,DG_EXIT 0, 24 paths vs merge base
f3ae441fa, 47 families, every one run from--commands. Plus the dispatch-namedcheck:declared-population-live, theany-edit
check:nul-bytes, andscripts/pm/check-governed-queue-guard.mjs --self-test(that file is edited here and
governed-surface-guard.ymlruns it, but the derivationdid not list it).
node scripts/check-test-completeness.mjs— exit 3, PREREQUISITE NOT MET (it grades asaved
turbo run testlog and none exists locally). Never read as a pass.node scripts/check-engine-split-ratio.mjsfirst came back exit 2 on the shallowclone; after
git fetch --shallow-since=2026-05-29 originit is exit 0 with a realratio, so it counts as RAN-PASS.
pnpm check:pm-dispatch-gatesran to completion:dispatch-gates self-test: 1241 cases pass.pnpm buildexit 0.pnpm lintover the WHOLE repo, not narrowed: exit 0, 5779 files,0 errors, 0 warnings (counts read from
--format json).--self-testto the SAME exit code andbyte-identical output as before the change (captured before the codemod, again after,
then diffed). One file differs on one nondeterministic field:
release-rehearsal-clone.mjsprints an elapsed-milliseconds figure (115 msvs102 ms).pnpm check:nul-bytesexit 0, plus a direct control-byte sweep of the touched files:no match.
Every exit code above was captured by redirecting first and reading
$?after — neverthrough a pipe.
skip-changeset:scripts/**only, nothing published from any package.🤖 Generated with Claude Code
https://claude.ai/code/session_01LAwHpn4uVuf4N1geBcD5i3
Generated by Claude Code