@@ -352,14 +352,18 @@ describe('i18n-extract ↔ translatePage walk parity (#13109)', () => {
352352 // Both directions, named separately so a failure says WHICH half broke.
353353 expect ( { offeredButIgnored : [ ...offered ] . filter ( ( id ) => ! applied . has ( id ) ) . sort ( ) } )
354354 . toEqual ( { offeredButIgnored : [ ] } ) ;
355- // The ONE standing exception, pre-dating this card and deliberate: a
356- // region-level `page:header`'s copy is offered under `pages.PAGE.title` /
357- // `.subtitle` instead, because emitting it here too would offer one string
358- // under two keys. The resolver still honours the id route for it, so it
359- // shows up as applied-not-offered — listed explicitly rather than filtered
360- // out of the fixture, so the exception stays visible and bounded to one id.
355+ // NO standing exception any more. This list used to read `['hdr']` — a
356+ // region-level `page:header` carrying an id, whose copy the extractor
357+ // deliberately offers under `pages.PAGE.title` / `.subtitle` while the
358+ // resolver went on honouring the id route for it and PREFERRING it. That
359+ // was the second half of the failure pair `walkAddressedPageComponents`
360+ // exists to prevent (the resolver reading an id the extractor omits), and
361+ // the 2026-09-06 ruling (decision batch #58) closed it by making the
362+ // page-name route canonical for this component. Empty in BOTH directions
363+ // is now the invariant; an entry reappearing here is a regression, not an
364+ // exception to document.
361365 expect ( { appliedButNotOffered : [ ...applied ] . filter ( ( id ) => ! offered . has ( id ) ) . sort ( ) } )
362- . toEqual ( { appliedButNotOffered : [ 'hdr' ] } ) ;
366+ . toEqual ( { appliedButNotOffered : [ ] } ) ;
363367 } ) ;
364368
365369 it ( 'pins the two sets by name, so a shape that stops being reachable is visible' , ( ) => {
@@ -369,11 +373,141 @@ describe('i18n-extract ↔ translatePage walk parity (#13109)', () => {
369373 ] ) ;
370374 // `card_body_child`, `card_footer_child`, `tab_child` and `slot_child` are
371375 // absent from BOTH sides — the shapes `translatePage` does not descend.
376+ // `hdr` — the region-level `page:header` — is absent from BOTH sides since
377+ // the ruling. `nested_header` stays: a `page:header` inside a container is
378+ // reached by the id route only, so the id key is the only key it has.
372379 expect ( [ ...idsResolverApplies ( page ) ] . sort ( ) ) . toEqual ( [
373- 'card' , 'hdr' , ' inner_flex', 'kpi_1' , 'kpi_deep' , 'kpi_label' , 'nested_header' , 'region_metric' ,
380+ 'card' , 'inner_flex' , 'kpi_1' , 'kpi_deep' , 'kpi_label' , 'nested_header' , 'region_metric' ,
374381 ] ) ;
375382 } ) ;
376383
384+ // ── The ruled invariant, pinned directly (decision batch #58, 2026-09-06) ──
385+ //
386+ // Maintainer 「同意」, option 1, verbatim: "The page-name route is canonical
387+ // for a region-level `page:header`. `translatePage` stops reading the id
388+ // route (`pages.PAGE.components.HEADERID.*`) for a `page:header` at region
389+ // level; a `page:header` nested inside a container stays id-only, as its doc
390+ // already says. One component, one address — `title` and `subtitle` now
391+ // follow the same rule."
392+ //
393+ // The set comparisons above measure `title` alone, because that is the key
394+ // both fixtures carry. This case walks the WHOLE shared key list and asserts
395+ // BOTH verbs of the failure pair on one component, in one place: the id key
396+ // is neither OFFERED nor READ. It also pins the three things that must NOT
397+ // move with it — the page-name route still translates the header, a nested
398+ // `page:header` is still id-addressed, and a bundle carrying both routes
399+ // resolves to the page-name one — so a regression that simply stops
400+ // translating region-level headers cannot pass here either.
401+ const headerRoutePage = ( ) : Record < string , any > => ( {
402+ name : 'header_route_page' ,
403+ regions : [
404+ {
405+ name : 'top' ,
406+ components : [
407+ // Region level, WITH an id — the shape the card measured (hotcrm's
408+ // five headers all carry one).
409+ { id : 'home_header' , type : 'page:header' , properties : { title : 'Sales Home' , subtitle : 'Welcome back' } } ,
410+ ] ,
411+ } ,
412+ {
413+ name : 'main' ,
414+ components : [
415+ {
416+ id : 'wrap' ,
417+ type : 'page:card' ,
418+ properties : {
419+ title : 'Wrap' ,
420+ children : [
421+ // Nested — the page-name route does not reach it, so the id
422+ // route is the ONLY route it has. Unchanged by the ruling.
423+ { id : 'inner_header' , type : 'page:header' , properties : { title : 'Inner header' } } ,
424+ ] ,
425+ } ,
426+ } ,
427+ ] ,
428+ } ,
429+ ] ,
430+ } ) ;
431+
432+ /** Every key of the shared list under one id, sentinel-valued. */
433+ const everyKeyFor = ( id : string ) : Record < string , string > =>
434+ Object . fromEntries ( PAGE_COMPONENT_COPY_KEYS . map ( ( k ) => [ k , `ID::${ id } ::${ k } ` ] ) ) ;
435+
436+ it ( 'neither offers nor reads the id key for a region-level `page:header` (batch #58)' , ( ) => {
437+ const page = headerRoutePage ( ) ;
438+
439+ // Half one — the extractor offers NOTHING under `components.home_header`,
440+ // for any key of the shared list.
441+ expect ( componentRows ( page ) . filter ( ( r ) => r . key . startsWith ( 'home_header.' ) ) ) . toEqual ( [ ] ) ;
442+
443+ // Half two — the resolver reads nothing there either. The bundle offers
444+ // every key of the shared list under that id; a sentinel reaching the
445+ // output would mean the id route is still live for this component.
446+ const idBundle = {
447+ en : {
448+ pages : {
449+ header_route_page : {
450+ components : { home_header : everyKeyFor ( 'home_header' ) , wrap : everyKeyFor ( 'wrap' ) } ,
451+ } ,
452+ } ,
453+ } ,
454+ } as any ;
455+ const viaId = translatePage ( page as any , idBundle , { locale : 'en' } ) ;
456+ const header = viaId . regions [ 0 ] . components [ 0 ] ;
457+ expect ( header . properties ) . toEqual ( { title : 'Sales Home' , subtitle : 'Welcome back' } ) ;
458+ expect ( header . label ) . toBeUndefined ( ) ;
459+ // Positive control for that empty: the SAME bundle shape does reach a
460+ // component the id route serves, so the two assertions above are a reading
461+ // and not an inert fixture.
462+ expect ( viaId . regions [ 1 ] . components [ 0 ] . properties . title ) . toEqual ( 'ID::wrap::title' ) ;
463+
464+ // Half three — the page-name route still translates the header. The fix is
465+ // "one address", not "no address".
466+ const nameBundle = {
467+ en : { pages : { header_route_page : { title : 'BY-NAME' , subtitle : 'SUB-BY-NAME' } } } ,
468+ } as any ;
469+ const viaName = translatePage ( page as any , nameBundle , { locale : 'en' } ) ;
470+ expect ( viaName . regions [ 0 ] . components [ 0 ] . properties )
471+ . toEqual ( { title : 'BY-NAME' , subtitle : 'SUB-BY-NAME' } ) ;
472+ // ...and it stops at region level: the nested header keeps its literal.
473+ expect ( viaName . regions [ 1 ] . components [ 0 ] . properties . children [ 0 ] . properties . title )
474+ . toEqual ( 'Inner header' ) ;
475+
476+ // Half four — a nested `page:header` is still reached, by the id route.
477+ const nestedBundle = {
478+ en : {
479+ pages : {
480+ header_route_page : { title : 'BY-NAME' , components : { inner_header : { title : 'ID::inner' } } } ,
481+ } ,
482+ } ,
483+ } as any ;
484+ const viaNested = translatePage ( page as any , nestedBundle , { locale : 'en' } ) ;
485+ expect ( viaNested . regions [ 1 ] . components [ 0 ] . properties . children [ 0 ] . properties . title )
486+ . toEqual ( 'ID::inner' ) ;
487+ // The extractor offers that nested id, which is the other half of "stays
488+ // id-only" — the two sides agree about it as much as about the region one.
489+ expect ( componentRows ( page ) . filter ( ( r ) => r . key === 'inner_header.title' ) )
490+ . toEqual ( [ { key : 'inner_header.title' , value : 'Inner header' } ] ) ;
491+
492+ // Half five — the card's Leg A shape 3, inverted. Both routes present:
493+ // BEFORE the ruling the id route won here ("ZH-by-ID"); the page-name
494+ // route is canonical now, so the components key is inert on this component
495+ // and the bundle that overrode a header title through it falls back.
496+ const bothBundle = {
497+ en : {
498+ pages : {
499+ header_route_page : {
500+ title : 'BY-NAME' ,
501+ subtitle : 'SUB-BY-NAME' ,
502+ components : { home_header : { title : 'BY-ID' } } ,
503+ } ,
504+ } ,
505+ } ,
506+ } as any ;
507+ expect ( translatePage ( page as any , bothBundle , { locale : 'en' } ) . regions [ 0 ] . components [ 0 ] . properties )
508+ . toEqual ( { title : 'BY-NAME' , subtitle : 'SUB-BY-NAME' } ) ;
509+ } ) ;
510+
377511 it ( 'carries the whole shared key list down into nesting, label either/or included' , ( ) => {
378512 const rows = componentRows ( walkParityPage ( ) ) ;
379513 // `label` authored at the component's top level, the same either/or
@@ -414,7 +548,10 @@ describe('i18n-extract ↔ translatePage walk parity (#13109)', () => {
414548 . toEqual ( [ { key : 'twice.title' , value : 'First nested wins' } ] ) ;
415549 // A region-level `page:header` emits nothing here, but its id still BLOCKS
416550 // a nested namesake — the resolver counts it as region-level, so offering
417- // the nested one would be a key the resolver ignores.
551+ // the nested one would be a key the resolver ignores. The ruling changed
552+ // what the resolver READS for that id, not who OWNS it: the arbitration is
553+ // a property of the document and both consumers still decide it the same
554+ // way, which is why this half is unchanged.
418555 expect ( rows . filter ( ( r ) => r . key . startsWith ( 'hdr_id.' ) ) ) . toEqual ( [ ] ) ;
419556
420557 // And the resolver agrees about which component the entry lands on.
@@ -432,7 +569,10 @@ describe('i18n-extract ↔ translatePage walk parity (#13109)', () => {
432569 const translated = translatePage ( page as any , bundle , { locale : 'en' } ) ;
433570 const region = translated . regions [ 0 ] . components ;
434571 expect ( region [ 0 ] . properties . title ) . toEqual ( 'S' ) ;
435- expect ( region [ 1 ] . properties . title ) . toEqual ( 'H' ) ;
572+ // `hdr_id` is the region-level `page:header`: the bundle's `H` under
573+ // `components.hdr_id.title` is NOT read, and this page has no
574+ // `pages.walk_collision_page.title` either, so the authored literal stands.
575+ expect ( region [ 1 ] . properties . title ) . toEqual ( 'Header holds this id' ) ;
436576 const nested = region [ 2 ] . properties . children ;
437577 expect ( nested . map ( ( c : any ) => c . properties . title ) ) . toEqual ( [
438578 'Nested namesake loses' ,
0 commit comments