@@ -551,13 +551,72 @@ function run(argv) {
551551 if ( ! v . ok || ! tv . ok ) process . exit ( 1 ) ;
552552}
553553
554+ // -- The self-test's own battery roster and floor (#13489) ------------------
555+ //
556+ // A pass used to be this self-test's ONLY success condition, so "every case
557+ // held" and "the cases never ran" printed the same line. Closed the way
558+ // PR #13487 validated on check-doc-authoring: what is pinned is the registered
559+ // NAMES, not a number. Every section opens with `battery('<name>')`, every
560+ // assertion is attributed to the battery most recently opened, and the floor
561+ // requires the OPENED set to equal the DECLARED set with each battery at or
562+ // above its own count.
563+ //
564+ // The counts are a FLOOR, not an equality -- adding cases is ordinary work and
565+ // must not red. A battery BELOW its floor means cases stopped running; the
566+ // remedy is to find what stopped registering.
567+ const SELF_TEST_BATTERIES = Object . freeze ( {
568+ 'whole-tree accounting: the pure table' : 26 ,
569+ 'porcelain parsing' : 3 ,
570+ 'whole-tree accounting: a real git tree' : 7 ,
571+ } ) ;
572+
573+ // DELETING an entry silences that battery's floor exactly as effectively as
574+ // zeroing it, so the roster's own size is pinned too.
575+ const SELF_TEST_BATTERY_FLOOR = 3 ;
576+
577+ // The key an assertion is filed under when no battery is open. It is not a
578+ // declared battery, so it reds by the same set difference rather than silently
579+ // inflating whichever battery happened to run last.
580+ const UNATTRIBUTED_BATTERY = '(no battery open)' ;
581+
554582// Returned by `selfTest()` only after its verdict is printed. The dispatch
555583// refuses anything else: a `return` that leaves the function above that line
556584// prints nothing and still exits 0 — a self-test that never finished, reported
557585// as one that passed (#13798).
558586const SELF_TEST_VERDICT = 'ablation-dist-preflight self-test reached its verdict' ;
559587
560588function selfTest ( ) {
589+ // The battery ledger this self-test's floor is evaluated against (#13489).
590+ // `battery()` opens a battery; every assertion below is attributed to the one
591+ // most recently opened, so a section that stops running stops registering and
592+ // names ITSELF at the floor rather than going quiet.
593+ const batterySeen = new Map ( ) ;
594+ let openBattery = null ;
595+ const battery = ( name ) => {
596+ openBattery = name ;
597+ } ;
598+ const registerCase = ( ) => {
599+ const b = openBattery ?? UNATTRIBUTED_BATTERY ;
600+ batterySeen . set ( b , ( batterySeen . get ( b ) ?? 0 ) + 1 ) ;
601+ } ;
602+ // The one in-body assertion helper the 6 inline `failed += 1` sites now route
603+ // through. Each site already had a ✓ branch and a ✗ branch; both are kept
604+ // verbatim, and the only change is that a case is COUNTED either way — which
605+ // is what lets the floor below tell "held" from "never ran".
606+ const check = ( label , ok , detail = '' ) => {
607+ registerCase ( ) ;
608+ if ( ok ) {
609+ console . log ( ` ✓ ${ label } ` ) ;
610+ return ;
611+ }
612+ console . error ( ` ✗ ${ label } ${ detail } ` ) ;
613+ failed += 1 ;
614+ } ;
615+ // Cases run before the first banner, so the first battery is opened at the
616+ // top of the body and that banner carries no second opener — PR #13487's own
617+ // shape, as batches 1b and 2 landed it.
618+ battery ( 'whole-tree accounting: the pure table' ) ;
619+ let failed = 0 ;
561620 const cases = [
562621 [ 'missing dist is red' , { mode : 'present' , distExists : false , scanned : 0 , codeHits : 0 , mapHits : 0 } , false ] ,
563622 [ 'empty dist is red, not a skip' , { mode : 'present' , distExists : true , scanned : 0 , codeHits : 0 , mapHits : 0 } , false ] ,
@@ -570,15 +629,9 @@ function selfTest() {
570629 [ 'absent mode: still in code is red' , { mode : 'absent' , distExists : true , scanned : 9 , codeHits : 3 , mapHits : 0 } , false ] ,
571630 [ 'absent mode: missing dist still red' , { mode : 'absent' , distExists : false , scanned : 0 , codeHits : 0 , mapHits : 0 } , false ] ,
572631 ] ;
573- let failed = 0 ;
574632 for ( const [ label , input , expected ] of cases ) {
575633 const got = verdict ( input ) . ok ;
576- if ( got !== expected ) {
577- console . error ( ` ✗ ${ label } : expected ok=${ expected } , got ok=${ got } ` ) ;
578- failed += 1 ;
579- } else {
580- console . log ( ` ✓ ${ label } ` ) ;
581- }
634+ check ( label , got === expected , `: expected ok=${ expected } , got ok=${ got } ` ) ;
582635 }
583636
584637 // Filesystem leg: a real dist tree where the marker lives only in a sourcemap
@@ -597,13 +650,7 @@ function selfTest() {
597650 [ 'scan classifies a map-only token as a map hit' , mapOnly . codeHits . length === 0 && mapOnly . mapHits . length === 1 ] ,
598651 [ 'map-only scan is judged RED' , verdict ( { mode : 'present' , distExists : true , scanned : mapOnly . scanned , codeHits : 0 , mapHits : mapOnly . mapHits . length } ) . ok === false ] ,
599652 ] ;
600- for ( const [ label , ok ] of checks ) {
601- if ( ok ) console . log ( ` ✓ ${ label } ` ) ;
602- else {
603- console . error ( ` ✗ ${ label } ` ) ;
604- failed += 1 ;
605- }
606- }
653+ for ( const [ label , ok ] of checks ) check ( label , ok ) ;
607654 } finally {
608655 rmSync ( tmp , { recursive : true , force : true } ) ;
609656 }
@@ -631,12 +678,11 @@ function selfTest() {
631678 ] ;
632679 for ( const [ label , input , expectedOk , expectedLeg ] of treeCases ) {
633680 const got = treeVerdict ( input ) ;
634- if ( got . ok !== expectedOk || got . leg !== expectedLeg ) {
635- console . error ( ` ✗ ${ label } : expected ok=${ expectedOk } leg=${ expectedLeg } , got ok=${ got . ok } leg=${ got . leg } ` ) ;
636- failed += 1 ;
637- } else {
638- console . log ( ` ✓ ${ label } ` ) ;
639- }
681+ check (
682+ label ,
683+ got . ok === expectedOk && got . leg === expectedLeg ,
684+ `: expected ok=${ expectedOk } leg=${ expectedLeg } , got ok=${ got . ok } leg=${ got . leg } ` ,
685+ ) ;
640686 }
641687
642688 // A red restore leg must NAME the leaked path -- a refusal that does not say
@@ -645,14 +691,11 @@ function selfTest() {
645691 {
646692 const red = treeVerdict ( { mode : 'absent' , gitReadable : true , files : [ f ( 'packages/spec/authorable-surface/data.json' , false , true ) ] } ) ;
647693 const named = red . paths . includes ( 'packages/spec/authorable-surface/data.json' ) ;
648- if ( named ) console . log ( ' ✓ a red restore leg names the leaked path' ) ;
649- else {
650- console . error ( ' ✗ a red restore leg names the leaked path' ) ;
651- failed += 1 ;
652- }
694+ check ( 'a red restore leg names the leaked path' , named ) ;
653695 }
654696
655697 // ---- porcelain parsing ---------------------------------------------------
698+ battery ( 'porcelain parsing' ) ;
656699 {
657700 const Z = String . fromCharCode ( 0 ) ;
658701 const parsed = parsePorcelainZ ( [ ' M packages/spec/authorable-surface/data.json' , '?? scratch note.txt' , 'R new/name.ts' , 'old/name.ts' , '' ] . join ( Z ) ) ;
@@ -661,16 +704,11 @@ function selfTest() {
661704 [ 'parses an untracked path holding a space, unquoted' , parsed [ 1 ] ?. path === 'scratch note.txt' && parsed [ 1 ] ?. untracked === true ] ,
662705 [ 'consumes a rename origin record instead of listing it' , parsed . length === 3 && parsed [ 2 ] ?. path === 'new/name.ts' ] ,
663706 ] ;
664- for ( const [ label , ok ] of checks ) {
665- if ( ok ) console . log ( ` ✓ ${ label } ` ) ;
666- else {
667- console . error ( ` ✗ ${ label } ` ) ;
668- failed += 1 ;
669- }
670- }
707+ for ( const [ label , ok ] of checks ) check ( label , ok ) ;
671708 }
672709
673710 // ---- whole-tree accounting: a real git tree -------------------------------
711+ battery ( 'whole-tree accounting: a real git tree' ) ;
674712 // The pure table cannot catch a broken `git status` read or a broken
675713 // HEAD-vs-worktree marker probe, and those are the wires that make the
676714 // verdict mean anything. This leg replays the measured incident end to end.
@@ -732,17 +770,60 @@ function selfTest() {
732770 [ 'git leg: a deleted guard is a mutate leg, not a restore leg' , deleteLeg . ok === true && deleteLeg . leg === 'mutate' ] ,
733771 [ 'git leg: an untracked path reds the restore leg' , untrackedLeg . ok === false && untrackedLeg . paths . includes ( 'scratch.txt' ) ] ,
734772 ] ;
735- for ( const [ label , ok ] of gitChecks ) {
736- if ( ok ) console . log ( ` ✓ ${ label } ` ) ;
737- else {
738- console . error ( ` ✗ ${ label } ` ) ;
739- failed += 1 ;
740- }
741- }
773+ for ( const [ label , ok ] of gitChecks ) check ( label , ok ) ;
742774 } finally {
743775 rmSync ( repo , { recursive : true , force : true } ) ;
744776 }
745777
778+ // -- The floor: every declared battery RAN, and ran its cases (#13489) -----
779+ //
780+ // Evaluated after every battery has had its chance and BEFORE the verdict, so
781+ // the success line below can only be printed by a run in which the set of
782+ // batteries that registered assertions EQUALS the set declared. A set
783+ // difference names WHICH battery stopped; a count says only that something did.
784+ const floorMessages = [ ] ;
785+ const floorFailure = ( message ) => { floorMessages . push ( message ) ; } ;
786+ const declaredBatteries = Object . keys ( SELF_TEST_BATTERIES ) ;
787+ let floorBreached = false ;
788+ if ( declaredBatteries . length < SELF_TEST_BATTERY_FLOOR ) {
789+ floorBreached = true ;
790+ floorFailure (
791+ `SELF_TEST_BATTERIES declares ${ declaredBatteries . length } batteries, below the pinned `
792+ + `${ SELF_TEST_BATTERY_FLOOR } — a battery deleted from the roster takes its own floor with it.` ,
793+ ) ;
794+ }
795+ for ( const [ name , count ] of batterySeen ) {
796+ if ( declaredBatteries . includes ( name ) ) continue ;
797+ floorBreached = true ;
798+ floorFailure (
799+ `self-test battery "${ name } " registered ${ count } case(s) but is not declared in `
800+ + 'SELF_TEST_BATTERIES — an assertion attributed to no declared battery is one nothing floors.' ,
801+ ) ;
802+ }
803+ for ( const name of declaredBatteries ) {
804+ const count = batterySeen . get ( name ) ?? 0 ;
805+ if ( count >= SELF_TEST_BATTERIES [ name ] ) continue ;
806+ floorBreached = true ;
807+ floorFailure (
808+ count === 0
809+ ? `self-test battery "${ name } " DID NOT RUN — 0 cases registered, ${ SELF_TEST_BATTERIES [ name ] } pinned. `
810+ + 'The verdict below would have claimed those cases hold.'
811+ : `self-test battery "${ name } " registered ${ count } case(s), below its pinned floor of `
812+ + `${ SELF_TEST_BATTERIES [ name ] } — cases that used to run no longer do.` ,
813+ ) ;
814+ }
815+ if ( floorBreached ) {
816+ floorFailure (
817+ 'A battery at or below its floor means cases STOPPED RUNNING — the battery is the bug, not the '
818+ + 'number. Find what stopped registering (an early return, a deleted block, a guard that now '
819+ + 'skips) and restore it.' ,
820+ ) ;
821+ }
822+ for ( const m of floorMessages ) {
823+ console . error ( ` ✗ ${ m } ` ) ;
824+ failed += 1 ;
825+ }
826+
746827 if ( failed > 0 ) {
747828 console . error ( `✗ ablation-dist-preflight self-test: ${ failed } case(s) failed.` ) ;
748829 process . exit ( 1 ) ;
0 commit comments