Skip to content

Commit c70581b

Browse files
os-elon-muskclaude
andauthored
fix(spec): give four packages/spec self-tests a roster, a floor and a verdict handshake (#19076)
Refs #18919 Clause-②: no Tranche 1 of #18919 — **4 of the 12** gates under `packages/spec/scripts/` that dispatch on `--self-test` while carrying no battery roster, no floor and no verdict handshake. ⛔ This PR deliberately does **not** close the card; the card's own fence is 「⛔ 不主张一次全做完」 and the eight-file residue is named below so the next tranche needs no re-derivation. ## The hole reproduces inside this package — measured before any retrofit The card claims **absence of protection, ⛔ not existence of a defect**, and says #18512's readings are not these files' readings. So the first act was an ablation on one of the four, at the branch point `d4cb05cbf`, through `scripts/ablation-replace.mjs` (anchor must hit, write verified against the disk, restore proven): | | reading | |:--|:--| | file | `packages/spec/scripts/check-exported-any.ts` | | mutation | `['BareAny', 'InferredFromAnySchema']` → `['BareAny']` — one of the two RED-leg detection pins stops being reached | | anchor on disk | hits `1` → `0`; replacement `0` → `1` | | blob before | `7150b80a00ead8fecb8b13f08f27d2627332fb28` | | blob after | `3210b6b88928bf1baebc50cf520133a65133b2fe` | | what the gate then said | `✅ self-test: detects \`any\` types and \`any\`-output schemas, and nothing else.` — **byte-identical to the unmutated run** | | exit code | **0** | | restore | `git checkout HEAD -- PATH`: blob back to `7150b80a00ea…`, `git diff HEAD` empty | A second reading, on the file whose dispatch hands the self-test's return value straight to `process.exit()` — `check-error-code-provenance.ts`, same tool, same tree, in a throwaway worktree at `d4cb05cbf`: | | reading | |:--|:--| | mutation | `return 0;` as the first statement of `selfTest()` | | blob | `9e74797b6d2db68bb59bd9c9fd93ff81e9ef32e7` → `2245217ca8d41e3b46fb1634c7b99637dd9b351d` | | what the gate then said | **nothing — zero bytes** | | exit code | **0** | | restore | blob back to `9e74797b6d2d…`, `git diff HEAD` empty | So both holes the card names are real here, not inherited: a battery can shrink silently, and a `return` above the verdict prints nothing and passes. ## What the four carry now, with the lit control Predicate, re-established rather than quoted from the dispatch — `grep -c -E 'SELF_TEST_BATTERIES|SELF_TEST_BATTERY_FLOOR'` (roster) and `grep -c 'returned without reaching its verdict'` (handshake), matching lines: | file | roster before → after | handshake before → after | self-test verdict now | |:--|:--|:--|:--| | `check-exported-any.ts` | 0 → 10 | 0 → 1 | 14 case(s) across 5 batteries | | `check-dual-source-exports.ts` | 0 → 10 | 0 → 1 | 9 case(s) across 4 batteries | | `check-error-code-provenance.ts` | 0 → 10 | 0 → 1 | 13 case(s) across 5 batteries | | `check-browser-reachable-entries.ts` | 0 → 10 | 0 → 1 | 29 case(s) across 9 batteries | | **lit control** `scripts/check-test-typecheck.mts` (unchanged) | **10** | **1** | its own `--self-test` prints 11 semantic cases | The same instrument answers 10 / 1 on the TS precedent and answered 0 / 0 on all four targets, so the four zeros were readings and not a dead grep. The registered case counts are not derived from the roster — each is the number the run itself registered, and the roster is a literal the run is compared against. Every floor was **measured, not guessed**: the roster was first declared with a sentinel floor of 999 per battery and the run asked which batteries registered how many cases; the floors are those answers. That sentinel run is also a red-leg demonstration — the floor named all nine `check-browser-reachable-entries.ts` batteries and exited 1. ## Reverse verification: the same mutation now reds, and names the battery From the committed retrofit, the ablation that was silent-green above, re-run through the same tool: ``` ✗ self-test floor: self-test battery "the RED leg: an exported TYPE that IS `any` is flagged" registered 1 case(s), below its pinned floor of 2 — 1 case(s) that used to run no longer do. ✗ self-test floor: A battery below its floor means cases STOPPED RUNNING — the battery is the bug, not the number. … ``` blob `99c2fbe72538…` → `f92324cceba0…`, command exit **1**, restore proven (`blob == HEAD`, `git diff HEAD` empty). And the handshake, one leg per file — `return;` (`return 0;` for the one returning a number) injected as the first statement of `selfTest()`, each from the committed state, each restored with the blob equal to HEAD's and an empty `git diff HEAD`: | file | mutated blob | exit | first line printed | |:--|:--|:--|:--| | `check-exported-any.ts` | `d51735a53c12…` | 1 | `✗ check-exported-any self-test: selfTest() returned without reaching its verdict,` | | `check-dual-source-exports.ts` | `21bf4cbbaedf…` | 1 | `✗ check-dual-source-exports self-test: …without reaching its verdict,` | | `check-error-code-provenance.ts` | `d5bd8fe9c0e6…` | 1 | `✗ check-error-code-provenance self-test: …without reaching its verdict,` | | `check-browser-reachable-entries.ts` | `33dc39d561dc…` | 1 | `✗ check-browser-reachable-entries self-test: …without reaching its verdict,` | In three of the four that `return` did not even stop the process before this PR: `selfTest()` was typed `never` and the dispatch was a bare `if (SELF_TEST) selfTest();`, so control fell through to the real audit, which on a built tree prints its own green line and exits 0. ## The retrofit changes nothing about what these gates say about the tree Each file's audit path is **byte-identical** to the branch point (`sha256` over the region, taken from the two blobs): | file | region | lines | sha (base) | sha (head) | |:--|:--|--:|:--|:--| | `check-exported-any.ts` | `// ── Audit ─` → EOF | 69 | `74d2dbb87e8e5ec6` | `74d2dbb87e8e5ec6` | | `check-dual-source-exports.ts` | `// ── Audit ─` → EOF | 92 | `f95e248f6ce2a74d` | `f95e248f6ce2a74d` | | `check-error-code-provenance.ts` | file start → self-test banner | 368 | `8ad0283719c0f855` | `8ad0283719c0f855` | | `check-browser-reachable-entries.ts` | file start → self-test banner | 714 | `82c3e7ee5ab505f9` | `82c3e7ee5ab505f9` | The only change outside the self-test region is each file's dispatch block. All four real (non-self-test) runs are green after a `packages/spec` build, and say what they said before: ``` ✅ no exported type resolves to `any`: 2378 types + 1457 schemas across 17 entry points. ✅ no new dual-source exports: 5001 names across 17 entry points — 204 re-exported (single declaration), 0 accepted dual-source (baseline). OK — every registered-code stamp site is listed under its own owner key or carries a recorded waiver (10 waiver(s), all live) ✅ check:browser-reachable-entries — 2 declared browser-reachable entries link no zod; 44 bundle(s) scanned, 40 zod link(s) seen elsewhere (instrument calibrated); … ``` ## Checker health is not a PR verdict — and now their own health is falsifiable `scripts/pm/dispatch-gates.mjs --commands` already separates this class by name, printing a `--self-test`-only family as 「⚠ checker-health only (--self-test) — NOT a PR verdict」. The repo therefore already knew a self-test is not a verdict about the tree. What these four lacked is the other half: a self-test whose own *health* can be falsified. A roster with a floor makes a shrunk battery say so, and the handshake makes a silent return say so. Two design points worth naming, both copied from the landed precedents rather than invented: - **registration asserts REACH, not failure.** The registering call is the first statement of each file's assertion sink, before the outcome is consulted. Routed through the failure sink instead, a fully green run would register zero cases and every battery would read DID NOT RUN — the floor inverted rather than installed. - **the flag is the self-test's last statement**, after the verdict line prints and after the floor has been evaluated. A flag set before the floor would report "reached" for a run that never evaluated it. ⛔ Nothing is imported: each of the four still runs standalone as `tsx scripts/NAME.ts --self-test`, which is the whole point of the mechanism. ## Residue — the eight files this tranche does NOT touch Measured at this branch's head, all eight still answer roster `0` / handshake `0` on the predicates above: ``` packages/spec/scripts/build-declaration-map.ts packages/spec/scripts/build-export-origins.ts packages/spec/scripts/build-migration-registry.ts packages/spec/scripts/check-llms-txt.ts packages/spec/scripts/check-objectui-pin-citations.ts packages/spec/scripts/check-skill-examples.ts packages/spec/scripts/check-template-manifests.ts packages/spec/scripts/check-yaml-examples.ts ``` 12 today, not the card's 13: PR #18916 landed `check-duration-unit-keys.ts`, which now carries all three pieces (roster 10 / handshake 1) and is out of the population. ## Changeset No changeset — **this diff releases nothing**, and the exemption label is the gate's own prescription for that case. `@objectstack/spec` ships `dist`, `json-schema`, `liveness`, `prompts`, `llms.txt`, `README.md`, `src/**/*.zod.ts`, `CHANGELOG.md`, `api-surface`, `api-surface-declarations` and `spec-changes.json`; the four changed files live under `scripts/`, which is **not** in that list, so they reach no tarball and a named changeset would publish a version whose content is unchanged. This PR briefly carried a `patch` changeset because the dispatch told the author to write one without checking whether the package ships `scripts/` — that was the **seat's** error; the author measured the contradiction and flagged it in `open_questions` rather than resolving it silently, and the changeset was dropped in `0e8b6567a49e` (one file, −31 lines, nothing else moved). `skip-changeset` was then applied by the seat, a label write the author is forbidden to make, after checking the one case where that label is refused: ruling ② B on #18375 refuses it where a PR's `.changeset` rows are CHANGED, and this PR's diff against base carries **zero** `.changeset` rows of any status. ## Verification Head of this branch when these were taken: `fe05b5ae1`. | what | command | reading | |:--|:--|:--| | gate families | `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` | **63 derived from the real change set** (5 paths vs merge base `0ec81857a`), every one run, exit code captured before any pipe | | census | `… --ran RECORD` | `✓ 63 derived famil(ies) accounted for — 61 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)` | | **61** | — | exit **0** | | **2** | `pnpm check:dual-build-cjs-loads`, `pnpm check:lean-entry-closure` | exit **3 = PREREQUISITE NOT MET** — both read BUILT output of every package (83 packages have no `dist` in this container; only `packages/spec`'s closure was built). Their own `--self-test` legs passed inside the same run. ⛔ Recorded as not measured, not as a pass and not as a finding. | | build | `pnpm --filter '@objectstack/spec...' build --concurrency=2`, through `scripts/pm/os-verify-lock.sh` | `VERDICT command-exit 0` · held the lock 145s · waited 0s | | typecheck | `pnpm --filter @objectstack/spec typecheck` | exit 0 — `tsc --noEmit`, `check:scripts-typecheck` (this is the leg that compiles `scripts/**`) and `check:test-typecheck` | | package tests | `pnpm --filter @objectstack/spec test`, under the same lock | exit 0 — 491 files, **14299 tests** pass | | the two suites that drive these four scripts | `vitest run --maxWorkers=2 scripts/dist-freshness-adoption.test.ts scripts/check-error-code-provenance.test.ts` | exit 0 — 24 tests. They pin `--self-test` end to end: the spawned `--self-test` must exit 0 printing `self-test OK`, and both stale-dist cases require `--self-test` to still run and exit 0 on a dist it never reads. | | all four gates end to end | `pnpm --filter @objectstack/spec run check:{exported-any,dual-source-exports,error-code-provenance,browser-reachable-entries}` | exit 0 each — self-test then real audit | | lint | `pnpm lint` (`eslint . --no-inline-config`, the whole repo) | exit 0 — the full run, so **no narrowing to declare** | | control bytes | `grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'` over the four files, plus `pnpm check:nul-bytes` | no match; gate exit 0 | `origin/main` was merged into this branch before these readings (three commits, `scripts/check-closing-target-claim.mjs`, `scripts/measure-self-test-floor.mjs`, `scripts/pm/os-verify-lock.sh` — all disjoint from this diff, none `merge=os-regen`, no regeneration debt recorded). ## Acceptance notes `noted, not filed`: `scripts/measure-self-test-floor.mjs` — the repo's own self-test floor/handshake instrument — has a population of repo-root `scripts/**` only (`population()` walks `join(ROOT, 'scripts')`), so the twelve gates under `packages/spec/scripts/` that #18919 is about cannot be probed by it at all, in either direction. Nothing in `AGENTS.md` or that file's own contract promises that directory, so this is a scope gap rather than a violated contract; widening it is its own card and its own measurement. Next toucher: whoever dispatches tranche 2 of #18919, or whoever regenerates `docs/audits/2026-09-self-test-shape-census.md`. --- _Generated by [Claude Code](https://claude.ai/code)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent ab41a8f commit c70581b

4 files changed

Lines changed: 637 additions & 29 deletions

File tree

‎packages/spec/scripts/check-browser-reachable-entries.ts‎

Lines changed: 157 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -715,21 +715,99 @@ function audit(): never {
715715
// Self-test — the shapes, not the corpus
716716
// ---------------------------------------------------------------------------
717717

718+
// Set by `selfTest()` only after its verdict line prints, and read at the
719+
// dispatch at the foot of this file: a `return` that leaves the function above
720+
// that line prints nothing, and here it does not even stop — control falls
721+
// through to `audit()`, which on a built tree prints its own green line and
722+
// exits 0. A self-test that never finished, reported as one that passed.
723+
// ⛔ AN EXIT CODE IS NOT A HANDSHAKE; the flag is the thing an early return
724+
// cannot carry with it.
725+
let selfTestReachedVerdict = false;
726+
727+
// ── The self-test's own battery roster and floor ───────────────────────────
728+
//
729+
// `failures.length === 0` used to be this self-test's ONLY success condition,
730+
// and the verdict line names three mechanisms rather than a count, so "every
731+
// case held" and "the cases never ran" printed the same sentence. The shrink was
732+
// measured on the sibling `check-exported-any.ts`, whose self-test has the same
733+
// anatomy — deleting one name from a fixture name list de-registers that case
734+
// and the run still prints its verdict byte-identically and still exits 0.
735+
//
736+
// Closed the way `scripts/check-agent-model-declared.mjs` and its TypeScript
737+
// ports (`scripts/check-test-typecheck.mts`, `check-duration-unit-keys.ts` in
738+
// this directory) closed it — COPIED and ⛔ never imported, because every
739+
// self-test has to keep running standalone as
740+
// `tsx scripts/check-browser-reachable-entries.ts --self-test`, and a shared
741+
// assertion module would be one point of failure for every instrument at once.
742+
// What is pinned is the registered NAMES, not a number.
743+
//
744+
// A BATTERY HERE IS A SECTION — the `// ──` groups this self-test was already
745+
// written in. Each opens with `battery('<name>')` and every `check()` after it
746+
// is attributed to that name until the next one opens, so a section that stops
747+
// running names ITSELF at the floor rather than going quiet.
748+
//
749+
// ⛔ A pinned TOTAL is not the repair — the refusal battery falling from 10
750+
// cases to 1 keeps a total "right" the moment a sibling grows — and ⛔ neither
751+
// is a roster DERIVED from the run: a count taken from the cases that ran can
752+
// never notice one that stopped.
753+
//
754+
// The counts are a FLOOR, not an equality: adding cases is ordinary work and
755+
// must not red. A battery BELOW its floor means cases stopped running.
756+
const SELF_TEST_BATTERIES: Readonly<Record<string, number>> = Object.freeze({
757+
'the scanner FINDS real specifiers': 1,
758+
'the scanner does NOT fabricate specifiers out of prose or lookalikes': 1,
759+
'the walk: transitive over relative hops, both verdicts': 4,
760+
'the refusal: an unbuilt or stale tree is NOT MEASURED': 10,
761+
'the reconciliation: both directions': 4,
762+
'target resolution reads BOTH conditions': 1,
763+
"the browser condition's targets, and only when declared (#11072)": 4,
764+
'what counts as Node-only (#11072)': 2,
765+
'the node-only verdict over a walked graph, BOTH directions (#11072)': 2,
766+
});
767+
768+
// DELETING an entry silences that battery's floor exactly as effectively as
769+
// zeroing it, so the roster's own size is pinned too.
770+
const SELF_TEST_BATTERY_FLOOR = 9;
771+
772+
// The key a case is filed under when no battery is open. It is not a declared
773+
// battery, so it reds by the same set difference rather than silently inflating
774+
// whichever battery happened to open last.
775+
const UNATTRIBUTED_BATTERY = '(no battery open)';
776+
718777
/**
719778
* A green run over today's build proves only what today's build contains, and
720779
* two of the things this gate must do cannot be exercised by it at all: the tree
721780
* has ZERO relative hops between bundles (every entry is self-contained), so the
722781
* transitive walk — the part that keeps the gate honest the day `splitting` is
723782
* turned on — would ship never having run. These fixtures are the contract.
724783
*/
725-
function selfTest(): never {
784+
function selfTest(): void {
726785
const failures: string[] = [];
786+
// The battery ledger this self-test's floor is evaluated against.
787+
// `battery()` opens a battery; every `check()` below is attributed to the one
788+
// most recently opened.
789+
//
790+
// Registration is the FIRST statement of `check()`, before the line is
791+
// printed and before `failures` moves, because the floor asserts REACH: a
792+
// case that runs and FAILS still registers, and only a case that never runs
793+
// at all goes missing from the ledger. Routing registration through the
794+
// failure sink instead would register a case only when it failed — a fully
795+
// green run would register 0 and every battery would read DID NOT RUN, the
796+
// floor inverted rather than installed.
797+
const seen = new Map<string, number>();
798+
let openBattery: string | undefined;
799+
const battery = (name: string): void => {
800+
openBattery = name;
801+
};
727802
const check = (name: string, ok: boolean, detail = ''): void => {
803+
const attributedTo = openBattery ?? UNATTRIBUTED_BATTERY;
804+
seen.set(attributedTo, (seen.get(attributedTo) ?? 0) + 1);
728805
if (!ok) failures.push(`${name}${detail ? ` — ${detail}` : ''}`);
729806
console.log(` ${ok ? '✓' : '✗'} ${name}${!ok && detail ? ` — ${detail}` : ''}`);
730807
};
731808

732809
// ── The scanner: it must FIND real specifiers … ──────────────────────────
810+
battery('the scanner FINDS real specifiers');
733811
const real = [
734812
`import { z } from 'zod';`,
735813
`export { a } from "./chunk-A.mjs";`,
@@ -746,6 +824,7 @@ function selfTest(): never {
746824
);
747825

748826
// ── … and it must NOT fabricate them out of prose or lookalikes ──────────
827+
battery('the scanner does NOT fabricate specifiers out of prose or lookalikes');
749828
// The first case is REAL text from dist/index.mjs: a documentation string that
750829
// contains a complete import statement. A naive grep reports it as a link.
751830
const ghost = [
@@ -764,6 +843,7 @@ function selfTest(): never {
764843
);
765844

766845
// ── The walk: transitive over relative hops, both verdicts ───────────────
846+
battery('the walk: transitive over relative hops, both verdicts');
767847
const tmp = mkdtempSync(join(tmpdir(), 'os-browser-reachable-'));
768848
try {
769849
const dist = join(tmp, 'dist');
@@ -813,6 +893,7 @@ function selfTest(): never {
813893
}
814894

815895
// ── The refusal: an unbuilt or stale tree is NOT MEASURED ────────────────
896+
battery('the refusal: an unbuilt or stale tree is NOT MEASURED');
816897
// Driven here rather than against this workspace, because the only way to
817898
// observe the refusal on the real tree is to break the real tree. Both
818899
// verdicts are pinned: a guard only ever seen green cannot be told apart from
@@ -998,6 +1079,7 @@ function selfTest(): never {
9981079
}
9991080

10001081
// ── The reconciliation: both directions ──────────────────────────────────
1082+
battery('the reconciliation: both directions');
10011083
const emptyLedger = (): Ledger => ({ browserReachable: {}, unjudged: [], notAModule: [] });
10021084

10031085
let problems: string[] = [];
@@ -1033,6 +1115,7 @@ function selfTest(): never {
10331115
check('accepts a fully classified exports map', problems.length === 0, JSON.stringify(problems));
10341116

10351117
// ── Target resolution reads BOTH conditions ──────────────────────────────
1118+
battery('target resolution reads BOTH conditions');
10361119
const targets = targetsOf({
10371120
import: { types: './dist/x/index.d.mts', default: './dist/x/index.mjs' },
10381121
require: { types: './dist/x/index.d.ts', default: './dist/x/index.js' },
@@ -1044,6 +1127,7 @@ function selfTest(): never {
10441127
);
10451128

10461129
// ── #11072: the browser condition's targets, and only when declared ──────
1130+
battery("the browser condition's targets, and only when declared (#11072)");
10471131
const conditioned = {
10481132
browser: {
10491133
import: { types: './dist/x/index.d.mts', default: './dist/browser/x/index.mjs' },
@@ -1077,6 +1161,7 @@ function selfTest(): never {
10771161
);
10781162

10791163
// ── #11072: what counts as Node-only ─────────────────────────────────────
1164+
battery('what counts as Node-only (#11072)');
10801165
const nodeOnly = ['fs', 'node:fs', 'fs/promises', 'node:path', 'pg-connection-string',
10811166
'pg-connection-string/index.js'];
10821167
const browserSafe = ['zod', 'zod/v4', './chunk.mjs', 'ai'];
@@ -1092,6 +1177,7 @@ function selfTest(): never {
10921177
);
10931178

10941179
// ── #11072: the node-only verdict over a walked graph, BOTH directions ───
1180+
battery('the node-only verdict over a walked graph, BOTH directions (#11072)');
10951181
const nodeOnlyTmp = mkdtempSync(join(tmpdir(), 'os-browser-reachable-nodeonly-'));
10961182
try {
10971183
const dist = join(nodeOnlyTmp, 'dist');
@@ -1131,13 +1217,78 @@ function selfTest(): never {
11311217
rmSync(nodeOnlyTmp, { recursive: true, force: true });
11321218
}
11331219

1134-
if (failures.length) {
1135-
console.error(`\n✗ self-test: ${failures.length} case(s) failed.`);
1220+
// ── The floor: every declared battery RAN, and ran its cases ─────────────
1221+
//
1222+
// Evaluated after every battery has had its chance and BEFORE the verdict, so
1223+
// the success line below can only be printed by a run in which the set of
1224+
// batteries that registered EQUALS the set declared, each at or above its own
1225+
// count. A set difference names WHICH battery stopped; a count says only that
1226+
// something did — and, before this block existed, not even that.
1227+
const floorProblems: string[] = [];
1228+
const declaredBatteries = Object.keys(SELF_TEST_BATTERIES);
1229+
if (declaredBatteries.length < SELF_TEST_BATTERY_FLOOR) {
1230+
floorProblems.push(
1231+
`SELF_TEST_BATTERIES declares ${declaredBatteries.length} batteries, below the pinned ` +
1232+
`${SELF_TEST_BATTERY_FLOOR} — a battery deleted from the roster takes its own floor with it.`,
1233+
);
1234+
}
1235+
for (const [name, count] of seen) {
1236+
if (declaredBatteries.includes(name)) continue;
1237+
floorProblems.push(
1238+
`self-test battery "${name}" registered ${count} case(s) but is not declared in ` +
1239+
'SELF_TEST_BATTERIES — a case attributed to no declared battery is one nothing floors.',
1240+
);
1241+
}
1242+
for (const name of declaredBatteries) {
1243+
const count = seen.get(name) ?? 0;
1244+
if (count >= SELF_TEST_BATTERIES[name]) continue;
1245+
floorProblems.push(
1246+
count === 0
1247+
? `self-test battery "${name}" DID NOT RUN — 0 cases registered, ${SELF_TEST_BATTERIES[name]} pinned. ` +
1248+
'The verdict below would have claimed those cases hold.'
1249+
: `self-test battery "${name}" registered ${count} case(s), below its pinned floor of ` +
1250+
`${SELF_TEST_BATTERIES[name]} — ${SELF_TEST_BATTERIES[name] - count} case(s) that used to run no longer do.`,
1251+
);
1252+
}
1253+
if (floorProblems.length > 0) {
1254+
for (const problem of floorProblems) console.error(`✗ self-test floor: ${problem}`);
1255+
console.error(
1256+
'✗ self-test floor: A battery below its floor means cases STOPPED RUNNING — the battery is the ' +
1257+
'bug, not the number. Find what stopped registering (a deleted case, a fixture the cases are ' +
1258+
'built from that no longer exists, a guard that now skips) and restore it.',
1259+
);
1260+
}
1261+
1262+
if (failures.length || floorProblems.length) {
1263+
if (failures.length) console.error(`\n✗ self-test: ${failures.length} case(s) failed.`);
11361264
process.exit(1);
11371265
}
1138-
console.log('✅ self-test: scanner, transitive walk and ledger reconciliation all behave.');
1139-
process.exit(0);
1266+
// The count is printed because a reader had to hand-tally the checkmarks to
1267+
// get one, and it is printed AFTER the floor rather than instead of it: the
1268+
// number is evidence, the floor is the proof.
1269+
const registered = [...seen.values()].reduce((a, b) => a + b, 0);
1270+
console.log(
1271+
'✅ self-test: scanner, transitive walk and ledger reconciliation all behave — ' +
1272+
`${registered} case(s) across ${declaredBatteries.length} batteries, every battery at or above ` +
1273+
'its pinned floor.',
1274+
);
1275+
selfTestReachedVerdict = true;
11401276
}
11411277

1142-
if (SELF_TEST) selfTest();
1278+
if (SELF_TEST) {
1279+
selfTest();
1280+
// The handshake. Without it a `return` above the verdict prints nothing and
1281+
// does not even stop: control reaches `audit()` below, which on a built tree
1282+
// prints its own green line and exits 0 — a self-test that never finished,
1283+
// reported as one that passed.
1284+
if (!selfTestReachedVerdict) {
1285+
console.error(
1286+
'\n✗ check-browser-reachable-entries self-test: selfTest() returned without reaching its verdict,\n' +
1287+
'so no verdict line was printed. Exiting 0 here would report a self-test that never\n' +
1288+
'finished as a self-test that passed.\n',
1289+
);
1290+
process.exit(1);
1291+
}
1292+
process.exit(0);
1293+
}
11431294
audit();

0 commit comments

Comments
 (0)