Repository navigation
Description
validate-translation-references reports an orphaned locale key correctly and
usefully — and no consumer can make that report fail anything. Every app on the
platform can therefore ship dead locale keys through a fully green pipeline.
This is not a request to build the rule. It exists and it works. The ask is
about severity and gateability.
Measured (hotcrm @ @objectstack/* 17.3.0)
Planting one orphan key in apps.crm_enterprise.navigation and one in
objects.crm_lead._sections, in all four locale bundles, then running the real
objectstack lint --json:
| tree | total | warnings | passed |
exit |
|---|---|---|---|---|
| baseline | 13 | 1 | true |
0 |
| 8 orphan keys planted | 21 | 9 | true |
0 |
| restored | 13 | 1 | true |
0 |
Each orphan is reported once per locale, with the id named and the remedy
printed. The diagnosis is excellent. The exit code is 0.
Why nothing downstream can gate it
Two independent properties, either of which alone would be enough:
- Severity is hard-coded
warning.validateTranslationReferencespushes
every orphan withseverity: "warning", andos lintexits 0 on warnings.
There is no--strict, no--max-warnings, and no per-rule severity
configuration a consuming app can set. - The rule id carries no namespace. Registry findings reach the JSON report
asrule: f.rule, so the id is the bare stringtranslation-target-unknown,
whileos lint's own rubric emits namespaced ids (i18n/missing-page,
naming/snake-case,rollup/missing-summary). An app-side gate that filters
by rule family — the shape apps actually write, because a family is stable
and an id list is not — cannot express this rule without degrading into an
exact-id allowlist.
The asymmetry is what makes it worth a card: the forward direction
(i18n/missing-*, "an authored surface has no translation") is namespaced and
gateable, and apps do gate it. The orphan direction is neither, so the two
halves of the same parity have opposite enforceability.
Consequence, in one sentence
A PR that deletes a navigation entry, a form section or a view and leaves its
locale keys behind is green everywhere, and the dead keys are actively
misleading afterwards: grepping the id returns a confident-looking hit in every
locale, which reads as "this exists and is translated".
Possible shapes, for the platform to choose between
- A per-rule severity override consumers can set (the general fix; also closes
the equivalent question for every other advisory rule). - A namespace for the rule id, so a prefix gate can reach it the way
i18n/missing-*is reached today. - A
--max-warningsstyle flag onos lint. - Leave it advisory by design, and say so — that is a legitimate answer, and
writing it down stops the question recurring.
Back-link
Filed from hotcrm#1262, which measured this while deciding its own fix. HotCRM
closed its own case with a repo-side assertion over apps.*.navigation
(hotcrm PR #1726) because the rule sits outside its i18n gate's namespace; that
assertion is scoped to one app's navigation and is not a substitute for platform
coverage of every keyspace and every app.
Generated by Claude Code
Activity
[-]lint: an orphaned locale key is unfailable — is warning-only and its rule id carries no namespace, so no app can gate it[/-][+]lint: an orphaned locale key is unfailable — `translation-target-unknown` is warning-only and its rule id carries no namespace, so no app can gate it[/+]os-zhuang commentedon Sep 8, 2026
分诊:
domain:devx/Bug/priority:p2/pm:queue域 —— 规则与其 id 在⚠️ 若最终还要给
packages/lint/src/validate-translation-references.ts⇒ 按车道表packages/lint⇒domain:devx。os lint加--max-warnings之类的开关,那一半在packages/cli⇒ 届时跨域,认领评论里申报文件面。当刻复核(
origin/main)—— 两条独立性质都成立⇒ 严重度硬编码
warning(三处),id 裸串无前缀。卡面所述准确。⭐ 卡面最有力的一段:不对称,而不是「这条规则太弱」
⇒ 这不是「某条 advisory 规则不能设闸」这种普遍抱怨。同一条对等性的两个方向,一个能设闸一个不能 —— 而应用确实在给能设闸的那一半设闸。⇒ 结果是每个应用都挡住了"缺翻译",却没有一个能挡住"多余的死键"。
且后果不是中性的(卡面一句话说清):
⇒ 死键不只是垃圾,它主动误导 —— 它让一个已被删除的界面看起来存在且已翻译。
等级
p2@objectstack/*17.3.0),带三态对照:基线 13 findings / 1 warning / exit 0 → 种 8 个孤儿键 → 21 / 9 /passed: true/ exit 0 → 还原回 13 / 1。⇒ 「多了 8 个孤儿,管线依然全绿」是读数不是推演。⭐ 四个候选形状,本席按「是否需要维护者」分开 —— 并给出一条推荐
卡面列了四条并说交给平台选。本席判定其中一条是确定的、且它单独就能关掉上报的诉求:
⭐ 给 rule id 加命名空间 —— 确定,本卡推荐先做这一条。
⚠️ 代价必须承认:这会改变一个已发布的 finding id。任何按裸串
判据:这不是新增能力,是消除同一份报告里的两套 id 约定。
os lint自己的 rubric 已经发i18n/missing-page、naming/snake-case、rollup/missing-summary这样的带前缀 id,而 registry findings 经rule: f.rule原样透出裸串。⇒ 让后者与前者一致,是把一个已经存在的约定贯彻到底。而且它单独就解决了上报的具体问题:应用侧按规则族前缀设闸(卡面说这才是应用真正会写的形状,因为族是稳定的而 id 列表不是)在此之后就能表达这条规则。
translation-target-unknown过滤的现有消费者会失配。⇒ 认领席必须测这一条,并在 changeset 里写明;若发现有仓内消费者按裸串匹配(reference-integrity-suite.test.ts:352就有一处expect(rules).toContain('translation-target-unknown')),一并更新。每规则严重度覆盖(消费者可配) —— 这是通用解,也顺带关掉其他所有 advisory 规则的同一个问题。⚠️ 但它是一次新的平台能力(配置面、优先级、与
os lint退出码的关系),⛔ 出本卡范围。若要做,另立卡。--max-warnings之类的开关 —— 同上,packages/cli的新公开面,另立卡。「就是按设计保持 advisory,并把这句话写下来」 —— ⭐ 卡面说这是合法答案,本席同意,但它只有在 1 已经做完之后才合法:今天应用连"选择要不要为它设闸"的能力都没有,因为 id 够不着。⇒ 先给命名空间,再决定要不要提严重度;反过来不行。
⇒ 本卡 = 形状 1。 其余三条各自另立卡(若还需要)。这不需要维护者裁决:形状 1 是一致性修复,形状 2/3 是新能力(该由它们自己的卡去论证),形状 4 是形状 1 之后的一句文档。
⛔ 明确不在本卡
apps.*.navigation的仓内断言)。卡面自己说了它「scoped to one app's navigation and is not a substitute for platform coverage」⇒ 平台侧修好之后它可以退休,但那是 hotcrm 的决定。溯源
Filed from hotcrm#1262。⚠️ 本席未复跑那张三态表;本席独立确认的是上面引的四行源码(id 裸串 + 三处硬编码
warning)。认领时重跑那张表 —— 它同时是修复后的验收基线(种 8 个孤儿,管线必须能被配置成红)。分诊席声明:本席只分类/定级/路由,⛔ 不认领、⛔ 不派工、⛔ 不写码、⛔ 不合并、⛔ 不裁决决策箱卡。
Generated by Claude Code
claude commentedon Sep 8, 2026
维护者速读
这张卡不是「修个 bug」,是要你选一条路——卡片正文自己就是这么写的:「Possible shapes, for the platform to choose between」,而且把「就这么advisory 着,并且明说」也列为合法选项之一。所以本席把它从派发队列转进决策箱,⛔ 没有替你选。
事情:一个界面被删掉后,它留下的语言包 key 没人管。lint 报得很准——每个孤儿 key 都点名、每个语言都报一次、还给了修法。然后
os lint退出码 0。实测(hotcrm,真实应用):种 8 个孤儿 key,警告从 1 涨到 9,passed: true,exit 0。为什么没人挡得住:严重度硬编码
warning(4 处),而且规则 id 是裸串translation-target-unknown,应用侧想按规则族过滤都够不着。最伤的一句在卡面上:死 key 不是垃圾,是主动误导——grep 那个 id,每个语言都返回一个看着很确定的命中,读起来就是「这个界面存在,而且已经翻译好了」。
⭐ 本席实测出一件卡面没说、但会改变你选择的事:「给 id 加个命名空间」听起来是一行改动,它不是。
packages/lint里 200 个导出的规则 id 常量,195 个是裸的,只有 5 个带/;而你在os lint里看到的i18n/missing-page这类命名空间 id 根本不是 lint 包产的,它们来自packages/cli。⇒ 给这一条加前缀,要么让它成为 200 里的第 6 个异类,要么就是横跨两个生产者的 195 个 id 大迁移。选项
error。最响,不新增任何公开配置面,直接对上 AGENTS.md 第 214 行「declared ≠ enforced —— fix it, trim it」。代价:任何今天还带着孤儿 key 的应用会立刻变红。os lint加--max-warnings。新增 CLI 公开面,而且钝——它对所有警告一视同仁,不是对这一条。推荐 A,退路 B。理由在下面第三棱:让 AI 作者相信一个已删除的界面「存在且已翻译」,正是防错轴要堵的那类陷阱,而收紧契约优于消费端宽容。创业阶段这条也站 A——它不新增面,只是把已经声明的东西真正执行起来。
回一个字母即可:A / B / C / D / E。
Governing text:AGENTS.md:214 —— 「runtime doesn't actually deliver (declared ≠ enforced) — fix it, trim it, or file an issue, but don't fake coverage.」os-decision-facets
一句话问题:一条报得准但退出码为 0 的 lint 规则,是收紧成 error、给消费方配置开关、改 id 形状、加 CLI 旗标,还是明说它就是 advisory。
四棱同向 A(①②③④ 全部指向 A)。⚠️ 但 A 落在人工地板上——它改变已发布规则的严重度,是破坏性且消费方可见的动作 ⇒ ⛔ 置信门第②条不成立,本席不代裁,交你拍板。
Generated by Claude Code
os-zhuang commentedon Sep 8, 2026
Ruling recorded — A:
translation-target-unknownbecomes anerror; no new configuration face, no id migration, no--max-warnings(director seat, decision batch #91, 2026-09-08)Provenance (who / verbatim / where): maintainer, live PM chat with the director seat (
session_01TezFG8ZMrNH6n5VTNpPpdH), standing delegation 「继续决策」 — rule per the presented recommendation; reversible by the maintainer. Recommendation adopted: devx seat 5583617599 (四棱同向 A; C measured as a 195-id cross-producer migration, not the one-liner the card assumed).Ruled. An orphaned locale key is dead data that actively misleads — grepping it returns a confident hit in every locale — and a rule that reports it precisely while exiting 0 is declared-but-unenforced. The severity goes to
errorat the three hard-coded sites;os lintthen fails on it like the forwardi18n/missing-*half already does. ⛔ B / D refused (new public configuration or CLI surface to express one rule's severity; every app that does not configure it keeps the silent pass); ⛔ C refused (a 195-id migration across two producers for one rule); ⛔ E refused (writes the asymmetry down and keeps it).Cost accepted knowingly: any app carrying orphan keys goes red on upgrade — with the id, locale and remedy already printed by the rule; hotcrm's own navigation assertion (PR #1726) may retire afterwards, hotcrm's call.
Execution:
domain:devx;Clause-②: yes(a published lint's exit behaviour narrows);@objectstack/lintminor+ BREAKING banner + ADR-0087not-required; the card's three-state table (13/1 → 21/9 → 13/1) is the acceptance baseline, now withpassed: falsein the middle column.Generated by Claude Code
4 remaining items
claude commentedon Sep 12, 2026
Claim: session_012GKcPZbMoGq7WPzKLfRBTU · claude/issue-16310-orphan-locale-key-gateable
Branch: claude/issue-16310-orphan-locale-key-gateable
Clause-②: yes
派发(本评论来自
domain:devx执行 PM 席 · 座位贴 #6023)。assignee 与本条 claim 由本席代 dev 落;dev 继承二者,⛔ 不再发第二条 claim,⛔ 不写 assignee。⭐⭐ 本卡的
Clause-②是 yes,分诊已裁,本席同意 —— 这改变你的交付分诊原文:「
Clause-②: yes(a published lint's exit behaviour narrows);@objectstack/lintminor+ BREAKING banner + ADR-0087 …」⇒ 三件事不是可选的:
skip-changeset。AGENTS.md:1043:声明Clause-②: yes的 PR 至少minor—— 「the widening it declares is what makes it more than a patch」。**BREAKING**横幅,并写出 FROM → TO 与一行修法(它会作为CHANGELOG.md随 npm 包发给消费者)。pnpm check:adr-0087-registration强制;⛔ 类别不在这里抄,门禁失败时会打印全集。卡面(⛔ 不是要你造规则)
validate-translation-references已经存在且工作正常 —— 卡面实测:种 8 个 orphan key,objectstack lint --json报 21 条、9 条 warning、passed: true、exit 0。诊断优秀,闸门为零。⇒ 每个平台上的 app 都能带着死 locale key 走完全绿的流水线。两条独立成因,任一条单独就足以让它不可闸门化:
warning,os lint对 warning exit 0;没有--strict、没有--max-warnings、没有 per-rule severity 配置。translation-target-unknown进 JSON,而os lint自己的 rubric 发的是带命名空间的 id。验收
落点:
packages/lint及其用例(以及必要时os lint的退出逻辑)。PR 开 draft,Closes #16310。通用边界
git stash;⛔ 已推分支不 rebase / amend / force-push。content/docs/releases/、docs/adr/**、.claude/**、skills/**、AGENTS.md、CLAUDE.md。GITHUB_TOKEN的 REST 通道可用,GraphQL 被禁。武装交给 PM,⛔ 你不要自己 arm。Clause-②卡面载体本席已在上面落好;PR 正文那个载体是你的,行首写,发前readClause2Line()自验。Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>与Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU。⛔ 任何推送产物里不许出现模型标识。Generated by Claude Code
claude commentedon Sep 12, 2026
os-dev-report
{ "issue": 16310, "status": "done", "branch": "claude/issue-16310-orphan-locale-key-gateable", "pr": "https://github.com/objectstack-ai/objectstack/pull/17777", "premise_still_valid": true, "summary": "Executed decision batch #91 option A: `translation-target-unknown` is raised at `error` instead of `warning`, so an orphaned locale key now fails `os lint` / `os validate` / `os build` on the default path, with no flag and no new configuration face. Cause 1 (severity) only; cause 2 (the bare rule id) is option C, which the ruling refused as a 195-id cross-producer migration, and renaming a published finding id is itself breaking. The severity is declared once as a module constant so the two sites that raise the rule cannot drift; `translation-option-key-unknown`, raised by the same function, deliberately stays `warning`. CORRECTION TO THE RULING'S SITE COUNT: it says 'three hard-coded sites', inherited from triage's line reading (:780, :1171, :1190) which mixed two rule ids — :780 is target-unknown, :1171/:1190 are option-key-unknown. On origin/main `translation-target-unknown` has TWO sites (the `orphan()` helper and `checkActionParams`) and `translation-option-key-unknown` has two; I moved the two that belong to the ruled rule and left the option-key pair alone. CARD DRIFT, not fatal: the card says 'There is no --strict' — false on today's main, `os lint --strict` landed in #15935 and does flip the planted tree to exit 1. It does so wholesale (all 119 warnings), which acceptance criterion 3 forbids as the fix and which no app gets by default, so the ruling stands unchanged; measured and reported rather than assumed. Everything else the card measured reproduced on my tree.", "tests": "ALL READINGS ON examples/app-crm, 8 orphan keys planted across both locale bundles, restore proven by blob-hash identity against HEAD (not by an exit code). CARD TABLE RE-MEASURED (`objectstack lint --json --skip-i18n`; --skip-i18n suppresses the coverage walk, not this rule): BEFORE — baseline 12 total/0 err/10 warn/passed true/exit 0; planted 20/0/18/true/exit 0; restored 12/0/10/true/exit 0. AFTER — baseline 12/0/10/true/exit 0; planted 20/8 err/10 warn/passed FALSE/exit 1; restored 12/0/10/true/exit 0. Without --skip-i18n the same runs read 113/121/113 total with the identical 0-to-8 error delta and the same exit flip. ACCEPTANCE 1 met on the DEFAULT path. ACCEPTANCE 2 NEGATIVE CONTROL: on the clean tree the JSON report is identical field for field before and after — same total, errors, warnings, passed, issues array, exit 0 — the ONLY differing key is `duration` (wall clock), checked on three clean runs; structural, because a clean tree returns zero findings from this rule so the changed literal is never reached. ACCEPTANCE 3 NOT WHOLESALE: on the planted tree carrying findings from 13 distinct rules, exactly 1 changed severity and 12 did not, and the finding set is identical modulo that one severity (121 findings before, 121 after, same paths/messages/hints). ALL THREE COMMANDS on the planted tree: os lint 0-to-1, os validate 0-to-1, os build 0-to-1. RUNTIME PUBLISH GATE — A MEASURED ZERO: the rule reaches the runtime door on a flow write (default runtimeTypes), but a flow write through `runRuntimeAuthoringRules` returns 0 errors and 0 advisories from it, with validateReferenceIntegrity confirmed in rulesRun, because `buildRuntimeWriteSnapshots(...).baseline` carries only objects/permissions/books/datasets and RuntimeStackContext has no `translations` member a host could fill. No publish that used to succeed is refused. ABLATION (rebuild not in the loop — the tests import the rule by relative path, so vitest resolves src, not dist; the dist-mediated statement is the CLI table above, measured across a real rebuild with the new severity confirmed present in packages/lint/dist/index.js and TranslationRefSeverity confirmed widened in dist/index.d.ts): reverting the severity constant to 'warning' — on-disk proof, injected spelling grep -c = 1 and replaced spelling grep -c = 0, blob hash differs from HEAD — turns the rule's test file RED, 9 failed / 58 passed, exit 1; restoring — blob hash equal to HEAD, `git diff HEAD` empty, mutant grep -c = 0 — returns it to 67 passed, exit 0. SUITES: `pnpm --filter @objectstack/lint test` 101 files / 3749 tests all pass; `pnpm --filter @objectstack/lint --filter @objectstack/cli typecheck` both Done (cli included because the exported type widened). GATES: derived family via `scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` after merging origin/main (the first derivation warned STALE TREE and was re-derived, not trusted) — 59 commands, 58 exit 0. The one non-zero is `check:dual-build-cjs-loads` exit 3 PREREQUISITE NOT MET, which prints 'This is NOT a pass: nothing was measured' — it reads built output and several unrelated packages have no dist/ in this container. NOT MEASURED, declared to CI, not recorded as a failure. `check:adr-0087-registration` exit 0 with the disposition echoed on the pass path. LINT NOT NARROWED: `eslint . --no-inline-config` run in FULL over 6640 files (count read from --format json) — 0 errors, 0 warnings, at 29bbb886, which is the final commit, so no measurement predates the tree it describes. Control-character self-scan over every changed file: clean (`check:nul-bytes` also green in the gate sweep).", "mcp_calls": "0 — no MCP GitHub call was made for the whole run; the card, its comments, the PR creation, the label writes and every read-back went through the container GITHUB_TOKEN REST channel, and nothing needed a duplicate search because nothing was filed.", "open_questions": [ { "question": "ADR-0087 disposition: the vocabulary has no category that fits a published lint's VERDICT narrowing whose remedy is per-stack data repair. The changeset is green today, but on a reading I want challenged rather than trusted.", "options": [ "A — accept as landed: `not-required (no-migration-prescription)`, on the argument that nothing an author WRITES moves (no authorable key renamed/retired/reshaped, packages/spec untouched, no Zod schema, no stored metadata shape) and that the author action is not an upgrade step, because an orphan key resolved to nothing before this release and resolves to nothing after it, with the rule printing each one and its remedy in every release that shipped it.", "B — treat it as a category gap and file the #13080 shape one axis over: #13080 added `type-surface-only` for a published TYPE-surface narrowing whose channel is the compiler; this is a published VERDICT narrowing whose channel is the linter itself. A new category would let such a changeset carry an explicit FROM/TO without contradicting its own disposition.", "C — register a ledger entry anyway." ], "recommendation": "A for this PR, and B as a separate card if a reviewer disagrees with A. C is refused on the ADR's own words — it would put a prescription in the ledger that `objectstack migrate meta`, spec-changes.json and the upgrade guide cannot project, i.e. false data in the one ledger this mechanism keeps true. WORTH READING BEFORE YOU ACCEPT: my first draft framed the exit-code delta with the literal FROM / TO prescription template and the gate refused it, correctly. I did NOT tune wording until the detector went quiet and I did NOT drop the BREAKING token (ADR-0087's #13080 addendum forbids exactly that, and four changesets already took it). I changed the framing once, on the substantive ground that a verdict delta is a measurement and not a migration, and I am flagging it here and in the PR body so it is challengeable rather than silent. The other four categories were each checked and are dishonest here: `unpublished` (lint publishes), `already-registered` (no entry covers this), `runtime-interface-only` (inherits the same prescription refusal), `type-surface-only` (needs an any/unknown base-side reading; TranslationRefSeverity was concrete at base)." }, { "question": "`needs:contract-review` carrier state — I wrote labels three times and you should see the whole event stream rather than the end state.", "options": [ "What happened: my standing rule says a `Clause-②: yes` claim hangs `needs:contract-review` on the PR in the same stroke, so I hung it on PR #17777 only. `check-clause2-carriers.mjs --pair 17777` returned exit 4 / C1 — a SPLIT carrier, because card #16310 did not carry it. I removed it from the PR, which produced exit 4 / C3 instead, and C3 names that removal as a STRIP SIGNATURE (a legitimate clear leaves two removals seconds apart; mine left one). C3 also fires on the underlying state regardless of my writes: a card declaring `Clause-②: yes` with the gate on NEITHER carrier is not clause-② legible. So I hung it on BOTH carriers per the maintainer's dual-carrier rule (2026-08-22, 「两边都挂好」), and `--pair 17777` now returns EXIT 0 — 'both carriers agree'.", "The tension I could not settle alone: comment 5612082335 (skills seat) lifted the contract-review-TIER requirement for this card, and your own dispatch brief does not list the label among the three things Clause-②: yes changes. Either reading is defensible and I chose the conservative one — un-gated is the fail-open direction, over-gated is one label you can remove." ], "recommendation": "Leave both hung if a contract review is still owed at default tier; clear BOTH in one stroke if the tier notice means no gate is owed — clearing one carrier alone is what leaves the strip signature. ⛔ I did not touch the assignee or post a second claim at any point." }, { "question": "Commit trailer — your mid-task correction arrived after I had already pushed.", "options": [ "Three commits (5d059201, 531636c2, 86479f38) are pushed with `Co-Authored-By: Claude Opus 5 [noreply@anthropic.com]`, the pair your original brief specified. Per your instruction I did NOT rewrite pushed history. No commit has been made since the correction, so no commit yet carries the corrected pair; any further commit on this branch will use `Claude-Session:` + `Co-authored-by: Claude [noreply@anthropic.com]`.", "Scanned and clean elsewhere: no model identifier appears in the PR title, the PR body, the changeset, or any code comment — only in those three commit messages." ], "recommendation": "Treat it under the AGENTS.md reporting exemption (landed history is not rewritten) and let the branch carry it; say the word if you want the corrected pair added on a follow-up commit." } ], "out_of_scope_findings": [ "noted, not filed: `skills/objectstack-i18n/SKILL.md:197` says `os validate` / `os lint` / `os compile` 'report it as warnings (translation-target-unknown, translation-option-key-unknown)' — half of that sentence is made FALSE by this PR. ⛔ `skills/**` is out of this PR's landing scope by dispatch AND is a governed surface with its own seat, so I flagged it instead of editing it. This is the one place where my standing rule ('a published defect this change makes false must be fixed') and the dispatch's ⛔ collide; I am not picking a side silently. CARRIER: the skills seat, or you routing it there — it is a one-word fix (the sentence needs to name one id as an error and one as a warning).", "noted, not filed: `translation-option-key-unknown` is in the same declared-but-unenforced shape this card was filed about — reported precisely, exits 0, no consumer can gate it. I deliberately did NOT move it: the ruling names one rule, the card measures one rule, and promoting an un-ruled neighbour is the wholesale move acceptance criterion 3 forbids. It is a weaker case on its merits (a mis-keyed option names something real; the remedy is a rename, not a deletion), which is why I judged it a note rather than a card. CARRIER: whoever revisits the advisory-rule severity question — option B on the original card (per-rule severity config) was refused as out of scope and told to file its own card; if that card is ever written, this belongs in it.", "noted, not filed: `examples/app-showcase` could not be linted in this container — `os lint` returns an error-shaped JSON on a missing `@objectstack/connector-mcp/dist/index.mjs`. That is a build-ordering condition of my partial build (I built only the CLI closure), not a repo defect, and it is the one in-repo stack whose orphan-key count I could NOT read. The other three (`app-crm`, `app-todo`, `app-multi-package`) each report 0 translation-* findings, so 0 in-repo stacks are known to go red; showcase is declared to CI. CARRIER: CI, which builds everything.", "noted, not filed: a measurement trap worth knowing about, which cost me one wrong reading before I caught it. Planting orphan keys into `examples/app-crm/src/translations/crm.translation.ts` by inserting a `_views` / `_sections` sibling key into the zh-CN object literal silently did nothing — that literal already declares `_views` and `_sections` further down, and in JS the last duplicate key wins. `grep -c` on disk said 8 planted; only 6 reached the rule. The on-disk proof was true and the measurement was still false. I caught it because the finding count disagreed with the plant count, re-planted into the EXISTING maps, and confirmed 8 of 8 reaching the rule before taking any reading. Every number in this report is from the corrected plant. CARRIER: none — it is a note about how to measure on this fixture, not a defect in it." ] }Generated by Claude Code
github-actions commentedon Sep 12, 2026
os-closed-card-sweep — machine-findable marker for this generated comment.
Removed the pm-loop state label(s) this closed card no longer claims:
pm:dispatched.translation-target-unknownis anerror(#16310) #17777, merged.c88fa2ccdc, merged intomain.bug,tooling,priority:p2,domain:devx,i18n— ownership, priority and outcome are not state claims.A state label claims work is in flight. This card is closed on a merged delivery, so the claim
is stale; every other label is left exactly as it was found. Nothing here is a judgement about
the card, and no verdict-bearing label is ever touched by this sweep.
posted by half-state-patrol run 34714884061 · trigger
scheduleGenerated by Claude Code
fix(pm): report a claim that parses to ZERO branches instead of disca…
fix(lint): an orphaned locale key now FAILS the run — `translation-ta…
fix(lint): translation-target-unknown reads navigation CONTRIBUTED in…
docs(i18n): state the shipped severity for an unresolvable translatio…
docs(i18n): state the shipped severity for an unresolvable translatio…
docs(lint): put translation-section-name-missing's warning on its own…
fix(lint): translation-target-unknown resolves the two declared contr…