@@ -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+ }
11431294audit ( ) ;
0 commit comments