Skip to content

Commit 9acddde

Browse files
claude[bot]claude
andauthored
Verdict handshake for 134 scripts/** self-tests that exit 0 on an early return (#14479)
* fix(devx): sentinel verdict handshake for 68 self-tests whose dispatch discards the result `scripts/**` gates spelled `if (--self-test) selfTest();` had no handshake: a `return` anywhere above the verdict printed nothing, evaluated no floor and still exited 0 — a self-test that never finished, reported as one that passed. Transplants the boundary-only shape landed on `check-self-test-wired.mjs`: a per-file `SELF_TEST_VERDICT` sentinel returned as the last statement of the self-test, after its verdict line, and compared at the dispatch. Nothing inside any self-test body changes. An unbraced branch body (`else if (c) selfTest();`) is braced first, so the multi-statement replacement cannot re-bind a following `else`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013Euac24FtaPN3AXf9uiUWA * fix(devx): verdict-flag handshake for 22 self-tests dispatched as `return selfTest()` Here the self-test's own return value reaches a caller, so the sentinel shape cannot be used without changing what the dispatch propagates. A module-level `selfTestReachedVerdict` is set only after the verdict prints and read between the call and the return, leaving the returned value exactly as it was. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013Euac24FtaPN3AXf9uiUWA * fix(devx): verdict-flag handshake for 20 self-tests dispatched as `process.exit(selfTest())` The shape that reads like a handshake and is not: an early bare `return` yields `undefined`, and `process.exit(undefined)` is exit 0. The self-test's numeric exit code is load-bearing, so it is captured and re-exited unchanged; the flag is read in between. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013Euac24FtaPN3AXf9uiUWA * fix(devx): verdict-flag handshake for 14 self-tests dispatched inside a ternary `process.exit(argv.includes('--self-test') ? selfTest() : main())` and the `const code = ... ? selfTest() : main(argv)` variant carry the same hole. The ternary is split so the self-test arm can be handshaked; the production arm keeps its exact expression and exit code. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013Euac24FtaPN3AXf9uiUWA * fix(devx): verdict-flag handshake for the remaining 10 self-test dispatches Nine gates whose dispatch discards the result but whose self-test ends in `return <code>` or `process.exit(...)`, so an appended sentinel return would be unreachable; the flag is set immediately before that terminal statement. Plus `check-durability-degradation-log-level.mjs`, whose dispatch calls TWO self-test entries and combines their statuses with `||`. An early return in either yields `undefined`, which that `||` reads as a pass, so each entry gets its own handshake rather than a wrapper. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013Euac24FtaPN3AXf9uiUWA * style(devx): align the wrapped dispatch blocks in 16 gates Braces were added around 51 unbraced branch bodies so the multi-statement handshake could not re-bind a following `else`. In the 16 whose dispatch was already nested, the block that moved inside them kept its old indentation and came out one level deep. Whitespace only — no statement moves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013Euac24FtaPN3AXf9uiUWA * fix(devx): declare the sentinel's type on check-test-typecheck's self-test `selfTest()` there is annotated `: void`, so returning the verdict sentinel added two raw tsc errors to the ROOT program and drifted the @objectstack/spec-monorepo DEBT entry 26 -> 28. The ledger is shrink-only and raising it is maintainer-only, so the errors are fixed instead: the annotation now says what the function returns. Root `tsc --noEmit` is back to 26. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013Euac24FtaPN3AXf9uiUWA --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent ab540ed commit 9acddde

134 files changed

Lines changed: 2494 additions & 133 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎scripts/ablation-dist-preflight.mjs‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,12 @@ function run(argv) {
551551
if (!v.ok || !tv.ok) process.exit(1);
552552
}
553553

554+
// Returned by `selfTest()` only after its verdict is printed. The dispatch
555+
// refuses anything else: a `return` that leaves the function above that line
556+
// prints nothing and still exits 0 — a self-test that never finished, reported
557+
// as one that passed (#13798).
558+
const SELF_TEST_VERDICT = 'ablation-dist-preflight self-test reached its verdict';
559+
554560
function selfTest() {
555561
const cases = [
556562
['missing dist is red', { mode: 'present', distExists: false, scanned: 0, codeHits: 0, mapHits: 0 }, false],
@@ -742,6 +748,8 @@ function selfTest() {
742748
process.exit(1);
743749
}
744750
console.log('✓ ablation-dist-preflight self-test: all cases pass.');
751+
752+
return SELF_TEST_VERDICT;
745753
}
746754

747755
const argv = process.argv.slice(2);
@@ -750,5 +758,14 @@ const invokedDirectly = isEntrypoint(import.meta.url);
750758

751759
if (!invokedDirectly) {
752760
// imported as a module — expose the exports and do nothing else
753-
} else if (argv.includes('--self-test')) selfTest();
761+
} else if (argv.includes('--self-test')) {
762+
if (selfTest() !== SELF_TEST_VERDICT) {
763+
console.error(
764+
'\n✗ ablation-dist-preflight self-test: selfTest() returned without reaching its verdict,\n'
765+
+ 'so no success line was printed. Exiting 0 here would report a self-test\n'
766+
+ 'that never finished as a self-test that passed.\n',
767+
);
768+
process.exit(1);
769+
}
770+
}
754771
else run(argv);

‎scripts/check-adr-0087-registration.mjs‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3224,6 +3224,12 @@ function auditStock(cwd, head) {
32243224
// real commits would be testing an imitation of the code path that ships.
32253225
// ---------------------------------------------------------------------------
32263226

3227+
// Returned by `selfTest()` only after its verdict is printed. The dispatch
3228+
// refuses anything else: a `return` that leaves the function above that line
3229+
// prints nothing and still exits 0 — a self-test that never finished, reported
3230+
// as one that passed (#13798).
3231+
const SELF_TEST_VERDICT = 'check-adr-0087-registration self-test reached its verdict';
3232+
32273233
function selfTest() {
32283234
const failures = [];
32293235
let checked = 0;
@@ -4898,6 +4904,8 @@ function selfTest() {
48984904
process.exit(1);
48994905
}
49004906
console.log(`✓ check-adr-0087-registration --self-test: ${checked} assertions over real temp git repos (real scan()/assertInputs() path)`);
4907+
4908+
return SELF_TEST_VERDICT;
49014909
}
49024910

49034911
// ---------------------------------------------------------------------------
@@ -4922,7 +4930,14 @@ if (isEntrypoint(import.meta.url)) {
49224930
};
49234931

49244932
if (argv.includes('--self-test')) {
4925-
selfTest();
4933+
if (selfTest() !== SELF_TEST_VERDICT) {
4934+
console.error(
4935+
'\n✗ check-adr-0087-registration self-test: selfTest() returned without reaching its verdict,\n'
4936+
+ 'so no success line was printed. Exiting 0 here would report a self-test\n'
4937+
+ 'that never finished as a self-test that passed.\n',
4938+
);
4939+
process.exit(1);
4940+
}
49264941
} else if (argv.includes('--list')) {
49274942
list(REPO_ROOT, 'HEAD');
49284943
} else if (argv.includes('--audit-stock')) {

‎scripts/check-adr-anchors.mjs‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -953,7 +953,24 @@ function ambiguousAnchorRefs(anchorList, allowlist) {
953953
return hits;
954954
}
955955

956-
if (process.argv.includes('--self-test')) selfTest();
956+
// Set by `selfTest()` only after its verdict is printed, and read at the
957+
// dispatch: a `return` that leaves the function above that line prints nothing
958+
// and still exits 0 — a self-test that never finished, reported as one that
959+
// passed (#13798). The self-test's own exit code stays load-bearing, so the
960+
// handshake is a flag rather than a returned sentinel.
961+
let selfTestReachedVerdict = false;
962+
963+
if (process.argv.includes('--self-test')) {
964+
selfTest();
965+
if (!selfTestReachedVerdict) {
966+
console.error(
967+
'\n✗ check-adr-anchors self-test: selfTest() returned without reaching its verdict,\n'
968+
+ 'so no success line was printed. Exiting 0 here would report a self-test\n'
969+
+ 'that never finished as a self-test that passed.\n',
970+
);
971+
process.exit(1);
972+
}
973+
}
957974

958975
let adrFiles;
959976
try {
@@ -1909,5 +1926,6 @@ function selfTest() {
19091926
`✓ check-adr-anchors --self-test: ${checked} assertions over the real auditAdrDirectory() / ` +
19101927
'auditCitedNumbers() / assembleAnchors() paths.',
19111928
);
1929+
selfTestReachedVerdict = true;
19121930
process.exit(0);
19131931
}

‎scripts/check-adr-links.mjs‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,12 @@ function assert(cond, message) {
365365
}
366366
}
367367

368+
// Returned by `selfTest()` only after its verdict is printed. The dispatch
369+
// refuses anything else: a `return` that leaves the function above that line
370+
// prints nothing and still exits 0 — a self-test that never finished, reported
371+
// as one that passed (#13798).
372+
const SELF_TEST_VERDICT = 'check-adr-links self-test reached its verdict';
373+
368374
function selfTest() {
369375
// 1. Discrimination: verbatim link shapes are invisible; prose ones are not.
370376
const doc = [
@@ -452,12 +458,23 @@ function selfTest() {
452458
);
453459

454460
console.log('✅ check-adr-links --self-test: discrimination, census, ADR-0046 pin and baseline staleness all verified');
461+
462+
return SELF_TEST_VERDICT;
455463
}
456464

457465
/* Run only when invoked as a program. The extractor is exported so a future
458466
* caller (or a REPL session chasing a false positive) can import it without the
459467
* import itself sweeping the repo. */
460468
if (isEntrypoint(import.meta.url)) {
461-
if (process.argv.includes('--self-test')) selfTest();
469+
if (process.argv.includes('--self-test')) {
470+
if (selfTest() !== SELF_TEST_VERDICT) {
471+
console.error(
472+
'\n✗ check-adr-links self-test: selfTest() returned without reaching its verdict,\n'
473+
+ 'so no success line was printed. Exiting 0 here would report a self-test\n'
474+
+ 'that never finished as a self-test that passed.\n',
475+
);
476+
process.exit(1);
477+
}
478+
}
462479
else runCheck();
463480
}

‎scripts/check-agent-model-declared.mjs‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,13 @@ function report(problems) {
325325
// Self-test — pins the RED paths so the gate cannot rot into a no-op.
326326
// ---------------------------------------------------------------------------
327327

328+
// Set by `selfTest()` only after its verdict is printed, and read at the
329+
// dispatch: a `return` that leaves the function above that line prints nothing
330+
// and still exits 0 — a self-test that never finished, reported as one that
331+
// passed (#13798). The self-test's own exit code stays load-bearing, so the
332+
// handshake is a flag rather than a returned sentinel.
333+
let selfTestReachedVerdict = false;
334+
328335
function selfTest() {
329336
const fm = (modelLine) =>
330337
`---\nname: demo\ndescription: >\n A demo agent.\n${modelLine ? `${modelLine}\n` : ''}---\n\nBody.\n`;
@@ -538,12 +545,24 @@ function selfTest() {
538545
process.exit(1);
539546
}
540547
console.log(`\n✓ check-agent-model-declared self-test: ${cases.length} cases pass.`);
548+
selfTestReachedVerdict = true;
541549
}
542550

543551
// ---------------------------------------------------------------------------
544552

545553
function main() {
546-
if (process.argv.includes('--self-test')) return selfTest();
554+
if (process.argv.includes('--self-test')) {
555+
const selfTestCode = selfTest();
556+
if (!selfTestReachedVerdict) {
557+
console.error(
558+
'\n✗ check-agent-model-declared self-test: selfTest() returned without reaching its verdict,\n'
559+
+ 'so no success line was printed. Exiting 0 here would report a self-test\n'
560+
+ 'that never finished as a self-test that passed.\n',
561+
);
562+
process.exit(1);
563+
}
564+
return selfTestCode;
565+
}
547566

548567
const { files, problems: layout } = readAgentFiles();
549568
const { problems, results } = runAllChecks(files, INHERIT_JUSTIFIED);

‎scripts/check-agent-test-spelling.mjs‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,13 @@ function baseFixtureFiles(extra = {}) {
811811
};
812812
}
813813

814+
// Set by `selfTest()` only after its verdict is printed, and read at the
815+
// dispatch: a `return` that leaves the function above that line prints nothing
816+
// and still exits 0 — a self-test that never finished, reported as one that
817+
// passed (#13798). The self-test's own exit code stays load-bearing, so the
818+
// handshake is a flag rather than a returned sentinel.
819+
let selfTestReachedVerdict = false;
820+
814821
function selfTest() {
815822
const failures = [];
816823
const t = (name, actual, expected) => {
@@ -1014,12 +1021,24 @@ function selfTest() {
10141021
return 1;
10151022
}
10161023
console.log('\n✓ check-agent-test-spelling --self-test: all cases pass');
1024+
selfTestReachedVerdict = true;
10171025
return 0;
10181026
}
10191027

10201028
if (isEntrypoint(import.meta.url)) {
10211029
const argv = process.argv.slice(2);
1022-
if (argv.includes('--self-test')) process.exit(selfTest());
1030+
if (argv.includes('--self-test')) {
1031+
const selfTestCode = selfTest();
1032+
if (!selfTestReachedVerdict) {
1033+
console.error(
1034+
'\n✗ check-agent-test-spelling self-test: selfTest() returned without reaching its verdict,\n'
1035+
+ 'so no success line was printed. Exiting 0 here would report a self-test\n'
1036+
+ 'that never finished as a self-test that passed.\n',
1037+
);
1038+
process.exit(1);
1039+
}
1040+
process.exit(selfTestCode);
1041+
}
10231042
else if (argv.includes('--list')) list();
10241043
else process.exit(run());
10251044
}

‎scripts/check-aggregator-roster.mjs‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,12 @@ async function main() {
373373

374374
// ── Self-test ───────────────────────────────────────────────────────────────
375375

376+
// Returned by `selfTest()` only after its verdict is printed. The dispatch
377+
// refuses anything else: a `return` that leaves the function above that line
378+
// prints nothing and still exits 0 — a self-test that never finished, reported
379+
// as one that passed (#13798).
380+
const SELF_TEST_VERDICT = 'check-aggregator-roster self-test reached its verdict';
381+
376382
async function selfTest() {
377383
const failures = [];
378384
let checked = 0;
@@ -594,13 +600,24 @@ async function selfTest() {
594600
`(baseline + a dropped member and a phantom needs: entry for each of the ${REQUIRED_AGGREGATORS.length} required aggregators + ` +
595601
`six refusals + the --leg cross-check + the malformed-roster and laundered-non-member pins + the CI wiring).`,
596602
);
603+
604+
return SELF_TEST_VERDICT;
597605
}
598606

599607
// The CLI dispatch is guarded so that IMPORTING this module is inert: `judge`
600608
// and `readWorkflows` are exported so another tree can be judged, and a module
601609
// that ran its gate on import would silently judge THIS repo instead and print
602610
// a verdict about the wrong subject (`check:entry-guard`).
603611
if (isEntrypoint(import.meta.url)) {
604-
if (process.argv.includes('--self-test')) await selfTest();
612+
if (process.argv.includes('--self-test')) {
613+
if ((await selfTest()) !== SELF_TEST_VERDICT) {
614+
console.error(
615+
'\n✗ check-aggregator-roster self-test: selfTest() returned without reaching its verdict,\n'
616+
+ 'so no success line was printed. Exiting 0 here would report a self-test\n'
617+
+ 'that never finished as a self-test that passed.\n',
618+
);
619+
process.exit(1);
620+
}
621+
}
605622
else await main();
606623
}

‎scripts/check-auth-mount-ledger.mjs‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,13 @@ const REAL_NOTE =
550550
'no SDK method builds this URL -- the sys_user unlock_user action posts it directly; ' +
551551
'platform-admin gated (ADR-0068)';
552552

553+
// Set by `selfTest()` only after its verdict is printed, and read at the
554+
// dispatch: a `return` that leaves the function above that line prints nothing
555+
// and still exits 0 — a self-test that never finished, reported as one that
556+
// passed (#13798). The self-test's own exit code stays load-bearing, so the
557+
// handshake is a flag rather than a returned sentinel.
558+
let selfTestReachedVerdict = false;
559+
553560
function selfTest() {
554561
const fail = [];
555562
let cases = 0;
@@ -820,6 +827,7 @@ function selfTest() {
820827
process.exit(1);
821828
}
822829
console.log(`check-auth-mount-ledger --self-test: ${cases} assertions OK (right boundary, lane exclusion, rationale, pending ratchet).`);
830+
selfTestReachedVerdict = true;
823831
}
824832

825833
// ---------------------------------------------------------------------------
@@ -834,7 +842,18 @@ function refuse(why) {
834842

835843
function main() {
836844
const argv = process.argv.slice(2);
837-
if (argv.includes('--self-test')) return selfTest();
845+
if (argv.includes('--self-test')) {
846+
const selfTestCode = selfTest();
847+
if (!selfTestReachedVerdict) {
848+
console.error(
849+
'\n✗ check-auth-mount-ledger self-test: selfTest() returned without reaching its verdict,\n'
850+
+ 'so no success line was printed. Exiting 0 here would report a self-test\n'
851+
+ 'that never finished as a self-test that passed.\n',
852+
);
853+
process.exit(1);
854+
}
855+
return selfTestCode;
856+
}
838857

839858
const mountAbs = join(ROOT, MOUNT_SOURCE);
840859
const ledgerAbs = join(ROOT, LEDGER_SOURCE);

‎scripts/check-bash32-floor.mjs‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,13 @@ function fixtureRepo(files) {
697697
return dir;
698698
}
699699

700+
// Set by `selfTest()` only after its verdict is printed, and read at the
701+
// dispatch: a `return` that leaves the function above that line prints nothing
702+
// and still exits 0 — a self-test that never finished, reported as one that
703+
// passed (#13798). The self-test's own exit code stays load-bearing, so the
704+
// handshake is a flag rather than a returned sentinel.
705+
let selfTestReachedVerdict = false;
706+
700707
function selfTest() {
701708
const SELF = fileURLToPath(import.meta.url);
702709
let failed = 0;
@@ -1097,12 +1104,24 @@ function selfTest() {
10971104
process.exit(1);
10981105
}
10991106
console.log(`\n✓ check-bash32-floor self-test: ${cases} cases pass.`);
1107+
selfTestReachedVerdict = true;
11001108
}
11011109

11021110
// ---------------------------------------------------------------------------
11031111

11041112
function main() {
1105-
if (process.argv.includes('--self-test')) return selfTest();
1113+
if (process.argv.includes('--self-test')) {
1114+
const selfTestCode = selfTest();
1115+
if (!selfTestReachedVerdict) {
1116+
console.error(
1117+
'\n✗ check-bash32-floor self-test: selfTest() returned without reaching its verdict,\n'
1118+
+ 'so no success line was printed. Exiting 0 here would report a self-test\n'
1119+
+ 'that never finished as a self-test that passed.\n',
1120+
);
1121+
process.exit(1);
1122+
}
1123+
return selfTestCode;
1124+
}
11061125

11071126
const rootFlag = process.argv.indexOf('--root');
11081127
const root = rootFlag === -1 ? REPO_ROOT : process.argv[rootFlag + 1];

‎scripts/check-changeset-no-major.mjs‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,12 @@ function main(argv) {
791791

792792
// ── Self-test ────────────────────────────────────────────────────────────────
793793

794+
// Returned by `selfTest()` only after its verdict is printed. The dispatch
795+
// refuses anything else: a `return` that leaves the function above that line
796+
// prints nothing and still exits 0 — a self-test that never finished, reported
797+
// as one that passed (#13798).
798+
const SELF_TEST_VERDICT = 'check-changeset-no-major self-test reached its verdict';
799+
794800
function selfTest() {
795801
const failures = [];
796802
let checked = 0;
@@ -1508,14 +1514,23 @@ function selfTest() {
15081514
`✓ check-changeset-no-major --self-test: ${checked} assertions ` +
15091515
'(frontmatter dialects measured against @changesets/parse + the pre/exit exemption switch in both directions + the #7005 diff scoping over real temp git repos + the #4690 pins + the wiring).',
15101516
);
1517+
1518+
return SELF_TEST_VERDICT;
15111519
}
15121520

15131521
// ── main ─────────────────────────────────────────────────────────────────────
15141522

15151523
const argv = process.argv.slice(2);
15161524

15171525
if (argv.includes('--self-test')) {
1518-
selfTest();
1526+
if (selfTest() !== SELF_TEST_VERDICT) {
1527+
console.error(
1528+
'\n✗ check-changeset-no-major self-test: selfTest() returned without reaching its verdict,\n'
1529+
+ 'so no success line was printed. Exiting 0 here would report a self-test\n'
1530+
+ 'that never finished as a self-test that passed.\n',
1531+
);
1532+
process.exit(1);
1533+
}
15191534
} else if (argv.includes('--list')) {
15201535
list();
15211536
} else {

0 commit comments

Comments
 (0)