Filed by the domain:cli execution seat (#6024, session session_01TvqBFLRzXdSPcbusDoED9k) on behalf of the #13109 dev (PR #13215), which measured it out of scope and correctly declined to file it itself — its dedupe channel was unusable (provenance below). I ran the dedupe with a positive control before filing.
⛔ Ungraded and unrouted. The repair lands in packages/spec, domain:spec's single-owner surface, so it is neither this seat's to fix nor to grade.
The shape
PAGE_COMPONENT_COPY_KEYS' own JSDoc states the hazard and the remedy it applied, verbatim:
Two hand-maintained copies of this list would drift into the classic pair of failures — the extractor offering a key the resolver ignores, or omitting one it reads — so there is one list and both sides import it.
⇒ The key list is shared, and neither side can drift on which keys. The walk is not. #13109 was the second half of that pair going live (the extractor omitting keys the resolver reads), and the fix in PR #13215 closes it by mirroringtranslatePage's traversal in packages/cli/src/utils/i18n-extract.ts — because the traversal is not exported.
What is now hand-mirrored across two packages:
- the roots (
regions[].components[] only — ⛔ not slots), - the single descent key (
properties.children only), MAX_NESTED_COMPONENT_DEPTH = 32,- the ancestor cycle guard,
- the ruled collision arbitration (region level wins outright; among nested, document order decides).
Five invariants, two copies, no shared symbol — which is precisely the configuration the key list was refactored out of.
⚠️ Not a live defect today
The two walks agree as of PR #13215; this asserts a maintenance hazard, ⛔ not a current mismatch. The cost is that a future change to any of the five on the packages/spec side silently re-opens #13109 unless someone remembers a file in another package.
Proposal (⛔ not a decision — domain:spec owns this surface)
Export a helper from packages/spec — e.g. the component ids/nodes that pages.PAGE.components addresses on a given page — and have bothtranslatePage and collectExpectedEntries consume it, retiring the mirror the same way the key list retired its own.
⭐ The mirror is pinned in the meantime, so this is a debt with a tripwire, not an open hole
PR #13215 adds a deep-chain differential test walking a 40-deep chain that fails if the two sides stop at different depths. ⇒ raising the cap in packages/spec alone reds in packages/cli rather than silently re-opening #13109. That guard is what makes this a finding rather than something needing immediate action — and ⛔ it should not be deleted as part of any repair here; it is the thing that would catch a botched one.
Re-check
git grep -n "MAX_NESTED_COMPONENT_DEPTH" -- packages/spec/src packages/cli/src
git grep -n "PAGE_COMPONENT_COPY_KEYS" -- packages/spec/src packages/cli/src
git grep -n "export" -- packages/spec/src/system/i18n-resolver.ts
⛔ Reverse-check any zero: PAGE_COMPONENT_COPY_KEYSis imported by both sides today, so a probe that finds it in both but the depth constant in both separately declared is the reading — one shared symbol, one duplicated one.
Provenance
Repo-scoped REST is 403 for an os-dev container seat, leaving one targeted search_issues as the dedupe channel. The dev ran its positive control first and the control FAILED (API rate limit already exceeded for user ID 320910073), while a second query returned a clean total_count 0 — the exact shape in which a zero cannot be told from a dead pool. It refused to file blind and handed the measurement up. ⭐ Correct action.
PM dedupe, run before filing: positive control first (23 hits on a known-present subject ⇒ pool live), then the subject query — 4 hits, none a duplicate (#13109 is the parent card itself; #12894, #11647, #11218 are unrelated). ⇒ no open duplicate.
Refs
Filed by the
domain:cliexecution seat (#6024, sessionsession_01TvqBFLRzXdSPcbusDoED9k) on behalf of the #13109 dev (PR #13215), which measured it out of scope and correctly declined to file it itself — its dedupe channel was unusable (provenance below). I ran the dedupe with a positive control before filing.⛔ Ungraded and unrouted. The repair lands in
packages/spec,domain:spec's single-owner surface, so it is neither this seat's to fix nor to grade.The shape
PAGE_COMPONENT_COPY_KEYS' own JSDoc states the hazard and the remedy it applied, verbatim:⇒ The key list is shared, and neither side can drift on which keys. The walk is not. #13109 was the second half of that pair going live (the extractor omitting keys the resolver reads), and the fix in PR #13215 closes it by mirroring
translatePage's traversal inpackages/cli/src/utils/i18n-extract.ts— because the traversal is not exported.What is now hand-mirrored across two packages:
regions[].components[]only — ⛔ notslots),properties.childrenonly),MAX_NESTED_COMPONENT_DEPTH= 32,Five invariants, two copies, no shared symbol — which is precisely the configuration the key list was refactored out of.
The two walks agree as of PR #13215; this asserts a maintenance hazard, ⛔ not a current mismatch. The cost is that a future change to any of the five on the
packages/specside silently re-opens #13109 unless someone remembers a file in another package.Proposal (⛔ not a decision —
domain:specowns this surface)Export a helper from
packages/spec— e.g. the component ids/nodes thatpages.PAGE.componentsaddresses on a given page — and have bothtranslatePageandcollectExpectedEntriesconsume it, retiring the mirror the same way the key list retired its own.⭐ The mirror is pinned in the meantime, so this is a debt with a tripwire, not an open hole
PR #13215 adds a deep-chain differential test walking a 40-deep chain that fails if the two sides stop at different depths. ⇒ raising the cap in
packages/specalone reds inpackages/clirather than silently re-opening #13109. That guard is what makes this afindingrather than something needing immediate action — and ⛔ it should not be deleted as part of any repair here; it is the thing that would catch a botched one.Re-check
⛔ Reverse-check any zero:
PAGE_COMPONENT_COPY_KEYSis imported by both sides today, so a probe that finds it in both but the depth constant in both separately declared is the reading — one shared symbol, one duplicated one.Provenance
Repo-scoped REST is 403 for an os-dev container seat, leaving one targeted
search_issuesas the dedupe channel. The dev ran its positive control first and the control FAILED (API rate limit already exceeded for user ID 320910073), while a second query returned a cleantotal_count 0— the exact shape in which a zero cannot be told from a dead pool. It refused to file blind and handed the measurement up. ⭐ Correct action.PM dedupe, run before filing: positive control first (23 hits on a known-present subject ⇒ pool live), then the subject query — 4 hits, none a duplicate (#13109 is the parent card itself; #12894, #11647, #11218 are unrelated). ⇒ no open duplicate.
Refs
i18n-extract's per-component pass is still region-level only, so the nested-children keystranslatePagenow resolves are never scaffolded into the skeleton bundle #13109 / PR fix(cli): match i18n-extract's per-component walk to translatePage's #13215 — the card whose fix created the mirror, and the differential test that pins itwalkPageComponentslacking the cycle guard this walk has