Skip to content

test(i18n): fail on an orphaned apps.*.navigation locale key - #1726

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-1262-orphan-locale-keys-have-a-gate
Sep 6, 2026
Merged

os-steve merged 1 commit into
mainfrom
claude/issue-1262-orphan-locale-keys-have-a-gate

Conversation

@claude

@claude claude Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Description

A single orphaned apps.*.navigation locale key now turns pnpm verify red.

Every i18n assertion in this repo is one-directional: it asks whether an authored
surface has a translation, never whether a translation still has a surface. That
direction structurally cannot see a key whose target is gone — deleting a
navigation entry leaves its apps.*.navigation rows behind in all four bundles,
and a forward walk has nothing left to iterate that would ever reach them.

The near miss this is written from: #1259 cut four navigation entries, and PR
#1261 removed their nav_pipeline / nav_all_tasks / nav_event_calendar /
nav_event_history keys — plus group_approvals — from zh-CN, es-ES and
ja-JP by hand. Had that author not remembered, pnpm verify would have
stayed fully green carrying 15 dead keys.

What I measured, and why I chose this route

The card says orphans are invisible to every i18n check. Half of that is
false, and the half that is true is what this PR fixes.
Everything below was
measured on this branch, not inherited.

1. Baseline (objectstack lint --json, this tree) — reproduced independently:

tree total warnings passed
baseline 13 1 true
one nav orphan + one _sections orphan planted in all 4 bundles 21 9 true
restored 13 1 true

Zero translation-target-unknown findings at baseline confirms the repo carries
no orphans today in any keyspace that rule covers.

2. The decisive probe — does the platform rule cover apps.*.navigation?
objects.* evidence proves nothing about apps.*, so a nav orphan was planted
directly. It is reported, once per locale:

warning | translation-target-unknown | translations[0].en.apps.crm_enterprise.navigation.nav_ablation_orphan_1262
warning | translation-target-unknown | translations[0]["zh-CN"].apps.crm_enterprise.navigation.nav_ablation_orphan_1262
warning | translation-target-unknown | translations[0]["ja-JP"].apps.crm_enterprise.navigation.nav_ablation_orphan_1262
warning | translation-target-unknown | translations[0]["es-ES"].apps.crm_enterprise.navigation.nav_ablation_orphan_1262

with the remedy printed: "Match the key to the navigation item's id, or drop
it. Declared navigation ids: … (33 total)."

3. So what is actually broken is that nothing gates it. The finding is a
warning; objectstack lint exits 0 on warnings (passed: true in every row
above); and scripts/check-lint-i18n-gate.mjs gates the i18n/missing-*
family, which is the forward direction.

Route: the repo-side assertion, not a wider gate

Both routes were live. The gating route was rejected on measurement:

  • The rule id is bare translation-target-unknown — no i18n/ namespace at
    all.
    The CLI passes registry rule ids straight through (rule: f.rule in
    @objectstack/cli's lint.js); only its own rubric carries family/ ids. So
    the rule sits outside the gate's world rather than merely unlisted in it,
    which is the condition on which the 2026-08-25 ruling stands unchanged.
  • A prefix gate cannot express it without becoming an exact-id allowlist for
    a family that spans every keyspace at once — objects, fields, views, sections,
    tabs, validations, actions, dashboards, flows. Adopting all of that is a much
    wider decision than this card.
  • It runs against this repo's standing direction. When eleven local
    assertions were retired (.changeset/retire-assertions-the-platform-already-reds-on.md),
    the criterion was that objectstack lint exits 1 on the defect. The
    converse is the rule for keeping one: this rule does not error, so the local
    assertion is exactly the kind this repo keeps.
  • test/i18n-references.test.ts's own header states the principle directly:
    every guard there walks the metadata and asks what it declares, and "none of
    them asks a linter what it noticed."

On the gate script's header text (which the 2026-08-25 grading cited): read in
full, it does not forbid widening — it says "Widening this script to cover
other rule families is a separate, deliberate change, not a side effect of this
one."
That sentence scopes #1018, not the script's future. The reason not to
widen it here is the namespace measurement above, not that header.

Changes Made

  • test/i18n-references.test.ts: adds no locale carries an apps.*.navigation entry the app does not declare, mirroring the existing _views orphan
    assertion, plus its anti-vacuity half (sees a real navigation tree and an apps.*.navigation table in every locale) pinning that stack.apps, the nav
    walk and each locale's table were all non-empty.
  • The walk counts group ids and follows children to any depth and areas[],
    matching what the console can render — group_approvals was one of the five
    orphans feat(nav): one entry per destination, one exemplar per nav-item kind (#1259) #1261 removed by hand.
  • An unknown app name is reported, never skipped: skipping is the exact shape
    (always green in the orphan direction) this card records.
  • .changeset/orphan-nav-locale-keys-turn-verify-red.md, empty frontmatter.

Scope stops at apps.*.navigation per the 2026-08-25 ruling — flat keyspace,
nav tree right beside it. objects.* / actions.* generalise from this shape
once agreed. The forward direction for navigation already gates as
i18n/missing-navigation and is deliberately not restated.

Acceptance, in both directions

RED — plant the real historical orphan nav_pipeline in zh-CN only, one
key, and run the full chain:

os-verify-lock: VERDICT command-exit 1
✓ Validation passed · typecheck ✓ · pnpm lint ✓
✓ i18n lint gate: 0 `i18n/missing-*` issues (14 total lint issue(s) reported, unaffected by this gate)
✓ source hygiene clean · ✓ source token ratchet clean · ✓ Build complete
 Test Files  1 failed | 160 passed (161)
      Tests  1 failed | 3409 passed | 1 skipped (3411)

FAIL  test/i18n-references.test.ts > every locale is complete on every authored surface
      > no locale carries an apps.*.navigation entry the app does not declare
AssertionError: navigation translations naming no declared nav item:
  zh-CN: apps.crm_enterprise.navigation.nav_pipeline

Note the gate line: with the orphan present the existing i18n gate stayed green
and merely counted one more unrelated issue. That is the card, in one line.

GREEN — at d4c203b6, the final commit:

os-verify-lock: VERDICT command-exit 0
✓ Validation passed (1319ms)
✓ i18n lint gate: 0 `i18n/missing-*` issues (13 total lint issue(s) reported, unaffected by this gate)
✓ source hygiene clean
✓ source token ratchet clean
✓ Build complete (1490ms)
 Test Files  161 passed (161)
      Tests  3410 passed | 1 skipped (3411)

Every mutation used the disk-proof procedure: hash the file, mutate, prove the
marker landed and the hash moved, measure under an EXIT/INT/TERM trap, restore
with git checkout HEAD -- ABSOLUTE_PATH, then re-hash to prove
byte-identity
. Both ablations restored byte-identical; no exit code was
trusted on its own, and no git stash was used.

Type of Change

  • New tests / guard (no src/ metadata changed, nothing user-visible)

Related Issues

Closes #1262

Testing

  • Unit tests pass (pnpm test — via pnpm verify, 3410 passed)
  • Linting passes (pnpm lint, pnpm lint:i18n-gate)
  • Build succeeds (pnpm build)
  • New tests added, and proved to go red before being accepted green

Additional Notes

Upstream, reshaped. The card anticipated asking the platform to build an
orphan rule family. It already has one. The remaining upstream ask is narrower:
translation-target-unknown is unfailable by design — warning severity, no
i18n/ namespace, no severity knob — so every app on the platform, not just
this one, ships dead locale keys through a green pipeline. Filing that is
optional and was deliberately left to the maintainer rather than guessed at,
since it is a severity/gateability request and not the "build it" card the
original wording describes.


Generated by Claude Code

Every i18n assertion in this repo asks whether an authored surface has a
translation. That direction cannot see a key whose target is gone: deleting a
navigation entry leaves its `apps.*.navigation` rows behind in all four
bundles, and a forward walk has nothing left to iterate that would reach them.
PR #1261 removed five such keys from three locales by hand; had it not,
`pnpm verify` would have stayed green with 15 dead keys.

Measured before choosing the shape. The platform rule
`translation-target-unknown` DOES report a navigation orphan, once per locale,
with the remedy printed — orphans are not invisible, which the card assumed.
What nothing does is fail: the finding is a `warning`, `objectstack lint` exits
0 on warnings, and `scripts/check-lint-i18n-gate.mjs` gates the
`i18n/missing-*` family, the forward direction. The platform rule id carries no
`i18n/` namespace, so it sits outside that gate rather than unlisted in it.

Adds the orphan assertion plus the anti-vacuity half that pins the nav walk and
the per-locale tables really were non-empty. Scope stops at
`apps.*.navigation`; the forward direction already gates as
`i18n/missing-navigation`.

Co-authored-by: Claude
@vercel

vercel Bot commented Sep 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
hotcrm Ignored Ignored Sep 6, 2026 1:41pm UTC

Request Review

@github-actions github-actions Bot added the ci/cd CI plumbing and the verification pipeline label Sep 6, 2026
@os-steve
os-steve marked this pull request as ready for review September 6, 2026 13:46
@os-steve
os-steve added this pull request to the merge queue Sep 6, 2026
Merged via the queue into main with commit 0f47874 Sep 6, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd CI plumbing and the verification pipeline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Orphaned locale keys are invisible to every i18n check — the gate only looks surface→translation

2 participants