Skip to content

docs(i18n): state the shipped severity for an unresolvable translation key - #18766

Merged
os-try-charles merged 1 commit into
mainfrom
claude/issue-18452-i18n-unresolvable-key-severity
Sep 17, 2026
Merged

os-try-charles merged 1 commit into
mainfrom
claude/issue-18452-i18n-unresolvable-key-severity

Conversation

@os-try-charles

Copy link
Copy Markdown
Collaborator

Part of #18452

content/docs/protocol/kernel/i18n-standard.mdx told authors that an unresolvable
translation-bundle key "is a warning, not an error: nothing crashes". Both halves are
false against the shipped linter, and have been since #16310 promoted
translation-target-unknown to error. An author who read the page and then wrote a locale
key against a target the linter cannot resolve got a FAILED run, having been told in writing
it would be a nag.

The two texts, re-measured on this branch

Repository objectstack-ai/objectstack, worktree at base f1c9bb305.

Reading Result
content/docs/protocol/kernel/i18n-standard.mdx:1027 (before this PR) An unresolvable key is a **warning**, not an error: nothing crashes, the string
packages/lint/src/validate-translation-references.ts:151 const TRANSLATION_TARGET_UNKNOWN_SEVERITY = 'error' as const;
consumed as the severity of every finding at :912 and :1364 — the card's :813 / :1265 are stale, so everything here was located by CONTENT
control that the probe reaches the page at all translation matches 49 lines in that same mdx

What an error actually does, read from the consuming commands rather than inferred from the
severity word:

  • os lint — failing = errors.length + (strict ? warnings.length : 0) and
    if (failing > 0) process.exit(1) (packages/cli/src/commands/lint.ts:918-922, :1040).
    No flag is needed; --strict only adds warnings on top.
  • os validate — splitBySeverity(findings), then this.exit(1) on a non-empty ruleErrors
    (packages/cli/src/commands/validate.ts:353-383).
  • os compile — the same shape (packages/cli/src/commands/compile.ts:370-397).

The rule reaches all three because its suite entry declares commands: ALL
(packages/lint/src/authoring-rules.ts:709-713).

Where the repair's boundary was drawn

The old sentence promised two things — "a warning, not an error" AND "nothing crashes" — so
repairing only the severity word would have left a worse half-truth than the original. The whole
claim is repaired instead:

  • the severity now reads error, names the rule id so a reader can grep it, and names the
    three commands that exit non-zero;
  • the runtime half is kept, because it is still true and is precisely the argument for the
    promotion: the string really does render in its source locale while its neighbours resolve, and
    that invisibility is what makes an orphan key worth failing on. The rule's own module header
    makes the identical argument (packages/lint/src/validate-translation-references.ts:29-48);
  • the sibling rule is separated out, because the very next paragraph on the page is about it:
    translation-option-key-unknown is still a warning
    (packages/lint/src/validate-translation-references.ts:1309, :1328).

Out of bounds on purpose: the two places on this page that describe the key shape (the card
measured both as correct) are untouched, as is content/docs/ui/translations.mdx.
packages/lint/** is untouched by design — #16310 was a deliberate promotion, so the page is
what drifted, and downgrading the rule would be the gate-weakening the triage ruling forbids.

The sibling sweep — the spellings and the populations, named

The card's starting point ("under content/docs, exactly one file mentions unresolvable key or
translation-target-unknown") is a reading over ONE spelling, not over the claim, so it was
re-run wide. Probes, all on f1c9bb305, all case-insensitive:

# Spelling Population
1 translation-target-unknown whole repo
2 unresolvable whole repo
3 orphan[- ](key|translation) / orphaned (key|translation) whole repo
4 nothing crashes whole repo
5 warning,? not an error / not an error,? (a|just a) warning whole repo
6 inert content/docs, skills, examples, apps, README.md
7 a severity word (warning|warn|error|severity|fails the run|fails the build|exit 1) on the same line as translation-key language (translat|locale|i18n|bundle key|orphan) content/docs, skills, examples, apps, README.md, minus content/docs/references and content/docs/releases
8 advisory|does not fail|doesn't fail|won't fail|never fails|non-blocking|non-gating|safe to ignore near translation language content/docs, skills, examples, docs, apps
9 renders? (in its )?(source|original) locale / renders? untranslated / stays english content/docs, skills, examples, docs, apps
10 the rule id / claim spellings of 1 and 3 docs, .claude, apps, skills, content
11 every severity word inside i18n-standard.mdx itself that one file

What they returned:

  • One live carrier in content/docs — the sentence this PR repairs. Probe 11 confirms the page
    states the severity claim exactly once; the other hits on that page are Callout type="warn"
    markers and an unrelated parse-failure sentence.
  • A positive control, and it is the useful one. skills/objectstack-i18n/SKILL.md:196-197
    already states the claim correctly: "translation-target-unknown is an error and fails
    the run; translation-option-key-unknown is a warning." The probe reaches a second document
    carrying this claim, and that document has been right all along — so a zero elsewhere is a
    reading, not a blind spot.
  • Dated historical records, deliberately not edited:
    docs/audits/2026-07-app-metadata-reference-integrity-assessment.md:93 ("warning throughout",
    stamped "Landed 2026-07-28") records what shipped on that date;
    .changeset/16310-orphan-locale-key-gates.md, packages/lint/CHANGELOG.md,
    packages/cli/CHANGELOG.md and content/docs/releases/** are release-owned and correct as of
    their entries. AGENTS.md forbids amending them from a code PR.
  • Near-misses re-read and cleared: content/docs/ui/translations.mdx:242-244 (default-locale
    COVERAGE severity) and :346-365 (the flows group's liveness warning) are different rules, not
    this claim.
  • One carrier this PR is fenced out of — see below; it is why this body says Part of and not
    a closing keyword.

Verification

Gate families derived mechanically from the change set rather than listed by hand:
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at commit
4083936f7, then reconciled with --ran carrying one recorded exit code per family:

Run reconciliation - 39 derived, 39 run, 0 NOT-MEASURED, 0 UNRUN.
dispatch-gates --ran: 39 derived famil(ies) accounted for - 39 run,
  0 NOT-MEASURED (a DERIVED zero - all 39 recorded an exit code and none of them is 3).

Three families first answered exit 3 = PREREQUISITE NOT MET (@objectstack/lint and
@objectstack/client-react were unbuilt in a fresh worktree), which is not a finding. They were
re-run after building those packages and all three then exited 0: check:doc-formula-expressions,
check:doc-security-posture, check:docs-transcript-drift, plus check:skill-examples which had
exited 1 for the same unbuilt-dist reason it prints by name.

Notable members of the 39, all exit 0: check:docs-section-name, check:doc-anchors,
check:doc-authoring, check:docs-single-h1, check:doc-frontmatter, check:corpus-claim-drift,
check:docs-audit-scope, check:cross-package-test-inputs, check:nul-bytes,
pnpm --filter @objectstack/spec check:docs.

Also self-scanned for control bytes beyond the gate, per AGENTS.md:
grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' over the edited file returns nothing (exit 1).

Declared scope of the local run: repository-wide scans (pnpm lint foremost) are CI's run, and
origin/main moved 4 commits after this branch's base. The only gate family those commits add is
check:rerun-safety-verdict, a self-test-only checker-health family wired whole-tree and unrelated
to content/docs; they add no docs-path family (measured by diffing .github/workflows/lint.yml
and package.json across that range). CI derives on the merge ref.

Changeset: none, and the measurement behind that

The criterion is whether anything PUBLISHED moves — what each package's files[] actually ships.
Measured over every non-private package.json in the workspace: 70 published packages, 0 whose
files[] names content/docs
; positive control on the same instrument, 70 of 70 name dist.
apps/docs (the Fumadocs site that renders this page) is "private": true. Nothing copies
content/docs into a package at build time. Hence skip-changeset.

Acceptance notes

Observed while sweeping, NOT fixed here and NOT filed as cards:

  • packages/lint/src/validate-translatable-sections.ts:40-43 justifies its own warning severity
    with "Same reading as its sibling rule (ADR-0072 D1): nothing crashes and nothing is dead". The
    sibling is validate-translation-references, which no longer holds that reading — this is the
    same falsified claim, one file over, by reference. Its own rule's warning severity is still
    correct; only the cross-reference is stale. packages/lint/** is fenced for this card by the
    triage ruling and by the dispatch, so it is reported rather than touched, and it is why this body
    reads Part of #18452 — the docs half is complete, the card's last carrier is not. The repo
    already carries the correction one file over, in
    packages/lint/src/reference-integrity-suite.ts:406-410: "warning-only on its own reading - NOT
    on its sibling's any more".
  • packages/lint/src/validate-translation-references.test.ts:26-27 narrates the superseded Severity
    note in the present tense inside a docblock; harmless in context (the tests below it assert
    error), noted only because it is the third text in the family.

Neither is a reproducible defect, a declared-contract violation or a metadata-authoring trap, so
neither is filed by this PR; both are handed to triage in the report instead.


Generated by Claude Code

…n key

`content/docs/protocol/kernel/i18n-standard.mdx` promised that an unresolvable
translation-bundle key "is a **warning**, not an error: nothing crashes". Both
halves are false against the shipped linter: `translation-target-unknown` has
carried `severity: 'error'` since #16310, so `os validate`, `os lint` and
`os compile` each exit non-zero on one. An author who read the page and then
wrote a locale key against a target the linter cannot resolve got a failed run,
having been told in writing it would be a nag.

The whole claim is repaired, not just the severity word:

- the severity now reads `error`, names the rule id so it can be grepped, and
  names the three commands that exit non-zero on it;
- the runtime half ("nothing crashes ... renders in its source locale") is kept,
  because it is still true and is precisely the reason the rule gates: the orphan
  key stays a confident-looking grep hit for a surface that was deleted;
- the sibling rule is separated out, since the very next paragraph is about it:
  `translation-option-key-unknown` is still a warning.

The linter is untouched by design. #16310 was a deliberate promotion, so the page
is what drifted.

Claude-Session: https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk
Co-authored-by: Claude <noreply@anthropic.com>
@os-try-charles os-try-charles added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 17, 2026 — with Claude
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation labels Sep 17, 2026
@os-try-charles
os-try-charles marked this pull request as ready for review September 17, 2026 20:03
@os-try-charles
os-try-charles added this pull request to the merge queue Sep 17, 2026
Merged via the queue into main with commit 2085be2 Sep 17, 2026
38 checks passed
@os-try-charles
os-try-charles deleted the claude/issue-18452-i18n-unresolvable-key-severity branch September 17, 2026 20:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants