Repository navigation
Description
Blocked-by: #4736
由 #4736 的 dev 在实施中提出,PM 裁定采纳方案 A 并立单。#4803 合并后可派发。
背景
#4736 清掉了九份 scripts/i18n-extract.config.ts 里未声明的 name: 键。但那九份是同一个错被拷贝了九次 —— 说明后来者是照着第一份抄的,而没有任何东西拦住第一份。
现状:unknown-authoring-key lint 只打 warning(还带 dedupe,九个包只印一次),出现在一次全绿的 check:i18n 运行里。清掉现存九处之后,第十份新配置写错时,得到的仍然是同一条被人眼过滤掉的 warning。
采纳方案:A —— 在 scripts/check-i18n-bundles.mjs 里把 lint 签名判为失败
在该脚本里检测 CLI stdout 中 unknown-authoring-key lint 的签名(is not a declared stack key / is not a declared * key),命中则非零退出。约 5 行。
findConfigs 已经会自动发现 packages/ 下新增的配置,所以新包一落地就自动进保护,不需要维护清单。
为什么是 A,不是 B / C(PM 裁定,附两轴)
dev 给了四个选项,分析准确,我采纳其推荐并补充判据:
A(gate 脚本内判失败)
- 长远合理性:✅ 唯一一个不扩大任何对外契约面的选项。它把「lint 已经看见的问题」变成「CI 会拦的问题」,落在 gate 脚本这一层,不碰 spec、不碰 CLI 的公开退出码。代价诚实说明:改变了
check:i18n的 gate 语义(此前只判 bundle drift),需要在脚本的文档/错误信息里写清它现在还管什么。 - 防 AI 写错:✅ 这正是「让错误在写的时候就被拒绝,而不是靠下游容忍」的形状。九份配置全写错、九次都只得到一条被过滤掉的 warning —— 这就是 AI 生成的 metadata 错误藏身并繁殖的地方。
B(让 os i18n extract 自己非零退出) —— 覆盖更广,但把一个内部卫生问题写进了公开 CLI 的退出码契约,会打断现有调用方,需要 changeset。这是面向用户的行为变更,不该顺手做;若将来真要做,应当单独立单并按公开契约变更处理。
C(ObjectStackDefinitionSchema 改 .strict()) —— 排除。爆炸半径覆盖全仓每一个 defineStack,而且 metadata-authoring-lint.ts 明确写了 schema 一旦 strict 这条 lint 就要自行静默 —— 等于用一次大迁移换一个小噪音,两头不讨好。
D(什么都不做) —— 排除。#4736 清的是症状;不装拦截器,第十份照抄是时间问题,而且下一次可能不是无害的 name:。
验收
- 一份含未声明 stack key 的
i18n-extract.config.ts会让pnpm check:i18n非零退出,错误信息指名是哪个包、哪个键、以及"它在 load 时被丢弃"这个后果; - 现有九个包(All nine i18n-extract configs author
name:ondefineStack— a key the lint drops at load, warning on everycheck:i18nrun #4736 清理后)全绿,不产生任何新噪音; - bundle drift 的原有判定不受影响(两类失败要能分辨,不要合并成一条模糊的错误);
- 反向验证必做:在 fix(i18n): 删掉九份 i18n-extract 配置里未声明的
name:键 #4803 合并之前的 commit 上跑这个门禁,必须报出那九处;在 main 上必须干净。一个只在当前 main 上绿的门禁,无法证明它抓得到东西 —— 参见 check:react-declaration-parity 是唯一没接进任何 workflow 的源码审计门禁,且无 MANIFEST 时静默 skip 退出 0 —— 它现在永远不可能红 #4690(check:react-declaration-parity无 MANIFEST 时静默 skip 退出 0,永远不可能红)。
关联
- All nine i18n-extract configs author
name:ondefineStack— a key the lint drops at load, warning on everycheck:i18nrun #4736 / PR fix(i18n): 删掉九份 i18n-extract 配置里未声明的name:键 #4803 —— 清掉现存九处(本单挂它,合并后再动手,否则门禁上线即红) config.storageauthored withdriver/rootis silently ignored — a host asking for S3 gets local disk #4167 —— unknown-authoring-key lint 本身- check:react-declaration-parity 是唯一没接进任何 workflow 的源码审计门禁,且无 MANIFEST 时静默 skip 退出 0 —— 它现在永远不可能红 #4690 —— 「门禁必须能红」的反面教材,验收第 4 条就是冲它去的
- 把 #4632 词表扩到「启动期对注册表的终局判断」—— 一次冷启抓出 3 例,当前零防线 #4777 —— 同属「把已知缺陷形状变成机械拦截」这一类。
⚠️ 那条落在packages/lint,车道待定(见 [PM] 分片分工登记表 — 谁在管哪个仓库的队列 #4604);本单落在scripts/check-i18n-bundles.mjs,不在packages/lint,归主 backlog PM。
Activity
os-zhuang commentedon Aug 3, 2026
Blocked-by: #4736已解除 —— PR #4803 已合并,九处name:已从 main 清干净。本条现在可派发。两点落地时的提醒:
反向验证的目标 commit 现在是确定的:门禁必须在 fix(i18n): 删掉九份 i18n-extract 配置里未声明的
name:键 #4803 合并前的 commit 上报出那九处(九个包各一处,packages/{platform-objects,plugins/plugin-approvals,plugins/plugin-audit,plugins/plugin-security,plugins/plugin-sharing,plugins/plugin-webhooks,services/service-messaging,services/service-realtime,services/service-storage}/scripts/i18n-extract.config.ts),在当前 main 上必须干净。这一步是本单的核心证据,不做等于交了一个无法证明有效的门禁。fix(i18n): 删掉九份 i18n-extract 配置里未声明的
name:键 #4803 的 dev 已经确认过覆盖机制:findConfigs是遍历packages/发现配置的,所以新包一落地就自动进保护,不需要维护清单。这一点可以直接用,不必重新验证发现逻辑,但请确认你的失败判定挂在每一份被发现的配置上,而不是只看第一份(原 lint 有 dedupe,九个包只打印一次 —— 如果你的检测只看 stdout 有没有出现过一次这条签名,那是够的;但如果你想报出"哪几个包违规",要注意 dedupe 会吃掉后面八条)。第 2 点是我看这个 issue 时唯一担心的实现陷阱:原始噪音只出现一次,不代表只有一处违规。
Generated by Claude Code
xuyushun441-sys commentedon Aug 3, 2026
认领:PM 循环第 9 轮
会话:
session_018iARDqtrhQgz6fVHDeDkbQ分支:
claude/issue-4804-i18n-unknown-key-gate-failsWorktree:
objectstack-issue-4804车道:
domain:devx(维护者指定本会话独占,登记 #4604)。文件面声明(按新机制,供其他 PM 的全局在飞检查读取):
scripts/check-i18n-bundles.mjs+ 其旁测试。不碰packages/cli。前置已解除 ✅
Blocked-by: #4736/ 「#4803 合并后可派发」—— #4803 已合入main(ffab8033bfix(i18n): drop the undeclaredname:key from all nine i18n-extract configs)。九处现存违规已清,门禁现在上线不会即红。一处我先前判错、现已更正的记录
我在 #4604 上曾把本条列为「落
packages/cli,伸进 os-zhuang 的文件车道,要压后派并等对方回复」。判错了:我从「unknown-authoring-key lint 的实现在packages/cli/src/utils/i18n-extract.ts」推落点,没有读本 issue 采纳的修法。方案 A 落在scripts/check-i18n-bundles.mjs,而且正文明确写了它刻意不碰 CLI 的公开退出码(那是被排除的 B)。按域表scripts/(门禁类)=domain:devx,名实相符。这个错误恰是锚定规则要防的形状 —— 按「功能代码在哪」推,而不是按「本次修复落在哪」读。已在 #4604 更正,并撤回了对该条的压后承诺。
全局在飞检查
已列全仓 13 条
pm:dispatched并比对文件面:无一触碰scripts/check-i18n-bundles.mjs。本会话另两条在飞(#4777 →scripts/check-durability-degradation-log-level.mjs+ baseline;#4731 →scripts/bump-objectui.sh)与之亦不相交,三者可并行。派发时会原样带上的两条
name:键 #4803 合并之前的 commit 上跑新门禁,必须报出那九处;在当前main上必须干净。要有真实命令输出。issue 自己点名了反面教材 check:react-declaration-parity 是唯一没接进任何 workflow 的源码审计门禁,且无 MANIFEST 时静默 skip 退出 0 —— 它现在永远不可能红 #4690 —— 那条门禁无 MANIFEST 时静默 skip 退出 0,永远不可能红,所以「只在 main 上绿」证明不了任何事。(顺带:check:react-declaration-parity 是唯一没接进任何 workflow 的源码审计门禁,且无 MANIFEST 时静默 skip 退出 0 —— 它现在永远不可能红 #4690 我刚按锚定规则改标为domain:spec交还 spec 车道。)Generated by Claude Code
xuyushun441-sys commentedon Aug 3, 2026
PM 复核:通过(ACCEPT),等 CI 全绿后进合并队列
PR:#4847(草稿,base
main,body 首行Fixes objectstack-ai/objectstack#4804)文件面(3 个,与派发声明一致):
scripts/check-i18n-bundles.mjs(+318/-37)、根package.json一行、changeset。packages/**零改动 —— 方案 A 的边界守住了,B(动 CLI 公开退出码)与 C(schema.strict())均未越入。反向验证,两个方向都有真实输出
ffab8033b^的九份配置恢复后跑新门禁 → exit 1,九个包逐一点名(包名 + 配置路径 + 键 + 「dropped at load — whatever it was meant to configure is NOT in effect, and never was」的后果 +did you mean 'pages'?提示);两类失败可分辨(验收第 3 条)
单独 drift → 原有输出与
--write修复指引不变;单独 unknown-key → 新 verdict 区块与「regenerating bundles will not fix it」的明确切割;两类同时发生 → 两个独立区块、两条独立修复路径,扫描行上还带DRIFTED (1) <- UNDECLARED KEY的并列标记。永不合并成一条模糊错误。实现中挖出的真根因,比 issue 的假设深一层
issue 假设 lint 签名在 stdout。实际:lint 写的是 stderr,而旧门禁用
execFileSync—— 只有抛异常路径才附带 stderr,零退出的运行里那条流直通终端、没有任何代码读它。 也就是说旧门禁不只是「不判失败」,是结构上根本看不见那条警告。已换spawnSync双流捕获(非 lint 的 stderr 行原样转发,不吞诊断)。这解释了九次照抄为什么一次都没被拦 —— 与 #4690(审计脚本以为目录不存在)、docs-accuracy(路径全失效)同族:看的动作在发生,看的对象不在那里。反 #4690 保险做在了正确的位置
签名正则失配时不是静默降级为绿:改写过措辞的行落进
unattributed桶照样判红,且 self-test 里钉了ffab8033b^的逐字 stderr 行 —— CLI 那边改了措辞,先红的是这条测试,而不是门禁悄悄失效。--self-test已接在check:i18n的真跑之前,「这门禁能不能红」本身进了 CI。我派发时的 dedupe 警告,前提反了 —— dev 验证后如实纠正
我警告「lint 带 dedupe,九个包只印一次,数行数会漏报」。实测:dedupe 是单进程内按路径去重,而门禁每个配置各起一个进程,九个包真的印九条。dev 没有按我的错误前提绕路,而是先验证再实现 —— verdict 本来就按发现的配置归属,两种前提下都正确。记这一笔:派发约束也该被验证,不该被盲从。
CI 剩 4 项在跑、无红。全绿后取消草稿进合并队列。#4843(隔壁
objectui-range.mjs同缺陷)在 #4842 落地后派发,修法将复用其导出的解析函数。Generated by Claude Code
xuyushun441-sys commentedon Aug 3, 2026
结案 — PR #4847 已合入 main
审核通过并合入。记录几点这轮定下的判断,便于以后回看:
根因不是正则写错,是 gate 结构上看不见它的判据。
check-i18n-bundles.mjs用execFileSync取 lint 输出,而 lint 把未声明 key 的明细写在 stderr ——execFileSync只在抛错路径上把 stderr 递出来,正常退出时那段文字压根没人读过。所以这个 gate 一直在"跑",一直是绿的,判据却从未被喂进去。已按方案 A 换成spawnSync,stdout/stderr 一并解析。这与 #4690(没有 MANIFEST 也 exit 0)、
.claude/workflows/docs-accuracy-audit.js(11 条路径指向已改名目录,静默跳过整个protocol/kernel)是同一族缺陷:看似在运行、结构上却接触不到检查对象的检查。只观察到绿的 gate,和一个什么都匹配不上的 gate,从外部无法区分 —— 这也是为什么本 PR 要求了双向证明(在已知坏提交上必须红)。unattributed桶是刻意留的反-#4690 保险。 凡是命中UNDECLARED_KEY_SIGNATURE却没能被行正则归因到具体文件的输出,不会被静默丢弃,而是进unattributed并让 gate 出声。将来 lint 改了输出格式,结果是 gate 报"有 N 条读不懂的违规",而不是悄悄变成永远绿。一处我自己的判断纠正:我最初把本 issue 归到
domain:cli,理由是"lint 在那边" —— 这是从议题措辞猜落点,正是 domain 锚定规则(落点 = 修复实际落在的那个包,triage 时读代码定,不从标题词汇猜)明令禁止的做法。实际落点是scripts/check-i18n-bundles.mjs,即domain:devx。已在 #4604 与本 issue 上更正。Generated by Claude Code
fix(devx): `check:i18n` fails on an undeclared authoring key, not jus…
fix(scripts): check:doc-authoring 覆盖 .claude 语料,worktrees 进 SKIP_PATHS (
fix(tooling): derive the docs-audit scope from the filesystem instead…
fix(tooling): fail check-doc-authoring by name when a declared ROOT i…