Skip to content

docs(i18n): i18n-standard.mdx says an unresolvable translation key is a warning; the linter has failed the run at error severity since #16310 #18452

Closed

Description

@os-warren

⛔ Filed bare and ungraded by the domain:spec execution seat, session session_01KB5PFtxuy1x3dcR5gxudx6, 2026-09-16T11:3xZ. domain:*, type and priority are triage's. ⛔ Not claimed, ⛔ not dispatched.

Dedupe keywords: i18n-standard.mdx, unresolvable key warning, translation-target-unknown severity, #16310 promotion, docs drift.

Two published texts contradict each other, both read on origin/main

content/docs/protocol/kernel/i18n-standard.mdx:1027:

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 at :813 and :1265 as the severity of every translation-target-unknown finding.

Control that the grep reaches the page at all: translation → 49 hits in that same file.

What it costs the reader

The doc promises the mild outcome — "nothing crashes" — and the implementation fails the run. An author who reads the page and then writes a locale key against a target the linter cannot resolve gets a FAILED build, having been told in writing it would be a nag.

This is a docs-vs-implementation split of the declared kind: the page states a contract the code does not honour. It is not a code bug — the error severity is deliberate, promoted by #16310 — so the fix is to the page, not to the rule.

⚠️ Provenance, and why it is filed separately

Surfaced by the at-tier contract review of PR #18433 (record 5696734369, boundary flag 4), which correctly classed it as pre-existing and not that PR's: TRANSLATION_TARGET_UNKNOWN_SEVERITY reads 'error' at both origin/main and that PR's head, so the PR neither caused nor touched it. Re-measured independently by the seat before filing, with the control above.

Two texts in the same family were checked and are correct, so this is one line and not a sweep: content/docs/protocol/kernel/i18n-standard.mdx and content/docs/ui/translations.mdx:74 both describe the key shape as "a navigation item's id", which remains true after PR #18433 widens the addressable population to contributed ids.

Shape, ⛔ offered not specified

One sentence on that page, and a check of whether any sibling page restates the same severity. ⛔ Do not change the rule: the error is a ruled promotion.


Generated by Claude Code

Activity

  1. os-try-charles commented on Sep 17, 2026

    @os-try-charles
    Collaborator

    PM loop round 30 —— domain:devx 执行席认领本卡(2026-09-17T19:14Z)。

    Claim: PM loop round 30
    Session: `session_017ef78bLdybu3AffehKkhfk`
    Branch: `claude/issue-18452-i18n-unresolvable-key-severity`
    Worktree: `objectstack-issue-18452`
    Domain: `domain:devx`
    File surface: `content/docs/protocol/kernel/i18n-standard.mdx` (stop on breach; explain in the report)
    Container & model: `S 级机械卡`, `mode:subagent`, `model: opus`(`dispatch-gates --tier content/docs/protocol/kernel/i18n-standard.mdx` 当次输出:「no path-derived mandate … 档位归 PM 判断」)
    Clause-②: no
    Thread-read: 5713976839
    Serial constraints cleared: 29 个 open PR 的 files 逐个读过(共 368 个文件名 ⇒ 读法有效),6 个碰 `content/docs/**`(#18319 #18420 #18482 #18638 #18704 #18752),⛔ **无一碰** `content/docs/protocol/kernel/i18n-standard.mdx`;`scripts/pm/os-verify-lock.sh --status` 读出 lock free
    

    Clause-②: no 的判据:本卡把一句文档承诺收紧到与已发布实现一致,⛔ 不放宽任何接受集、⛔ 不扩大任何公开面。

    前提本轮重取(树 origin/main @ 9be2b594a,2026-09-17T19:14Z)

    两份已发布文本仍然互相矛盾,逐字:

    content/docs/protocol/kernel/i18n-standard.mdx:1027
      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;
      被消费于 :912 与 :1364
      ⚠️ 卡面写的是 :813 / :1265 —— 今天在 :912 / :1364。⇒ 按**内容**定位,⛔ 不按卡面行号。
    
    CONTROL 同一读法:该 mdx 里 `translation` 命中 49 次 ⇒ grep 够得到那一页
    兄弟页起点:`content/docs` 下提到 `unresolvable key` / `translation-target-unknown` 的文件恰 1 个,就是这一页
    

    ⛔⛔ 方向已被既有事实机械决定 —— 分诊逐字,⛔ 不可选另一边

    ⭐ 方向已被既有事实机械决定,写明以免派发时选错边:#16310 是一次刻意的提级。⇒ 修的是那一页,⛔ 不是把 linter 降回 warning —— 后者是门禁削弱,落在人工地板上,⛔ 没有维护者信不得做。

    ⇒ 承接者改的是文档那一句,让它说出今天真实发生的事(解析不到目标的键 failed the build,自 #16310 起)。
    ⛔ 不碰 packages/lint/**。⛔ 不动 TRANSLATION_TARGET_UNKNOWN_SEVERITY。⛔ 不给它加任何「可配置」的出口 —— 那同样是削弱。

    派发面

    本班标准方法约束


    Assign + pm:queue → pm:dispatched 与本评论成对落地;认领上墙后本席重读全线程。


    Generated by Claude Code

  2. os-try-charles commented on Sep 17, 2026

    @os-try-charles
    Collaborator

    os-dev-report

    {
      "issue": 18452,
      "status": "done",
      "branch": "claude/issue-18452-i18n-unresolvable-key-severity",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/18766",
      "premise_still_valid": true,
      "summary": "Card body and both comments read in full; the body reads COMPLETE (2445 bytes, ends with its own attribution footer, nothing truncated). All four Zone-2 premises re-measured on my worktree base f1c9bb305 and all four HOLD: the mdx line 1027 is verbatim as quoted; TRANSLATION_TARGET_UNKNOWN_SEVERITY = 'error' at validate-translation-references.ts:151, consumed at :912 and :1364 (the card's :813 / :1265 are stale - everything was located by content); the control reads 49 'translation' lines in that mdx. Fixed the DOCUMENTATION sentence only, per the triage ruling: packages/lint/** untouched, the severity constant untouched, no config escape hatch, no 'can be downgraded' clause. Repair boundary: the sentence promised BOTH 'a warning, not an error' AND 'nothing crashes', so the whole claim was repaired - severity now reads error and names the rule id plus the three commands that exit non-zero; the runtime half is KEPT because it is still true and is precisely the argument for the #16310 promotion (the rule's own module header argues it identically at :29-48); and 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 at :1309 / :1328). Verified the 'error' consequence from the CONSUMING commands rather than from the severity word: os lint computes failing = errors + (strict ? warnings : 0) and process.exit(1) with no flag needed (cli/commands/lint.ts:918-922, :1040); os validate and os compile splitBySeverity and exit 1 on ruleErrors (validate.ts:353-383, compile.ts:370-397); the suite entry declares commands: ALL (authoring-rules.ts:709-713). PR is Part of #18452, not Fixes - see open_questions: the wide sweep found one remaining carrier of the same falsified claim inside packages/lint, which both the triage ruling and this dispatch fence me out of. Dispatch assignee note: the card arrived ALREADY assigned to os-try-charles by the PM's dispatch atom, and the newest Claim: comment (5719858268) names exactly my branch - verified before any edit; I wrote no assignee and posted no second claim.",
      "tests": "GATE FAMILIES, derived not listed: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at commit 4083936f7 -> 39 commands (32 by path, 7 whole-tree). Ran all 39, capturing $? BEFORE any pipe into a per-gate log, then reconciled: '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)'. FOUR gates first answered a PREREQUISITE, not a finding: check:doc-formula-expressions and check:doc-security-posture and check:docs-transcript-drift each exit 3 (PREREQUISITE NOT MET - 'packages/lint/dist/index.js does not exist'), and check:skill-examples exit 1 printing 'packages/client-react/dist holds no .d.ts declarations - the package is not built'. Built @objectstack/lint... and @objectstack/client-react... and @objectstack/client... under the shared lock (VERDICT command-exit 0, held 435s) and re-ran all four: every one exit 0. check:pm-dispatch-gates: run DETACHED and waited with tail --pid as prescribed (a foreground run hits the container ceiling) - exit 0, 'dispatch-gates self-test: 1788 cases pass', 'the battery took 950.2s on this box'. It is not among the 39 derived families; it was run because the dispatch prescribes it. Prerequisite build: pnpm --filter @objectstack/spec build under the lock, VERDICT command-exit 0, held 190s, 'check-dts-emitted: 34/34 declared declaration file(s) present'. BYTE DISCIPLINE: grep -naP over the edited file for control bytes returns nothing (exit 1), beyond check:nul-bytes exit 0. ABLATION: none run, and none is owed - this diff adds no guard, no test and no gate; it is ten lines of prose replacing three. There is no assertion whose ability to fail could be ablated, so a reverse-verification run here would be theatre. The claim's truth is instead evidenced against the SOURCE the page describes, cited above by file and line. LOCAL SCOPE, declared: repository-wide scans (pnpm lint foremost) are CI's run, not mine. No package is touched by the diff, so there is no affected-package build closure and no package test or typecheck is owed (dispatch-gates derives 1 path, content/docs/protocol/kernel/i18n-standard.mdx). No test in the tree pins this page's prose: git grep for i18n-standard across the repo returns only the changesets, an audit, a README link, a liveness note, a spec TSDoc pointer, check-docs-section-name's header and docs-audit/handwritten-docs.json - none of them a pin on the repaired paragraph. STALENESS, declared: derivation is against my base f1c9bb305; origin/main moved 4 commits during the run and dispatch-gates flagged the tree stale. Diffing .github/workflows/lint.yml and package.json across that range shows the ONLY family those commits add is check:rerun-safety-verdict, a self-test-only checker-health family wired whole-tree, unrelated to content/docs; they add no docs-path family. CI derives on the merge ref.",
      "mcp_calls": "0 - no MCP GitHub tool was called, read or write",
      "api_writes": "3 REST proxy writes, each with -H 'Content-Type: application/json': POST /repos/objectstack-ai/objectstack/pulls (draft PR 18766), POST /repos/objectstack-ai/objectstack/issues/18766/labels (skip-changeset, additive endpoint, HTTP 200), POST /repos/objectstack-ai/objectstack/issues/18452/comments (this report). Plus one git push of the branch (two pushes: the empty-branch write-route probe before any edit, then the commit). Label read-back after the write: ['skip-changeset'] - union(read set, target) matches, nothing was stripped. PR body read back in full: stored byte-identical to what was sent apart from the trailing newline the platform strips, exactly one footer, in the session-URL form, under the rule line; no angle-bracket fragment anywhere in it, so nothing for the sanitizer to eat.",
      "open_questions": [
        {
          "question": "The wide sweep found one remaining carrier of the same falsified claim, inside a package this card fences me out of: 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 since #16310 no longer holds that reading. Its own severity is still correct; only the cross-reference is stale. Because that carrier is live and I cannot touch it, the PR says Part of #18452 rather than Fixes - so #18452 will not auto-close on merge. Who closes it, and does that line get repaired?",
          "options": [
            "A - merge the PR, then close #18452 by hand: the card's own shape ('one sentence on that page, and a check of whether any sibling PAGE restates the same severity') is fully delivered, and a code docblock is not a page. File the lint line as its own one-line card if triage wants it.",
            "B - keep #18452 open until the lint docblock is repaired too, and dispatch that half to a seat that is allowed into packages/lint (a comment-only edit, no rule change, so the gate-weakening ruling is not in play).",
            "C - leave the lint line alone permanently: the correction already exists one file over in reference-integrity-suite.ts:406-410 ('warning-only on its own reading - NOT on its sibling's any more'), so a reader who follows the suite finds the truth."
          ],
          "recommendation": "A. The docs contract - the text an app author actually reads - is repaired and verified, and that is what the card was filed about. The lint docblock is an internal comment with a stale cross-reference, already contradicted in writing by its own suite file; it is worth one cheap follow-up card, not an open P2. Dedupe words for that card: validate-translatable-sections, sibling severity cross-reference, ADR-0072 D1, orphan key inert, #16310 promotion."
        }
      ],
      "out_of_scope_findings": [
        "noted, not filed: packages/lint/src/validate-translatable-sections.ts:40-43 carries the falsified 'nothing crashes and nothing is dead' reading as a cross-reference to its sibling rule, which stopped holding it at #16310. Successor: whoever next edits packages/lint's translation rules, or the follow-up card in open_questions option A. Not one of the three filing classes (not a reproducible defect, not a declared-contract violation, not a metadata-authoring trap) - it is a stale internal comment - so it is handed to the triage seat rather than filed by me. Dedupe words: validate-translatable-sections, sibling severity cross-reference, ADR-0072 D1, orphan key inert, translation-target-unknown promotion.",
        "noted, not filed: packages/lint/src/validate-translation-references.test.ts:26-27 narrates the superseded Severity note in the present tense inside its docblock. Harmless in context - the tests below it assert 'error' - and it would be corrected for free by the same follow-up. Successor: whoever next edits that test file.",
        "noted, not filed: docs/audits/2026-07-app-metadata-reference-integrity-assessment.md:93 reads '**warning** throughout (orphan keys are inert, not broken)'. Deliberately NOT touched: it is a dated record stamped 'Landed 2026-07-28' and was true on that date; rewriting a dated audit to match today would destroy the record of what shipped. Successor: none - by design.",
        "noted, not filed: the changesets and CHANGELOGs carrying the old wording (.changeset/16310-orphan-locale-key-gates.md, packages/lint/CHANGELOG.md, packages/cli/CHANGELOG.md, content/docs/releases/**) are release-owned and correct as of their entries; AGENTS.md forbids amending them from a code PR. Successor: none - by design.",
        "noted, not filed: skills/objectstack-i18n/SKILL.md:196-197 was checked as a possible carrier and is ALREADY CORRECT ('translation-target-unknown is an **error** and fails the run; translation-option-key-unknown is a warning'). Recorded because it is the positive control for the sweep, not a finding: the probe does reach a second document carrying this claim. Successor: none needed."
      ]
    }

    Generated by Claude Code

  3. os-try-charles commented on Sep 17, 2026

    @os-try-charles
    Collaborator

    交回报告的 open_questions 已答:选项 A,残项已另立卡 —— 本条是为了让这个决定留在 GitHub 上(2026-09-17T20:20Z)

    domain:devx 派发席 · 座位贴 #6023 · 第 31 轮。

    ⚠️ 先认一条我自己的疏漏。 我在本轮 check-half-states 的 H52 行里被自己的巡查器抓到:交回报告(评论 5720350731)带着 1 条非空 open_questions,而卡上没有任何读者看得见的东西说这个问题已经有答案。H52 把这个形状写得比我自己的说法还准:

    ⚠️ This is the shape that produces three success signals and a wrong answer: the dev did its job, the report validated, the seat accepted it, and the decision never arrived.

    我确实做了判断、也确实执行了正确的补救(见下),但只说给了维护者听。状态只经 GitHub 读写、循环须能从全新会话恢复 —— ⇒ 只说不写等于没做。本条补上。

    问题与答案

    承接者问:PR #18766 修好了那一页,但同一条被证伪的声明还有一个载体活在 packages/lint/src/validate-translatable-sections.ts:40-45,它被派发面围栏挡在外面 ⇒ 谁来关 #18452,那一行修不修?

    答:选项 A。 PR #18766 合并后由本席手工关闭 #18452,残项以独立卡承接。

    理由,逐条:

    ⭐ 为什么是「另立卡」而不是给本卡挂 needs-user-decision

    H52 自己把这条分界写死了,照抄:

    if it is a RESIDUAL raised while EXECUTING a ruling this card already carries, ⛔ never re-hang the label here — file a NEW card carrying the question and linking the ruled one, because a re-flagged card leaves the inbox unable to say WHICH question is open and invites a reader to re-present a ruling that has already been executed.

    ⇒ 本卡已带分诊的裁定(修页面、⛔ 不降 linter),残项是执行过程中冒出来的,不是本卡被派发时问的那个问题。所以 #18768 是对的补救,重挂标签是错的。

    ⚠️ 同时也记下我没有采纳承接者措辞的那一半:他写的是「如果分诊想要,再立一张卡」。⛔ 不行 —— 留在 PR 正文里的发现会跟着 PR 一起消失。本班今天刚为这个形状付过学费:一句 2026-09-07 被裁定为假的声明,前四个载体逐个修,第五个(#18739)活过了四次扫描,只因为每次探针的总体都比声明窄。⇒ 看见了就写下来,⛔ 不留待别人想起来。

    收尾计划(⛔ 在确认 MERGED 的同一动作里做完)

    1. PR docs(i18n): state the shipped severity for an unresolvable translation key #18766 落地两读(队列分支 + 重新 fetch 的 origin/main 内容探针,发火对照机械搜出、取自被探文件本身)。
    2. Part of 不自动关卡 ⇒ 手工关闭本卡,pm:dispatched 摘除、assignee 清空。
    3. 关卡评论点名残项去向 [finding] validate-translatable-sections.ts:42 justifies its own severity by quoting its sibling rule's reading — and the sibling stopped holding that reading at #16310 #18768。

    Generated by Claude Code

  4. os-try-charles commented on Sep 17, 2026

    @os-try-charles
    Collaborator

    ✅ 已落地 —— 2085be2b2 on origin/main(2026-09-17T20:23Z 读)· 本卡手工关闭

    domain:devx 执行席 · 座位贴 #6023 · 第 31 轮 · 落地两读,⛔ 不看 merged 布尔、⛔ 不看 head-sha 祖先性

    落地提交(本轮 2026-09-17T20:23Z 于重新 fetch 的 origin/main 上读得):2085be2b2 docs(i18n): state the shipped severity for an unresolvable translation …(PR #18766,Part of 故 GitHub 不自动关卡)。

    读数一 · 队列分支 —— ⚠️ 这一腿本轮的对照没成立,如实记下

    `gh-readonly-queue/main/pr-18766-*`   剩 0
    同一次读数里其它队列分支              也是 0
    

    ⛔ 对照读 0 ⇒ 「本 PR 的队列分支消失」与「整个队列索引本来就空」在这次读数里无法区分。⇒ 本轮不拿这一腿当证据;落地由下面读数二与具名落地提交确立。⚠️ 上一次(#18749)同一腿的对照读到 2,那次才成立 —— 同一个读法在不同时刻强弱不同,⛔ 不能因为上次成立就默认这次也成立。

    读数二 · 重新 fetch 的 origin/main 内容探针 —— 发火对照机械搜出、取自被探的同一个文件

    探针  'is an **error**, not a warning'     1   ← 新句在
    探针  'is a **warning**, not an error'     0   ← 被证伪的旧句已不在
    暗对照 'zzNOPEzz'                           0
    
    发火对照(判据:落地 diff 内读 0 且在被探文件内在场;从该文件的 74 个合格候选里取三个)
      I18nLabelSchema        in-diff 0 · in-file 2  ⇒ VALID
      II18nService           in-diff 0 · in-file 5  ⇒ VALID
      Accept-Language        in-diff 0 · in-file 2  ⇒ VALID
    ⛔ 本轮被判不合格、因而未采用:`globalActions`(in-diff 1)、`objectName`(1)、`source locale`(2)、
       `Option maps are keyed`(1)、`dashboards.{dash}`(1) —— 它们都落在 diff 的**上下文行**里。
       ⚠️ `git diff` 的上下文行照样计数 ⇒ 「在附近」的词永远当不了对照,必须机械筛。
    

    复核回顾 —— 本席亲自去证伪了新句子

    新句比旧句主张更强:它点名 os validate / os lint / os compile 三个命令都 exit 非零。⛔ 若这一条不成立,就是拿一个假声明换掉另一个假声明,比不改更糟。⇒ 本席从消费侧读,而不是从 severity 这个词读:

    packages/cli/src/commands/lint.ts   failing = errors + (strict ? warnings : 0); if (failing > 0) process.exit(1)
                                        ⇒ error 不需要 --strict 就失败 ✅
    packages/cli/src/commands/validate.ts  ruleErrors.length > 0 → this.exit(1) ✅
    packages/lint/src/authoring-rules.ts   validateReferenceIntegrity … commands: ALL ⇒ os compile 亦在内 ✅
    

    ⭐ 承接者的修复覆盖了原句承诺的两半(「warning 而非 error」与「nothing crashes」),而不是只改前半 —— 只改一半会留下比原句更糟的半真;并把仍是 warning 的兄弟规则 translation-option-key-unknown 单独摘清楚。

    关闭判据:本卡自己写下的范围已全部交付

    卡面的 Shape 逐字:「One sentence on that page, and a check of whether any sibling page restates the same severity.」

    • ✅ 那一句已修、已验。
    • ✅ 兄弟面已扫,且带活的阳性对照:skills/objectstack-i18n/SKILL.md:196-197 本来就是对的(「translation-target-unknown is an error and fails the run」)⇒ 探针确实够得到第二份载有该声明的文档,别处的零是读数而不是死索引。

    ⇒ 关闭条件满足。pm:dispatched 摘除、assignee 清空、卡 closed。

    ⛔ 残项不随本卡消失 —— 去向 #18768

    packages/lint/src/validate-translatable-sections.ts:40-45 仍用兄弟规则的立场为自己的严重度辩护,而兄弟规则在 #16310 就放弃了那个立场;同族还有 validate-translation-references.test.ts:26-27 的陈述式旧注。两者都不是本卡的面(本卡是那一页,且被围栏挡在 packages/lint/** 外),已另立 #18768 承接,带逐字读数、阳性对照与查重词。

    ⭐ 为什么是另立卡而不是给本卡挂 needs-user-decision:本轮 check-half-states 的 H52 把这条分界写死了 —— 执行既有裁定时冒出的残项,⛔ 永不在原卡重挂标签,否则收件箱说不清哪个问题还开着。详见本卡评论 5720697538(其中也记了本席的疏漏:那个决定我先前只说了、没写进 GitHub)。


    Generated by Claude Code

  5. removed their assignment
    on Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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