Skip to content

[finding] translatePage's addressed-component walk is not exported, so the CLI extractor hand-mirrors its depth cap, cycle guard and collision arbitration — the copy hazard the key list closed, left open for the walk #13218

Description

@os-trump

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions