7777//
7878// `rest-server.ts` is pinned below at its STATIC reading: 80 `routeManager`
7979// call sites, 17 registrars, 19 sites inside the one mintable registrar, 61
80- // outside. A RUNTIME census — construct `RestServer` against a recording
80+ // outside. ⭐ [#15542] Those 80 are counted across TWO spellings since the
81+ // per-item family gained a switch-carrying helper — 72 direct
82+ // `this.routeManager.register(` sites plus 8 `registerPerItemRoute(` calls, the
83+ // helper's own forwarding call excluded so it is not counted twice. The total
84+ // did not move; the rule had to learn the second spelling to keep reading it.
85+ // A RUNTIME census — construct `RestServer` against a recording
8186// `RouteManager` and a protocol implementing every optional capability, then
8287// call each registrar — reads 85 / 17 / 19 / 66 instead. Both are correct and
8388// the delta is fully explained: `registerApprovalsEndpoints` builds 12 routes
@@ -312,7 +317,47 @@ export const PROBE_FILE_CENSUS: readonly ProbeFileReading[] = [
312317 population : 80 ,
313318 reachable : 19 ,
314319 blindSpot : 61 ,
315- populationRule : '`this.routeManager.register(` call sites; reachable = those inside registerMetadataEndpoints' ,
320+ populationRule :
321+ 'route registration sites — `this.routeManager.register(` call sites, LESS the one inside ' +
322+ '`registerPerItemRoute` (the shared forwarder, not a route), PLUS `registerPerItemRoute(` call sites; ' +
323+ 'reachable = those inside registerMetadataEndpoints' ,
324+ // [#15542 / #15854] ⭐ THE POPULATION RULE LEARNED A SECOND SPELLING, and
325+ // the numbers it produces did NOT move: 80 / 19 / 61, exactly as before.
326+ //
327+ // WHAT MOVED IN THE SOURCE. The per-item family's later members (`PUT`,
328+ // `DELETE`, `/history`, `/audit`, `/diff`, `/published`, `/publish`,
329+ // `/rollback`) stopped being direct `this.routeManager.register(` call
330+ // sites and became `registerPerItemRoute(` calls — a local helper carrying
331+ // the `endpoints.item` switch. Net -7 on the old one-spelling reading: 8
332+ // sites left that spelling and the helper's own forwarding call added 1
333+ // back. So the old rule read population 73 / reachable 12.
334+ //
335+ // ⛔ 73 / 12 WAS NOT RE-RECORDED, and the reason is this file's whole
336+ // purpose. Those 8 routes are still mounted and still registered exactly
337+ // where they were; only the spelling of the call changed. Writing 73 down
338+ // would have ratified a population 7 short of the real one and encoded a
339+ // 7-route hole in the very blind-spot count this census exists to keep
340+ // honest — the failure it is named for, committed by its own record.
341+ //
342+ // ⚠️ REACHABILITY WAS CHECKED BEFORE THE COUNT WAS WIDENED, because if the
343+ // helper HID those routes from the probe the repair would belong in
344+ // `rest-server.ts` and not here. It does not. `registerPerItemRoute` reads
345+ // `this.routeManager` at CALL time and every one of its 8 call sites is
346+ // lexically inside `registerMetadataEndpointsInner`, which
347+ // `registerMetadataEndpoints` runs with `this.routeManager` swapped to the
348+ // anonymous-deny `guardedRouteManager` and restored in a `finally`. So a
349+ // helper-routed registration goes through the identical wrapping the 11
350+ // remaining direct sites in that registrar do, and the umbrella key
351+ // `meta:rest-server.ts:registerMetadataEndpoints` covers it unchanged.
352+ // Pinned at runtime rather than argued from source:
353+ // `packages/rest/src/rest-meta-auth.test.ts` drives an anonymous
354+ // `GET {meta}/:type/:name/history` — a helper-routed route — to 401.
355+ //
356+ // The decomposition, so the two halves stay legible: 72 direct route
357+ // registrations + 8 helper-routed = 80 population; 11 + 8 = 19 reachable.
358+ // `this.routeManager.register(` reads 73 because the helper's forwarder is
359+ // one of them, and it is sliced out before counting.
360+ //
316361 // [#13214] `enforceAuth` 61 -> 64. ⛔ RE-ANCHORED, not relaxed: the control
317362 // exists to prove this census is still reading the file it thinks it is, and
318363 // a rising `enforceAuth` is precisely what the 2026-08-30 ruling on #13214
@@ -329,7 +374,21 @@ export const PROBE_FILE_CENSUS: readonly ProbeFileReading[] = [
329374 // 80, `reachable` 19, `private register*Endpoints(` 17 and
330375 // `this.routeManager.register(` 80 are all unchanged — #13214 added no route
331376 // and no registrar. `blindSpot` therefore stays 61 as well.
332- controls : { 'private register*Endpoints(' : 17 , 'this.routeManager.register(' : 80 , enforceAuth : 64 } ,
377+ // ⚠️ That last figure is the reading AS OF #13214 and is left as written:
378+ // the control is 73 today for the spelling reason recorded above, and the
379+ // population it feeds is still 80. Do not "correct" the paragraph — it is a
380+ // dated measurement, not a live claim.
381+ controls : {
382+ 'private register*Endpoints(' : 17 ,
383+ 'this.routeManager.register(' : 73 ,
384+ // Both halves of the new rule carry their own control, so neither can go
385+ // silently to zero: a helper deleted and its routes inlined back would
386+ // still read population 80, and only these two controls would notice the
387+ // shape moved and force this provenance to be re-read.
388+ 'registerPerItemRoute(' : 8 ,
389+ 'const registerPerItemRoute =' : 1 ,
390+ enforceAuth : 64 ,
391+ } ,
333392 note :
334393 'The single non-tripwire probe names ONE registrar of 17. The other 16 can never mint a key: ' +
335394 'registerCrudEndpoints, registerApprovalsEndpoints, registerDataActionEndpoints, registerReportsEndpoints, ' +
@@ -560,21 +619,57 @@ export function deriveProbeFileCensus(): {
560619 }
561620
562621 // ── rest-server.ts ──────────────────────────────────────────────────────
622+ //
623+ // [#15542 / #15854] TWO SPELLINGS, ONE POPULATION. A route registration in
624+ // this file is EITHER a direct `this.routeManager.register(` call site OR a
625+ // `registerPerItemRoute(` call — the local helper the per-item family's later
626+ // members go through, which carries the `endpoints.item` switch and forwards
627+ // to `this.routeManager.register(entry)`. Both are registrations; counting
628+ // only the first spelling reads 7 short.
629+ //
630+ // ⛔ The helper's OWN forwarding call is NOT a registration site — it is the
631+ // one shared mechanism 8 sites go through — so its body is sliced out before
632+ // counting. Counting it would double-count every helper-routed route.
563633 {
564634 const src = read ( 'packages/rest/src/rest-server.ts' ) ;
565635 const registrarRe = / ^ \s * p r i v a t e \s + r e g i s t e r [ A - Z a - z ] * E n d p o i n t s \s * \( / gm;
566636 const mountRe = / t h i s \. r o u t e M a n a g e r \. r e g i s t e r \( / g;
637+ // Matches the CALL sites only. The declaration reads
638+ // `const registerPerItemRoute = (` and the docblock mentions read
639+ // `{@link registerPerItemRoute}` — in neither is the name followed by `(`.
640+ const helperCallRe = / r e g i s t e r P e r I t e m R o u t e \( / g;
641+ const helperDeclRe = / c o n s t \s + r e g i s t e r P e r I t e m R o u t e \s * = / ;
642+
643+ /**
644+ * Registration sites in one haystack: direct call sites, LESS the helper's
645+ * own forwarding call, PLUS the helper's call sites.
646+ *
647+ * ⛔ Fail-loud, like the ledger marker slice above: a helper declaration
648+ * that moves out of this shape slices to '' and nothing is subtracted, so
649+ * the reading comes out ONE HIGH (81 / 20) and this census goes RED. It
650+ * never silently shrinks — a quietly narrower rule is the failure mode the
651+ * whole file is built against.
652+ */
653+ const sites = ( hay : string ) : number => {
654+ const at = hay . search ( helperDeclRe ) ;
655+ const stop = at < 0 ? - 1 : hay . indexOf ( '\n };' , at ) ;
656+ const forwarder = at < 0 || stop < 0 ? '' : hay . slice ( at , stop ) ;
657+ return occurrences ( hay , mountRe ) - occurrences ( forwarder , mountRe ) + occurrences ( hay , helperCallRe ) ;
658+ } ;
659+
567660 // Slice the mintable registrar's body: from its declaration to the next one.
568661 const decls = [ ...src . matchAll ( registrarRe ) ] . map ( ( m ) => ( { at : m . index ?? 0 , text : m [ 0 ] } ) ) ;
569662 const metaIdx = decls . findIndex ( ( d ) => d . text . includes ( 'registerMetadataEndpoints' ) ) ;
570663 const start = decls [ metaIdx ] ?. at ?? 0 ;
571664 const end = decls [ metaIdx + 1 ] ?. at ?? src . length ;
572665 files . set ( 'packages/rest/src/rest-server.ts' , {
573- population : occurrences ( src , mountRe ) ,
574- reachable : occurrences ( src . slice ( start , end ) , / t h i s \. r o u t e M a n a g e r \. r e g i s t e r \( / g ) ,
666+ population : sites ( src ) ,
667+ reachable : sites ( src . slice ( start , end ) ) ,
575668 controls : {
576669 'private register*Endpoints(' : occurrences ( src , / p r i v a t e \s + r e g i s t e r [ A - Z a - z ] * E n d p o i n t s \s * \( / g) ,
577670 'this.routeManager.register(' : occurrences ( src , / t h i s \. r o u t e M a n a g e r \. r e g i s t e r \( / g) ,
671+ 'registerPerItemRoute(' : occurrences ( src , / r e g i s t e r P e r I t e m R o u t e \( / g) ,
672+ 'const registerPerItemRoute =' : occurrences ( src , / c o n s t \s + r e g i s t e r P e r I t e m R o u t e \s * = / g) ,
578673 enforceAuth : occurrences ( src , / e n f o r c e A u t h / g) ,
579674 } ,
580675 } ) ;
0 commit comments