@@ -693,75 +693,167 @@ const AUDIT_CONTROLS = [
693693// handshake is a flag rather than a returned sentinel.
694694let selfTestReachedVerdict = false ;
695695
696+ // -- The self-test's own battery roster and floor (#13489) ------------------
697+ //
698+ // Reaching the verdict used to be this self-test's ONLY success condition, so
699+ // "every control held" and "the controls never ran" printed the same line.
700+ // Closed the way PR #13487 validated on check-doc-authoring: what is pinned is
701+ // the registered NAMES, not a number.
702+ //
703+ // This file's assertion sites are `if (...) problems.push(...)` -- a
704+ // FAILURE-ONLY sink. Routing `problems.push` itself through `registerCase()`
705+ // would register a case only when a control FAILS: a fully green run would
706+ // register 0 and the battery would read DID NOT RUN, the floor inverted rather
707+ // than installed. So the sites are counted by the `check(() => { ... })` THUNK
708+ // PR #15198 measured: the existing `if (...) problems.push(...)` is carried in
709+ // VERBATIM, no condition is touched, and registration happens whether or not
710+ // the site fires.
711+ //
712+ // This file declares ONE battery, opened at the top of the self-test body: it
713+ // carries ZERO named section banners, fewer than the two the sectioning
714+ // criterion needs, and a comment is NOT promoted to a section head. The hoisted
715+ // single battery is the shape PR #14896, PR #15003 and PR #15217 landed.
716+ //
717+ // ⭐ ONE site stays OUTSIDE the roster, deliberately: the `catch` arm of the
718+ // AUDIT_CONTROLS loop (`audit control threw: ...`) ends in `continue`, which is
719+ // illegal inside the thunk's arrow function. Wrapping it would mean rewriting
720+ // that control flow -- exactly what the verbatim rule forbids. Its sibling in
721+ // the same loop body, `if (!ac.expect(result))`, IS floored, so a loop that
722+ // stops running still reds. This is the residue recipe A's non-table
723+ // assertions carry too (PR #15286's `extra` call).
724+ //
725+ // The count is a FLOOR, not an equality -- adding controls is ordinary work and
726+ // must not red. A battery BELOW its floor means cases stopped running; the
727+ // remedy is to find what stopped registering, never to lower the number. Four
728+ // of the thirteen floored sites sit inside `for` loops, so the count is the
729+ // number of times a site is REACHED on a run, measured on a run.
730+ const SELF_TEST_BATTERIES = Object . freeze ( {
731+ 'measure-position-name-fold-census self-test' : 24 ,
732+ } ) ;
733+
734+ // DELETING an entry silences that battery's floor exactly as effectively as
735+ // zeroing it, so the roster's own size is pinned too.
736+ const SELF_TEST_BATTERY_FLOOR = 1 ;
737+
738+ // The key an assertion is filed under when no battery is open. It is not a
739+ // declared battery, so it reds by the same set difference rather than silently
740+ // inflating whichever battery happened to run last.
741+ const UNATTRIBUTED_BATTERY = '(no battery open)' ;
742+
696743function selfTest ( { quiet = false } = { } ) {
744+ // The battery ledger this self-test's floor is evaluated against (#13489).
745+ // `battery()` opens a battery; every site below is attributed to the one most
746+ // recently opened, so a section that stops running stops registering and
747+ // names ITSELF at the floor rather than going quiet.
748+ const batterySeen = new Map ( ) ;
749+ let openBattery = null ;
750+ const battery = ( name ) => {
751+ openBattery = name ;
752+ } ;
753+ const registerCase = ( ) => {
754+ const b = openBattery ?? UNATTRIBUTED_BATTERY ;
755+ batterySeen . set ( b , ( batterySeen . get ( b ) ?? 0 ) + 1 ) ;
756+ } ;
757+ battery ( 'measure-position-name-fold-census self-test' ) ;
758+ // The thunk: it registers the case and then runs the existing site VERBATIM,
759+ // so no control's condition is inverted or rewritten and the `problems` sink
760+ // keeps its own semantics.
761+ const check = ( fn ) => {
762+ registerCase ( ) ;
763+ fn ( ) ;
764+ } ;
697765 const problems = [ ] ;
698766 const c = census ( ) ;
699767
700768 for ( const name of CONTROLS . builtinPositions ) {
701- if ( ! c . positions . has ( name ) ) {
702- problems . push ( `parse-integrity: built-in POSITION \`${ name } \` was not found. The scanner has drifted`
703- + ' off the declaration shape; every number below it would be an under-count.' ) ;
704- }
769+ check ( ( ) => {
770+ if ( ! c . positions . has ( name ) ) {
771+ problems . push ( `parse-integrity: built-in POSITION \`${ name } \` was not found. The scanner has drifted`
772+ + ' off the declaration shape; every number below it would be an under-count.' ) ;
773+ }
774+ } ) ;
705775 }
706776 for ( const name of CONTROLS . builtinPermissionSets ) {
707- if ( ! c . permissionSets . has ( name ) ) {
708- problems . push ( `parse-integrity: built-in PERMISSION SET \`${ name } \` was not found. Same failure, other axis.` ) ;
709- }
777+ check ( ( ) => {
778+ if ( ! c . permissionSets . has ( name ) ) {
779+ problems . push ( `parse-integrity: built-in PERMISSION SET \`${ name } \` was not found. Same failure, other axis.` ) ;
780+ }
781+ } ) ;
710782 }
711783
712784 const a = CONTROLS . junctionAnchor ;
713- if ( ! c . bindings . some ( ( b ) => b . position === a . position && b . set === a . set ) ) {
714- problems . push ( `positive control (junction) NOT classified as a junction binding: `
715- + `${ a . position } -> ${ a . set } \n ${ a . why } ` ) ;
716- }
717- if ( c . nameFolds . some ( ( f ) => f . name === a . position ) ) {
718- problems . push ( `positive control (junction) was reported as a NAME-DEPENDENCY: ${ a . position } \n ${ a . why } ` ) ;
719- }
785+ check ( ( ) => {
786+ if ( ! c . bindings . some ( ( b ) => b . position === a . position && b . set === a . set ) ) {
787+ problems . push ( `positive control (junction) NOT classified as a junction binding: `
788+ + `${ a . position } -> ${ a . set } \n ${ a . why } ` ) ;
789+ }
790+ } ) ;
791+ check ( ( ) => {
792+ if ( c . nameFolds . some ( ( f ) => f . name === a . position ) ) {
793+ problems . push ( `positive control (junction) was reported as a NAME-DEPENDENCY: ${ a . position } \n ${ a . why } ` ) ;
794+ }
795+ } ) ;
720796
721797 const b = CONTROLS . nameFoldAnchor ;
722798 const fold = c . nameFolds . find ( ( f ) => f . name === b . name ) ;
723- if ( ! fold ) {
724- problems . push ( `positive control (name-fold) NOT detected: ${ b . name } \n ${ b . why } ` ) ;
725- } else if ( ! fold . permissionSets . some ( ( s ) => s . kind === 'artifact' ) ) {
726- problems . push ( `positive control (name-fold) ${ b . name } was detected, but its permission-set half is not`
727- + ' the composed artifact -- the control is passing for the wrong reason.' ) ;
728- }
799+ check ( ( ) => {
800+ if ( ! fold ) {
801+ problems . push ( `positive control (name-fold) NOT detected: ${ b . name } \n ${ b . why } ` ) ;
802+ } else if ( ! fold . permissionSets . some ( ( s ) => s . kind === 'artifact' ) ) {
803+ problems . push ( `positive control (name-fold) ${ b . name } was detected, but its permission-set half is not`
804+ + ' the composed artifact -- the control is passing for the wrong reason.' ) ;
805+ }
806+ } ) ;
729807
730808 const n = CONTROLS . negative ;
731- if ( c . nameFolds . some ( ( f ) => f . name === n . position ) ) {
732- problems . push ( `negative control reported as a name-fold: ${ n . position } \n ${ n . why } ` ) ;
733- }
734- if ( ! c . bindings . some ( ( x ) => x . position === n . position && x . set === n . set ) ) {
735- problems . push ( `negative control ${ n . position } -> ${ n . set } was not seen as a junction binding at all;`
736- + ' the binder scan is blind, so every junction-bound pair would be misfiled as unbound.' ) ;
737- }
809+ check ( ( ) => {
810+ if ( c . nameFolds . some ( ( f ) => f . name === n . position ) ) {
811+ problems . push ( `negative control reported as a name-fold: ${ n . position } \n ${ n . why } ` ) ;
812+ }
813+ } ) ;
814+ check ( ( ) => {
815+ if ( ! c . bindings . some ( ( x ) => x . position === n . position && x . set === n . set ) ) {
816+ problems . push ( `negative control ${ n . position } -> ${ n . set } was not seen as a junction binding at all;`
817+ + ' the binder scan is blind, so every junction-bound pair would be misfiled as unbound.' ) ;
818+ }
819+ } ) ;
738820
739821 const sb = CONTROLS . secondBinder ;
740- if ( ! c . bindings . some ( ( x ) => x . position === sb . position && x . set === sb . set ) ) {
741- problems . push ( `second-binder control missed: \`${ sb . position } -> ${ sb . set } \` is a junction binding declared by`
742- + ' a binder whose list is NOT named POSITION_PERMISSION_SET_BINDINGS. The scan is recognising one app\'s'
743- + ' spelling again, so other apps\' junction rows are invisible and their positions read as inert.' ) ;
744- }
745- if ( c . inert . some ( ( i ) => i . name === sb . position ) ) {
746- problems . push ( `second-binder control: \`${ sb . position } \` was reported INERT while a binder binds it.` ) ;
747- }
748- if ( ! c . nameFolds . some ( ( f ) => f . name === 'sales_manager' ) ) {
749- problems . push ( 'a position junction-bound to a DIFFERENT set must still count as a name-fold on its own name'
750- + ' (sales_manager is bound to crm_sales_user and still folds onto the HotCRM `sales_manager` set).'
751- + ' Losing this would tell 要点 2 the pair is already governed.' ) ;
752- }
822+ check ( ( ) => {
823+ if ( ! c . bindings . some ( ( x ) => x . position === sb . position && x . set === sb . set ) ) {
824+ problems . push ( `second-binder control missed: \`${ sb . position } -> ${ sb . set } \` is a junction binding declared by`
825+ + ' a binder whose list is NOT named POSITION_PERMISSION_SET_BINDINGS. The scan is recognising one app\'s'
826+ + ' spelling again, so other apps\' junction rows are invisible and their positions read as inert.' ) ;
827+ }
828+ } ) ;
829+ check ( ( ) => {
830+ if ( c . inert . some ( ( i ) => i . name === sb . position ) ) {
831+ problems . push ( `second-binder control: \`${ sb . position } \` was reported INERT while a binder binds it.` ) ;
832+ }
833+ } ) ;
834+ check ( ( ) => {
835+ if ( ! c . nameFolds . some ( ( f ) => f . name === 'sales_manager' ) ) {
836+ problems . push ( 'a position junction-bound to a DIFFERENT set must still count as a name-fold on its own name'
837+ + ' (sales_manager is bound to crm_sales_user and still folds onto the HotCRM `sales_manager` set).'
838+ + ' Losing this would tell 要点 2 the pair is already governed.' ) ;
839+ }
840+ } ) ;
753841
754842 const bleed = CONTROLS . isDefaultBleed ;
755- if ( ! c . bindings . some ( ( x ) => x . position === 'everyone' && x . set === bleed . bound ) ) {
756- problems . push ( `regression control: \`${ bleed . bound } \` declares \`isDefault: true\` and must appear as an`
757- + ' `everyone` auto-bind. It does not, so the isDefault scan has stopped seeing declarations it owns.' ) ;
758- }
759- for ( const set of bleed . unbound ) {
760- if ( c . bindings . some ( ( x ) => x . position === 'everyone' && x . set === set ) ) {
761- problems . push ( `regression control fired again: \`everyone -> ${ set } \` was reported as a junction binding,`
762- + ` but ${ set } declares no \`isDefault\`. The isDefault look-ahead is reading past the declaration's own`
763- + ' object literal again -- see CONTROLS.isDefaultBleed.' ) ;
843+ check ( ( ) => {
844+ if ( ! c . bindings . some ( ( x ) => x . position === 'everyone' && x . set === bleed . bound ) ) {
845+ problems . push ( `regression control: \`${ bleed . bound } \` declares \`isDefault: true\` and must appear as an`
846+ + ' `everyone` auto-bind. It does not, so the isDefault scan has stopped seeing declarations it owns.' ) ;
764847 }
848+ } ) ;
849+ for ( const set of bleed . unbound ) {
850+ check ( ( ) => {
851+ if ( c . bindings . some ( ( x ) => x . position === 'everyone' && x . set === set ) ) {
852+ problems . push ( `regression control fired again: \`everyone -> ${ set } \` was reported as a junction binding,`
853+ + ` but ${ set } declares no \`isDefault\`. The isDefault look-ahead is reading past the declaration's own`
854+ + ' object literal again -- see CONTROLS.isDefaultBleed.' ) ;
855+ }
856+ } ) ;
765857 }
766858
767859 for ( const ac of AUDIT_CONTROLS ) {
@@ -772,9 +864,11 @@ function selfTest({ quiet = false } = {}) {
772864 problems . push ( `audit control threw: ${ ac . label } -- ${ e ?. message } ` ) ;
773865 continue ;
774866 }
775- if ( ! ac . expect ( result ) ) {
776- problems . push ( `audit control FAILED: ${ ac . label } \n got: ${ JSON . stringify ( result ) } ` ) ;
777- }
867+ check ( ( ) => {
868+ if ( ! ac . expect ( result ) ) {
869+ problems . push ( `audit control FAILED: ${ ac . label } \n got: ${ JSON . stringify ( result ) } ` ) ;
870+ }
871+ } ) ;
778872 }
779873
780874 if ( problems . length > 0 ) {
@@ -783,6 +877,53 @@ function selfTest({ quiet = false } = {}) {
783877 return 1 ;
784878 }
785879 if ( ! quiet ) {
880+ // -- The floor: the declared battery RAN, and ran its cases (#13489) ----
881+ //
882+ // Evaluated after every site has had its chance and BEFORE the verdict, so
883+ // the success line below can only be printed by a run in which the set of
884+ // batteries that registered cases EQUALS the set declared.
885+ //
886+ // ⭐ It lives INSIDE the `!quiet` branch on purpose. `main()` gates the
887+ // PRODUCTION path on `selfTest({ quiet: true })`, which prints no verdict
888+ // and therefore makes no claim for a floor to guard; evaluating the floor
889+ // there would let a roster edit change what a census run outputs. The floor
890+ // belongs to the `--self-test` verdict, which is the line that would
891+ // otherwise claim controls hold that never ran.
892+ //
893+ // It reports in this file's own idiom -- a stderr block and a non-zero
894+ // return -- and returns BEFORE `selfTestReachedVerdict` is set, so a breach
895+ // reds through the #13798 handshake as well as through the exit code.
896+ const floorFailures = [ ] ;
897+ const declaredBatteries = Object . keys ( SELF_TEST_BATTERIES ) ;
898+ if ( declaredBatteries . length < SELF_TEST_BATTERY_FLOOR ) {
899+ floorFailures . push (
900+ `SELF_TEST_BATTERIES declares ${ declaredBatteries . length } batteries, below the pinned `
901+ + `${ SELF_TEST_BATTERY_FLOOR } -- a battery deleted from the roster takes its own floor with it.` ,
902+ ) ;
903+ }
904+ for ( const [ name , count ] of batterySeen ) {
905+ if ( declaredBatteries . includes ( name ) ) continue ;
906+ floorFailures . push (
907+ `self-test battery "${ name } " registered ${ count } case(s) but is not declared in `
908+ + 'SELF_TEST_BATTERIES -- a case attributed to no declared battery is one nothing floors.' ,
909+ ) ;
910+ }
911+ for ( const name of declaredBatteries ) {
912+ const count = batterySeen . get ( name ) ?? 0 ;
913+ if ( count >= SELF_TEST_BATTERIES [ name ] ) continue ;
914+ floorFailures . push (
915+ count === 0
916+ ? `self-test battery "${ name } " DID NOT RUN -- 0 cases registered, ${ SELF_TEST_BATTERIES [ name ] } pinned. `
917+ + 'The verdict below would have claimed those controls hold.'
918+ : `self-test battery "${ name } " registered ${ count } case(s), below its pinned floor of `
919+ + `${ SELF_TEST_BATTERIES [ name ] } -- cases that used to run no longer do.` ,
920+ ) ;
921+ }
922+ if ( floorFailures . length > 0 ) {
923+ process . stderr . write ( `x measure-position-name-fold-census self-test floor (${ floorFailures . length } breach(es))\n\n${
924+ floorFailures . map ( ( f ) => ` - ${ f } ` ) . join ( '\n\n' ) } \n\n A battery at or below its floor means cases STOPPED RUNNING -- the battery is the bug, not the number. Find what stopped registering (an early return, a deleted block, a guard that now skips) and restore it.\n\n`) ;
925+ return 1 ;
926+ }
786927 process . stdout . write (
787928 `✓ measure-position-name-fold-census self-test: junction anchor (${ a . position } -> ${ a . set } ) classified as`
788929 + ` a junction binding and absent from the name-dependency list; name-fold anchor (${ b . name } ) detected`
0 commit comments