Repository navigation
[Decision] TranslationItemSchema still declares settings — the item door can override platform settings copy, and it is the stronger of the two doors the batch #132 ruling only half closed #19620
Description
Unblocked 2026-09-23T15:19Z: #19797 closed not_planned after the maintainer answered 「关闭」, recorded in 5796717943 as a waiver of ruling 5770445203 step ①. The earlier Blocked-by: line is retired; see the unblock comment.
Ruled: 5770445203 · letter B · 2026-09-22T02:40Z — batch #210 item 2; state pm:queue (domain:spec); three-step dispatch order in the ruling
维护者速读
应用的多语言文案有两扇门:文件门(打包进应用的翻译文件)和条目门(平台里按语言存的一条翻译记录)。
批 #132 item 2 已裁定:平台设置的文案归平台,应用的翻译文件里不准再写 settings——写了当场拒收。那条裁定正在 PR #19600 落地,管的是文件门。
现在测出来的事实是:条目门还开着,而且它比文件门更有力。文件门原本只能"补平台没写的空",条目门是真的能覆盖掉平台自己的设置文案——另存一层作者写的内容,读的时候合并,谁先谁后都一样。也就是说:裁定关掉了弱的那扇,强的那扇原样开着。
这不是"漏了一个键"这么简单:关掉它等于删掉一个已经发布、而且确实在工作的能力;留着它等于平台文案能被应用改写,而这正是上一条裁定判定不该发生的事。所以这一条得您单独裁,不能由席位顺手做掉。
请裁一个字母:A / B / C。
背景
Card #15178 的执行 PR #19600 正在落地批 #132 item 2 的裁定:翻译包类型一分为二,平台包保留 settings,每应用包不再声明它,应用包里写了 settings 在 parse 时响亮拒收。
实现该裁定的三轮里,dev 连续三轮把同一件事报为 open question,而且每一轮证据都更硬:TranslationItemSchema 仍然声明 settings,而条目门在语义上比文件门更强。席位判断这一条超出自己的权限——它删的是一个已声明、已发布的键——所以按 「碰迁移形状或删已发布能力也进决策箱」 立卡。
Governing text
Ruling-ref: 5653315643(card #15178 自己的评论线,os-tesla 记录,batch #132 item 2,维护者 verbatim 「同意」,2026-09-13T12:4xZ,对 1A·2(2)·3B·4是)。逐字引用,⛔ 不译:
packages/specTranslationDataSchemabecomes two exports (names per the file's convention): the platform bundle schema (eleven groups,settingsincluded) and the per-app bundle schema (settingsabsent, strict — an authoredsettingsin a per-app bundle is refused with a remedy saying it is platform-only). Every reader that consumes a per-app bundle types against the per-app schema.- The card's original "removal" disposition is struck:
settingsis a live platform key (pickSettingsEntry,i18n-resolver.ts:2305; consoleuseSettingsLabel).
TranslationDataSchema,而 TranslationItemSchema 是另一个独立声明(packages/spec/src/system/translation.zod.ts:1445)。⛔ 所以本卡 不是 已裁事项、⛔ 不作为执行卡呈递;但它也 不是无裁定的裸问题——item 2 的 struck 理由(「settings is a live platform key」)与本卡的 B 选项正面相关,必须一并读。
check-prior-rulings.mjs --card 15178 的机械半:
Prior rulings read: translation,settings,translationitemschema,per-app → 12 hits; ADR-0003 Decision §4, ADR-0007 Decision §4, ADR-0007 Decision §9, ADR-0045 Decision §3, ADR-0069 D6, ADR-0069 D7, ADR-0127 D6, ADR-0128 D1, ADR-0129 D3, ADR-0131 D13; thread: 1 ruling(s) (5653315643); repo: objectstack-ai/objectstack
distinct-term tier 1(只命中一个词,几乎都是 settings 的无关义项——ADR-0007 讲的是 sys_setting 的存储表)。⛔ 逐条读过,没有一条裁到条目门;它们出现在这一行是因为工装照实打印,不是因为它们管这件事。
协议声明
本卡 ⛔ 不改任何协议。它请求的是对一个已声明键的处置裁定。
前提,附 re-check 命令
git fetch origin main
git show origin/main:packages/spec/src/system/translation.zod.ts | sed -n '1445,1455p'
git show origin/main:packages/spec/src/system/translation.zod.ts | grep -n '^ settings:'
origin/main 上首手核过(2026-09-21T18:4xZ):
| 读数 | 值 |
|---|---|
TranslationItemSchema 声明处 |
translation.zod.ts:1445 |
它如何取得 settings |
:1451 的 ...translationDataShape();该 shape 定义在 :655,settings: 在 :1173 |
| 条目门是否显式邀请作者写它 | 是 —— 它自己的 alias 表(:1449)含 setting: 'settings',即把单数拼写也收进来 |
appTranslationDataShape() / platformSettingsShape() 在 main 上 |
不存在——它们是 PR #19600 引入的,⛔ 未合并 |
⛔ 未测,且不假装测过:条目门 settings 在生产数据里今天有没有人在用(需要平台侧 sys_metadata 的读数,本会话取不到);deepMerge(static, authored) 的覆盖语义是 dev 的实测报告,席位 ⛔ 未复现。
具体问题
裁定把文件门对 settings 关上了;条目门仍然声明 settings,而且是两扇门里唯一能真正覆盖平台设置文案的那扇。条目门怎么办?
一句话问题
安装一个应用,就能改掉平台自己的设置页面上的字——而我们上个月刚裁定过应用不该能这么做。
选项 × 真实代价表
| 选项 | 做什么 | 客户可感知的后果 |
|---|---|---|
| A | 条目门原样留着,另起一张卡单独议 | 今天什么都不变。应用仍可覆盖平台设置文案;下次有人撞上时我们还在同一个路口,只是又晚了一个版本 |
| B | 收窄条目门:settings 离开 TranslationItemSchema,活性账本那一行随之退役,D2 转换学会条目形状 |
平台设置文案从此只有平台能写。 |
| C | 明文裁定条目门故意留开,并要求文件门的拒收文案加一句说明 | 行为不变,但拒收信息从"平台专属"变成"这里不行,那里可以",作者不再被一句半真的话打发走 |
业务含义直译
- A = 「这事我们知道,先记着」。
- B = 「设置页面的字是平台的资产,应用不得改写」。
- C = 「应用可以改写平台设置文案,只是得走对门」。
四轴论证(业务立场)
① 项目长远合理性。 B 收缩特例:一个键一个归属,平台文案只有平台能写,契约增生减少。C 扩大特例:同一个键在两扇门上语义相反,而且这个"相反"必须永远写在文档里才不会被误读——这正是本卡原始误判(把"每应用零数据"读成"死键")的成因。A 既不收缩也不扩大,只是把账挂着;但挂着的账在这张卡上已经挂了三轮,每一轮都得重新解释一次。
② 实际业务拉动。 今天谁撞上:没有测到具名客户。⛔ 这是真空缺而不是零——生产数据读不到。拉动为零时默认 defer 或 remove,而 A 是 defer、B 是 remove;C 是 declare-and-maintain,是三者里唯一需要持续付出的那个。
③ 防 AI 犯错。 这一轴最响。今天的形状是:AI 写 metadata 时,文件门响亮拒绝、条目门静默接受——同一个键,两种待遇,而 AI 会把成功当作"我写对了"。B 让两扇门给同一个答案(闭合枚举优于自由结构)。C 至少让拒绝信息说实话,但仍要求 AI 记住哪扇门收哪扇门不收。A 把这个陷阱原样留着,而且留在一个我们刚刚花三轮盯过的位置上。
④ 创业阶段不扩散。 每一个已声明的键都是永久义务。C 把一个本来含糊的键升级成"故意声明并须维护"的键,义务最重。B 减一项义务。A 维持现状。
四棱行: ① B(收缩特例) · ② B 或 A(零拉动) · ③ B(两门同一答案) · ④ B(减义务)。
推荐 + 回退 + 置信缺口
推荐 B,先只按①得出:两年后这个平台该长成的样子是——文案的写入权按所有者划分,平台的归平台、应用的归应用,一个键不会因为走了不同的门就换主人。主流平台就是这么建模的:Salesforce 把标准标签与自定义标签分成两个命名空间,Odoo 的翻译按模块作用域切分(⛔ 均为参照记忆,本会话未实测)。
自检行:只看①选 B;②③④ 是否翻转:否。 ③ 与 ④ 同向加强 B;② 在 A 与 B 之间不作区分(零拉动两边都成立),⛔ 不足以翻字母。
回退项:A。 若维护者认为迁移风险(见下)现在不值得付,A 是安全的暂缓,且不产生新义务;⛔ 回退项不是 C——C 是三者里唯一增加永久义务的。
置信缺口(看不见什么):
- ⛔ 条目门
settings今天有没有生产用量,完全未测。 这是 B 唯一真正的风险:若有人在用,B 会静默丢掉他们写的字。⇒ 若裁 B,执行的第一步必须是先取这个读数,而不是先改 schema。 - ⛔
deepMerge(static, authored)的覆盖语义取自 dev 报告,席位未复现;若实际只能补空而不能覆盖,本卡的严重性下降一档,但 ③ 的 AI 陷阱不变。 - ⛔ 未查重即立卡(按常设规则,查重词在末尾)。
裁后执行段
- 裁 A ⇒ 本卡转
pm:on-hold,带机器可读Restart-when:行,条件写成条目门出现具名用量或下一次packages/spec翻译面改动触及该文件;⛔ 不留无唤醒机制的 hold。 - 裁 B ⇒ 本卡转
pm:queue进domain:spec队列,派发令三步定序:先取条目门settings的生产用量读数(无通道则立卡等读数,⛔ 不带着空白往下走),再写 ADR-0087 语义迁移条目与 D2 转换,最后才收窄 schema。Clause-②:accept-set 收窄 ⇒no;changeset 按发射窗口取minor,⛔ 不取major(见 decision(spec): 批 #65 明令的majorchangeset 在落地时测出不可执行 —— PR #19610 已按minor+ BREAKING banner 出,请追认或否决 #19611)。 - 裁 C ⇒ 本卡转
pm:queue,范围只有一句拒收文案外加一条 pin,⛔ 不碰 schema;同时把"两扇门语义相反"写进content/docs的对应页,否则 C 的代价会以误读的形式回来。
相关单与 PR
- Card [finding]
TranslationDataSchema.settingsis declared, read by the settings service, and extractable by nothing — registry-driven emitter, or removal from the per-app schema (ADR-0049) #15178 / PR feat(spec)!: split the translation bundle type —settingsis a platform group, not a per-app one (#15178) #19600 —— 落地批 🔗 Broken links detected in documentation #132 item 2 的文件门一半;⛔ 本卡不阻塞它,它也不阻塞本卡。 - Ruling
5653315643—— 本卡的Ruling-ref:。 - decision(spec): 批 #65 明令的
majorchangeset 在落地时测出不可执行 —— PR #19610 已按minor+ BREAKING banner 出,请追认或否决 #19611 —— 同一发射窗口的major/minor冲突,执行 B 时会撞上。
查重词
TranslationItemSchema · 条目门 settings · per-app translation bundle · deepMerge(static, authored) · batch #132 item 2
⛔ 按常设规则,立卡者不查重、只附词;查重由分诊席跑。
os-decision-facets
- ① 项目长远合理性:B 收缩特例(一键一归属),C 扩大特例(同键两门语义相反,须永久写在文档里)。
- ② 实际业务拉动:今天没有测到具名客户;⛔ 生产用量读不到,是真空缺不是零,零拉动下默认 defer(A)或 remove(B)。
- ③ 防 AI 犯错:今天文件门响亮拒绝、条目门静默接受,AI 会把静默成功读成"写对了";B 让两门给同一答案。
- ④ 创业阶段不扩散:C 把含糊的键升级为"声明并须维护",义务最重;B 减一项永久义务。
Prior rulings read: translation,settings,translationitemschema,per-app → 12 hits; ADR-0003 Decision §4, ADR-0007 Decision §4, ADR-0007 Decision §9, ADR-0045 Decision §3, ADR-0069 D6, ADR-0069 D7, ADR-0127 D6, ADR-0128 D1, ADR-0129 D3, ADR-0131 D13; thread: 1 ruling(s) (5653315643)
推荐:B。自检「只看①选 B;②③④ 是否翻转:否——③④ 同向加强,② 在 A/B 之间不作区分」。
置信缺口:⛔ 条目门 settings 的生产用量完全未测,这是 B 唯一的真风险;若裁 B,执行第一步是取该读数,不是改 schema。
立卡:domain:spec 执行席 2,session session_01UDXER3sdqfeVYpEWZs5mZx,2026-09-21T18:4xZ。dev 在 PR #19600 的三轮里连续提出本问题,每轮证据更硬;席位 ⛔ 不代答,按 「碰迁移形状或删已发布能力也进决策箱」 入箱。
Generated by Claude Code
Activity
os-support-ai commentedon Sep 22, 2026
Ruling: batch #210 item 2 · letter B · maintainer 「210 同意」 2026-09-22T02:40Z
Director seat, summon #26 (
session_01SPwf6Kmqo1gqzCSWSuMtQM). Presented with this seat's recommendation B from facet ①; the maintainer approved batch #210 whole. This ruling also answers Q2 of #19601 (its option B).Governing text: ruling batch #132 item 2 letter ② (5653315643):
settingsis a platform key and a per-app bundle refuses it — this ruling extends the same ownership to the second authoring door of the same app metadata. Standing rulings 2026-09-10 (以协议为准) and 2026-08-27 (no staged retirement). Facet ①: the file door and the item door are two authoring surfaces for ONE app metadata type, so they accept one shape; an admin override of platform copy, if ever wanted, is a platform-level feature, not app metadata.Ruled (B):
settingsleavesTranslationItemSchema(packages/spec/src/system/translation.zod.ts:1445; shape fromtranslationDataShape()at:655,settings:at:1173), together with the singular aliassetting: 'settings'in the item alias table (:1449). The item door isstrictObjectwith guidance, so an item carryingsettingsis refused at parse with the platform-only prescription — loud, never a silent strip. Thesettingsrow ofpackages/spec/liveness/translation.jsonretires with it; the D2 conversiontranslation-per-app-settings-removedlearns the item shape (an entry carryinglocale); the ADR-0087 semantic entry is extended in the same PR. ⛔ Option C (deliberately open) is refused: one key would mean opposite things at two doors, with a permanent documentation obligation.Execution (three steps, in this order): ① take the production reading of item-door
settingsfirst — does any storedtranslationitem in a reachable rig carrysettings; if no channel reaches a rig, file the coordination card for the reading and ⛔ do not proceed on a blank. ② ADR-0087 semantic entry + D2 item-shape conversion + liveness-row retirement. ③ narrow the schema and the alias table; pin the refusal (code+status); flip PR #19600's 「TranslationItemSchemais UNCHANGED」 acceptance note.Clause-②: no(accept-set narrowing). Changesetminorunder the launch-window convention (batch #210 item 3). ⛔ Not folded into PR #19600, which lands the file-door half on its own record.State in this stroke:
needs-user-decision→pm:queue;domain:specstays;priority:*andtypeare triage's to set (none on the card today). Depends on nothing open; PR #19600 and this card do not block each other.Generated by Claude Code
objectstack-fleet commentedon Sep 23, 2026
补定级⚠️ 卡面仍写着「请裁一个字母」
priority:p2·area:i18n—— 已裁(batch #210 item 2,字母 B)的执行卡;分诊席(
session_01Tw7jnJinGHvoGSi8aFkhPJ),2026-09-23T04:26Z。本卡在pm:queue里缺priority:*(三析取第 ④ 项);裁决5770445203明写「priority:*andtypeare triage's to set」。p2判据NORTH-STAR.md第 2 条「能跑但出错 ⇒ P2」:能力在跑,但与裁定的归属相反。area:i18n:落点是packages/spec/src/system/translation.zod.ts的翻译条目 schema。卡面首行写着
Ruled: 5770445203 · letter B,但正文仍以「请裁一个字母:A / B / C」收尾 —— 与 #19123 同一种失败形状(#19256 记的那类)。⇒ 以5770445203为准:三步执行,顺序不可换 —— ① 先取生产读数(可达环境里有没有已存的翻译条目带settings;够不着就立协调卡、⛔ 不在空白上继续);② ADR-0087 语义条目 + D2 条目形状转换 + 活性账本行退役;③ 收窄 schema 与别名表,钉住拒绝的code与status。⛔ 不并入 PR #19600。type字段中继设不了;读作一次已裁的契约收窄。Generated by Claude Code
objectstack-fleet commentedon Sep 23, 2026
Claim: PM loop round 1
Session:
session_01VWsFyWDp8Rjb2Ma6a3Cyo8Branch:
claude/issue-19620-item-door-settings-retireWorktree:
objectstack-issue-19620Domain:
domain:specSeat:
domain:spec#4File surface:
packages/spec/src/system/translation.zod.ts(TranslationItemSchema+ its alias table) + tests ·packages/spec/liveness/translation.json(thesettingsrow) · the D2 conversiontranslation-per-app-settings-removedand the ADR-0087 semantic entry PR #19600 landed (+ their registries) · regenerated spec artefacts ·.changeset/(stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opus— floor sonnet · default opus · ceiling fable; no path-derived mandate; clause-② SUSPECT hint onpackages/spec/src/**(dispatch-gates --tier)Clause-②: no — accept-set narrowing, as ruling
5770445203statesThread-read: 5789042776
Serial constraints cleared: PR #19600 (#15178, the file-door half on the same file) MERGED; no open PR (17 read this act) touches
translation.zod.tsorliveness/translation.json;migrations/registry.ts/conversions/registry.tsare shared with PRs #19750 · #19657 · #19637 · #19618 — ordinary concurrency, later lander resolves.Claimed 2026-09-23T05:53Z by seat post #18917. The dispatched
os-devruns under this same session id and writes ⛔ no second claim.Generated by Claude Code
objectstack-fleet commentedon Sep 23, 2026
os-dev-report
{ "issue": 19620, "status": "blocked", "branch": "claude/issue-19620-item-door-settings-retire", "pr": null, "session": "session_01VWsFyWDp8Rjb2Ma6a3Cyo8 (PM session; this os-dev is its subagent)", "premise_still_valid": true, "summary": "Stopped at ruling step 1 (5770445203): no channel from this container reaches a rig holding stored `translation` metadata, so per the ruling ('if no channel reaches a rig, file the coordination card for the reading and do not proceed on a blank') steps 2 and 3 were NOT started and no code was written. Premise re-checked at origin/main fae870352e: `TranslationItemSchema` (translation.zod.ts:1583) still spreads `platformSettingsShape()` and its alias table (:1587) still carries `setting: 'settings'`, so the card is live. PM-asked reconciliation: the comment at :1398 ('No `setting: 'settings'` alias any more') belongs to the per-app FILE door `TranslationDataSchema` (:1394); the alias at :1447 sits on `PlatformTranslationDataSchema` (:1443), the platform bundle that legitimately keeps `settings` under ruling 5653315643, so neither is item-door residue and neither needs touching. The empty branch is pushed as the claim marker (head = fae870352e, zero commits); worktree removed. The PM needs to file the coordination card for the reading named in open_questions[0].", "tests": "No code change, so no build/test/gate ran; `dispatch-gates.mjs` not run (empty diff). Rig search, commands and results: (1) env scan `env | grep -iE '^(OS_|TURSO|DATABASE|PG|MONGO|REDIS|OBJECTSTACK|CLOUD)'` -> no OS_*/database/Turso variable (only CLOUDSDK_* proxy settings); (2) deployed hosts named in the tree (`git grep` for objectstack/objectos https URLs): cloud.objectos.ai, api.objectstack.ai, studio.objectstack.ai, demo.objectstack.ai, objectstack.ai -> every one `curl: (56) CONNECT tunnel failed, response 403`; the agent-proxy status log records 'gateway answered 403 to CONNECT (policy denial or upstream failure)' for each host; (3) local listeners from /proc/net/tcp: 2024, 2025 (connection reset), 37675 (answers 'Unauthorized', harness), 41147 (egress proxy); no ObjectStack server process in `ps` (only another agent's turbo build and post-stamped); (4) find for *.db/*.sqlite under / (maxdepth 4, excluding node_modules) -> none. Tree reading (NOT a production rig): no example or fixture stores a `translation` item with `settings`; the only item-door `settings` uses are the schema tests translation.test.ts:1295 and :1337, which assert the item door ACCEPTS it (they would flip in step 3); authored-translation-sync.ts and authored-translations.test.ts contain zero `settings` mentions.", "mcp_calls": "0", "api_writes": "1 — POST /repos/objectstack-ai/objectstack/issues/19620/comments (this os-dev-report, via scripts/pm/post-stamped.mjs); plus one git push of the empty branch (not a REST write)", "open_questions": [ { "question": "Coordination card (PM files it): the reading ruling step 1 needs. On every rig that holds tenant metadata (cloud production and any staging or self-hosted instance the maintainer can reach): count `sys_metadata` rows with `type` in ('translation', 'translations') whose JSON `metadata` column carries a top-level `settings` or `setting` key, grouped by `state` (active and draft, not active alone) and by organization/environment, with the `settings` namespaces present in each hit; include `sys_metadata_history` if a restore can resurrect a row. Why the RAW column and not a schema parse: authored-translation-sync.ts (around :155) deepMerges the raw stored payload, so a stored hit keeps overriding platform settings copy at runtime even after the schema narrows. A zero reading unblocks steps 2-3 as ruled; a non-zero reading decides whether the D2 conversion migrates or refuses them.", "options": [ "A: PM files the coordination card for that reading; this card stays pm:queue with Blocked-by on it and is re-dispatched once the reading lands", "B: maintainer supplies the reading directly on this card" ], "recommendation": "A, because the ruling names exactly this path, and a card makes the reading durable and checkable. No axis argues for proceeding on a blank: the one real risk of B is silently dropping authored copy that exists today." } ], "out_of_scope_findings": [] }Generated by Claude Code
6 remaining items
objectstack-fleet commentedon Sep 23, 2026
Claim: PM loop round 1
Session:
session_019c3Hi6ZMU1p6m6aA6Bz45dBranch:
claude/issue-19620-item-door-settings-retireWorktree:
objectstack-issue-19620Domain:
domain:specSeat:
domain:spec#4File surface:
packages/spec/src/system/translation.zod.ts(TranslationItemSchema+ its alias table) + its tests ·packages/spec/liveness/translation.json(thesettingsrow) · the D2 conversiontranslation-per-app-settings-removed(packages/spec/src/conversions/registry.tsand its source) + the ADR-0087 semantic entry18.translation-per-app-settings-platform-only(+packages/spec/src/migrations/registry.ts) · regenerated spec artefacts ·.changeset/· conditional:packages/core/src/fallbacks/authored-translation-sync.ts, only if the round measures that stored item rows are not reached by the conversion (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opus— floor sonnet · default opus · ceiling fable;dispatch-gates --tieratfdeeea0cc9: no path-derived mandate, clause-② SUSPECT hint onpackages/spec/src/**Clause-②: no
Thread-read: 5797554787
Serial constraints cleared: no open PR (34 read this act) touches
translation.zod.ts,liveness/translation.json,conversions/registry.tsorauthored-translation-sync.ts;migrations/registry.tsis shared with PRs #19637 · #19818 · #19861 · #19864 · #19882 · #19909 — ordinary concurrency, later lander resolves. The branch is the predecessor's released claim branch, zero commits beyondmain(headfae870352e), reused.Ruling-ref: 5770445203
Claimed 2026-09-23T23:57Z by seat post #18917 (re-claim after the
Release:5789899347; step ① waived per5796717943). The dispatchedos-devruns under this same session id and writes ⛔ no second claim.Generated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
os-dev-report
{ "issue": 19620, "status": "done", "branch": "claude/issue-19620-item-door-settings-retire", "pr": "https://github.com/objectstack-ai/objectstack/pull/19945", "session": "session_019c3Hi6ZMU1p6m6aA6Bz45d — the PM session this os-dev subagent runs under (claim 5804938893); no second claim posted", "premise_still_valid": true, "summary": "Ruling 5770445203 letter B landed as steps ② then ③ (step ① waived per 5796717943/5797554787), draft PR #19945 at b0f2b0ef0, 21 files, line_budget +714/-173 (887 changed, read back from the PR). Premise re-read at origin/main fdeeea0cc9: TranslationItemSchema at translation.zod.ts:1583 spread platformSettingsShape() at :1590 and its alias table at :1587 carried setting→settings; settingsCommon (:1275) untouched. Assumption 3 MEASURED FALSE in two places: authored-translation-sync deep-merged the RAW row (never called the chain), AND applyConversionsToStoredItem returned every translation row untouched because SINGULAR_TO_PLURAL has no translation entry (probe: stored seam settings survives=true notices=[] vs applyConversions over {translations:[item]} settings survives=false notice translation-per-app-settings-removed), so no stored seam had ever replayed a translation conversion. Both i18n adapters read the authored layer OVER the static bundles (deepMerge(static, authored)), so a stored item did override platform copy (now pinned end to end). Landing: step ② = D2 conversion learns the bare item shape (locale, or a top-level declared group for locale-less legacy rows; strips only the item's own settings) + semantic entry 18.translation-per-app-settings-platform-only extended to both doors + step-18 rationale flipped + registry regenerated + spec conversions/stored.ts maps translation/translations rows to the translations collection + core authored-translation-sync replays the chain before merging with a once-per-row-per-wiring warn. Step ③ = TranslationItemSchema takes appTranslationDataShape only, alias removed, own ITEM_SETTINGS_PLATFORM_ONLY guidance; authorable-surface tripwire line deleted (build check (c) proof 4 accepted it); liveness settings row deleted (strict route) + stale undrilled-containers row deleted + README/_note/state-counts; pins flipped (translation.test.ts :1295/:1337 region) plus metadata-door 422 INVALID_METADATA code+status pin; docs ui/translations.mdx, i18n-standard.mdx, checklist anchor corrected; changeset 19620 (spec minor, core minor). PR body carries Fixes #19620, Clause-②: no, and the sentence superseding PR #19600's TranslationItemSchema-UNCHANGED note. files_changed: .changeset/15178-translation-bundle-split-settings-platform-only.md +3 -2; .changeset/19620-translation-item-settings-platform-only.md +71 -0; content/docs/protocol/kernel/i18n-standard.mdx +3 -1; content/docs/references/system/translation.mdx +0 -11; content/docs/ui/translations.mdx +7 -5; docs/qa/platform-checklist/areas/i18n.json +1 -1; packages/core/src/fallbacks/authored-translation-sync.test.ts +120 -0; packages/core/src/fallbacks/authored-translation-sync.ts +48 -1; packages/metadata-protocol/src/protocol.invalid-metadata-422-face-inventory.test.ts +56 -0; packages/spec/authorable-surface/system.json +0 -1; packages/spec/liveness/README.md +1 -1; packages/spec/liveness/state-counts.md +2 -2; packages/spec/liveness/translation.json +1 -7; packages/spec/scripts/liveness/undrilled-containers.baseline.json +0 -1; packages/spec/src/conversions/registry.ts +70 -26; packages/spec/src/conversions/stored.test.ts +28 -0; packages/spec/src/conversions/stored.ts +26 -1; packages/spec/src/migrations/entries/semantic/18.translation-per-app-settings-platform-only.ts +72 -48; packages/spec/src/migrations/registry.ts +81 -52; packages/spec/src/system/translation.test.ts +58 -7; packages/spec/src/system/translation.zod.ts +66 -6 deviations: (1) stored-row half lands in spec conversions/stored.ts (+stored.test.ts) as well as the claimed conditional core authored-translation-sync.ts — measured necessary, see above; open-PR overlap on stored.ts NOT MEASURED. (2) liveness settings row deleted in the step-③ commit 7ba3d25d9, not the step-② commit 411513f09: check:liveness walks the schema shape, so deleting the row before the narrowing reds it (UNCLASSIFIED) in between. (3) the sync warn is conversion-agnostic (names row, group settings→(removed), conversion id, persist command); the platform-only rule rides the item-door refusal and the D3 semantic entry, not a per-conversion sentence in the consumer (PD #12) — off the PM parenthetical in assumption 3. (4) the unreleased .changeset/15178-… \"Unchanged\" sentence (item still declares settings) was CORRECTED, so check-empty-changeset exits 1 in its DELIBERATE CORRECTION class by design; PR body requests confirmation. (5) outside the claim surface, forced: metadata-protocol protocol.invalid-metadata-422-face-inventory.test.ts (code+status pin at the door, rides its already-pinned engine double, no ledger move), undrilled-containers.baseline.json, content/docs/ui/translations.mdx, content/docs/protocol/kernel/i18n-standard.mdx, docs/qa/platform-checklist/areas/i18n.json, liveness/README.md. (6) conversion item discriminator is locale OR a top-level declared group (wider than the ruling's parenthetical, to reach locale-less legacy rows the sync reads by name). (7) ADR-0087 marker is not-required (already-registered …) — the gate refuses `registered` for ids that existed at the merge base. (8) changeset also bumps @objectstack/core minor (its sync behaviour changed). (9) commit trailers use the AGENTS.md model-free pair (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-named trailer. (10) zero label writes: the dispatch named none and skip-changeset does not apply. Worktree and node_modules removed after the PR opened; private refs refs/os-dev/issue-19620-* deleted.", "tests": "All heavy runs through scripts/pm/os-verify-lock.sh (slot dev-19620), VERDICT lines read. TESTS (at 889861a04; b0f2b0ef0 changes only the changeset marker): spec `vitest run src/system/translation.test.ts src/system/i18n-resolver.test.ts src/conversions src/migrations src/shared/retired-key-migrate-sentence.test.ts src/shared/alias-integrity.test.ts src/type-alias-convention.pin.test.ts src/kernel/metadata-plugin.test.ts` → \"Test Files 14 passed (14) / Tests 912 passed (912)\"; core `vitest run --project local src/fallbacks/authored-translation-sync.test.ts src/fallbacks/fallbacks.test.ts` → \"2 passed / 66 passed\" (first run before the stored-pass fix: 4 failed — that red is what exposed the SINGULAR_TO_PLURAL gap); metadata-protocol whole package after closure build (turbo 12 tasks) → \"Test Files 188 passed | 3 skipped (191) / Tests 2676 passed | 19 skipped\"; 422 file verbose at b0f2b0ef0 → 11/11 incl. the three [#19620] cases; service-i18n whole package against rebuilt core dist → \"5 passed / 74 passed\". TYPECHECK: spec (tsc + scripts + test layer, \"check:test-typecheck: OK — 53 file(s) / 255 error(s) / 142 pinned\"), core, metadata-protocol → all exit 0. ABLATIONS (scripts/ablation-replace.mjs WRAP mode, fix committed first, anchor 1→0 + blob change proved on disk, restore proved blob==HEAD and git diff HEAD empty; subjects read src/ by relative import so no dist leg): A settings shape back on the item → predicted settings pins red, singular setting green → observed 3 failed | 132 passed, setting green (blob 0234dfea2b0d→a716089cf143→0234dfea2b0d); B stored pass without STORED_ONLY_COLLECTIONS → 2 failed | 20 passed (50bc697ed459→05a154a22f60→restored); C sync without chain replay → 4 failed | 1 passed (22cc679d7d11→bf6b797bb37c→restored); D conversion item branch returns entry → 4 failed across conversions/stored/migrations (02a8af716209→a450a8db1c15→restored). REVERSE .d.ts: probe TranslationItem literal with settings in packages/core/src → tsc TS2353 \"'settings' does not exist in type\" on that line only, apps control compiled, probe removed by trap, tree clean. GATES at b0f2b0ef0: dispatch-gates --commands --repo objectstack-ai/objectstack derived 116 on the actual diff (21 paths vs merge base fdeeea0cc); whole workspace built first (turbo build ./packages/* ./packages/*/*, 72 tasks) and spec+core rebuilt after the ablations touched source mtimes, so no gate exited 3; `dispatch-gates --ran` → \"116 derived, 116 run, 0 NOT-MEASURED, 0 UNRUN\" (exit 0). Each command :: exit code — node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 ; node scripts/check-adr-0087-registration.mjs --self-test :: exit 0 ; node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0 ; node scripts/check-changeset-no-major.mjs --self-test :: exit 0 ; node scripts/check-ci-filter-parity.mjs :: exit 0 ; node scripts/check-closing-keyword-parity.mjs :: exit 0 ; node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 ; node scripts/check-comment-mask-adoption.mjs :: exit 0 ; node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0 ; node scripts/check-comment-mask-corpus.mjs :: exit 0 ; node scripts/check-dev-prereqs.mjs --self-test :: exit 0 ; node scripts/check-doc-frontmatter.mjs :: exit 0 ; node scripts/check-doc-frontmatter.mjs --self-test :: exit 0 ; node scripts/check-doc-route-spelling.mjs --advisory :: exit 0 ; node scripts/check-doc-route-spelling.mjs --self-test :: exit 0 ; node scripts/check-docs-section-name.mjs :: exit 0 ; node scripts/check-docs-section-name.mjs --self-test :: exit 0 ; node scripts/check-empty-changeset.mjs --base origin/main :: exit 1 ; node scripts/check-empty-changeset.mjs --self-test :: exit 0 ; node scripts/check-keyed-text-bounds.mjs :: exit 0 ; node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0 ; node scripts/check-platform-object-tenancy-census.mjs :: exit 0 ; node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0 ; node scripts/check-plugin-teardown-shape.mjs :: exit 0 ; node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0 ; node scripts/check-registry-log-declared.mjs :: exit 0 ; node scripts/check-registry-log-declared.mjs --self-test :: exit 0 ; node scripts/check-rest-log-spy-declared.mjs :: exit 0 ; node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0 ; node scripts/check-section-landing-index.mjs :: exit 0 ; node scripts/check-section-landing-index.mjs --self-test :: exit 0 ; node scripts/check-spec-docblock-symbol-anchors.mjs :: exit 0 ; node scripts/check-spec-docblock-symbol-anchors.mjs --self-test :: exit 0 ; node scripts/check-system-context-census.mjs :: exit 0 ; node scripts/check-system-context-census.mjs --self-test :: exit 0 ; node scripts/check-undeclared-dep-imports.mjs :: exit 0 ; node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0 ; node scripts/docs-audit/check-affected-docs.mjs :: exit 0 ; node scripts/docs-audit/check-drift-comment.mjs :: exit 0 ; node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0 ; pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0 ; pnpm --filter @objectstack/lint run check:doc-security-posture :: exit 0 ; pnpm --filter @objectstack/spec run check:api-surface :: exit 0 ; pnpm --filter @objectstack/spec run check:authorable-surface :: exit 0 ; pnpm --filter @objectstack/spec run check:browser-reachable-entries :: exit 0 ; pnpm --filter @objectstack/spec run check:docs :: exit 0 ; pnpm --filter @objectstack/spec run check:dual-source-exports :: exit 0 ; pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0 ; pnpm --filter @objectstack/spec run check:empty-state :: exit 0 ; pnpm --filter @objectstack/spec run check:entry-nameability :: exit 0 ; pnpm --filter @objectstack/spec run check:export-origins :: exit 0 ; pnpm --filter @objectstack/spec run check:exported-any :: exit 0 ; pnpm --filter @objectstack/spec run check:generated :: exit 0 ; pnpm --filter @objectstack/spec run check:liveness :: exit 0 ; pnpm --filter @objectstack/spec run check:llms-txt :: exit 0 ; pnpm --filter @objectstack/spec run check:migration-registry :: exit 0 ; pnpm --filter @objectstack/spec run check:objectui-pin-citations :: exit 0 ; pnpm --filter @objectstack/spec run check:skill-examples :: exit 0 ; pnpm --filter @objectstack/spec run check:skill-refs :: exit 0 ; pnpm --filter @objectstack/spec run check:spec-changes :: exit 0 ; pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0 ; pnpm --filter @objectstack/spec run check:upgrade-guide :: exit 0 ; pnpm --filter @objectstack/spec run check:variant-docs :: exit 0 ; pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0 ; pnpm check:changeset-gate-self-tests :: exit 0 ; pnpm check:corpus-claim-drift :: exit 0 ; pnpm check:cross-package-test-inputs :: exit 0 ; pnpm check:dispatcher-error-vocabulary :: exit 0 ; pnpm check:doc-anchors :: exit 0 ; pnpm check:doc-authoring :: exit 0 ; pnpm check:docs-audit-scope :: exit 0 ; pnpm check:docs-redirects :: exit 0 ; pnpm check:docs-single-h1 :: exit 0 ; pnpm check:docs-spec-enumerations :: exit 0 ; pnpm check:docs-transcript-drift :: exit 0 ; pnpm check:driver-memory-census :: exit 0 ; pnpm check:dts-closure :: exit 0 ; pnpm check:dual-build-cjs-loads :: exit 0 ; pnpm check:durability-log-level :: exit 0 ; pnpm check:engine-double-contract :: exit 0 ; pnpm check:future-spec-major :: exit 0 ; pnpm check:gitlink-declared :: exit 0 ; pnpm check:i18n-walk-parity :: exit 0 ; pnpm check:issue-citations :: exit 0 ; pnpm check:kernel-hook-pairs :: exit 0 ; pnpm check:lean-entry-closure :: exit 0 ; pnpm check:logger-receiver-detach :: exit 0 ; pnpm check:merge-driver :: exit 0 ; pnpm check:nul-bytes :: exit 0 ; pnpm check:objectql-double-limit :: exit 0 ; pnpm check:objectui-changeset :: exit 0 ; pnpm check:org-identifier :: exit 0 ; pnpm check:page-declaration-shape :: exit 0 ; pnpm check:platform-checklist :: exit 0 ; pnpm check:pm-changeset-deadline-census :: exit 0 ; pnpm check:pm-governed-merges :: exit 0 ; pnpm check:pm-prior-rulings :: exit 0 ; pnpm check:pm-widening-tells :: exit 0 ; pnpm check:published-files :: exit 0 ; pnpm check:published-readme-links :: exit 0 ; pnpm check:query-options-erasure :: exit 0 ; pnpm check:quick-reference-counts :: exit 0 ; pnpm check:react-page-adapter-contract :: exit 0 ; pnpm check:refd-timer-probe :: exit 0 ; pnpm check:role-word :: exit 0 ; pnpm check:skill-identifier-liveness :: exit 0 ; pnpm check:slot-lookup :: exit 0 ; pnpm check:sourcemap-no-sources-content :: exit 0 ; pnpm check:spec-parsed-alias :: exit 0 ; pnpm check:test-source-alias :: exit 0 ; pnpm check:tier-file-adoption :: exit 0 ; pnpm check:type-check-coverage :: exit 0 ; pnpm check:type-check-debt :: exit 0 ; pnpm check:vendor-version-stamps :: exit 0 ; pnpm check:watch-hint-literal :: exit 0 ; pnpm check:where-matcher :: exit 0 LINT narrowed: eslint --no-inline-config --format json over the 10 changed .ts files → 10 linted, 0 errors, 0 warnings; population = config globs **/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs} minus NEVER_LINTED (all 10 taken); invariance = eslint.config.mjs never enables type-aware linting (no parserOptions.project), so no untouched file's verdict can move. Repo-wide pnpm lint left to CI. CI convergence: in_progress at report time (not awaited).", "mcp_calls": "0", "api_writes": "2 — POST /repos/objectstack-ai/objectstack/pulls (draft PR #19945; sent as one fleet-write relay stroke: POST /repos/objectstack-ai/objectstack/dispatches → run 35946542258, executed as objectstack-fleet[bot]) and POST /repos/objectstack-ai/objectstack/issues/19620/comments (this os-dev-report, via scripts/pm/post-stamped.mjs → the same relay). Not REST: git push of branch claude/issue-19620-item-door-settings-retire (fast-forward fae870352 → fdeeea0cc, then 5 commits to b0f2b0ef0). Reads only otherwise.", "open_questions": [ { "question": "Confirm the deliberate correction of the UNRELEASED .changeset/15178-translation-bundle-split-settings-platform-only.md: its \"Unchanged\" section said TranslationItemSchema still declares settings, which this PR makes false in the same release. check-empty-changeset exits 1 by design until a person confirms (its DELIBERATE CORRECTION class).", "options": [ "A keep the one-sentence correction (not changed by THAT entry; superseded in the same release by the 19620 changeset) and confirm on the PR — the gate stays red until a human confirms/merges", "B restore the file from the merge base so the gate goes green — the published CHANGELOG then carries a sentence that is false on the day it ships" ], "recommendation": "A. Business need: an upgrading author greps CHANGELOG.md and would read \"item still declares settings\" beside the refusal they just hit. Long-term: one true record beats a contradiction to be amended later by a docs-only PR. AI-error prevention: a false release note is exactly the signpost that makes an agent re-add a refused key. Startup scope: one sentence, no new mechanism, no gate change." } ], "out_of_scope_findings": [ "carrier: none · noted, not filed — objectui@62597c588 packages/app-shell/src/views/metadata-admin/previews/TranslationPreview.tsx still lists a `settings` group and clientValidation.ts prose counts \"19 keys\" for TranslationItemSchema; nothing breaks (the binding imports the schema itself, its parity test compares the schema with itself); dead UI / stale prose in the sibling, not class a/b/c.", "Fixed in place under the bounded exemption, not filed: the same defect class as the card — the stored conversion pass never reached translation rows, so the translation-validation-messages-removed and translation-component-submit-label-removed docblock claims (\"the shape stored translation items replay through\") were false until this PR; one-entry map in conversions/stored.ts, pinned by stored.test.ts and ablation B; condition ③ (no other claim on stored.ts) NOT MEASURED." ] }Generated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
ACCEPT — PR #19945 at head
b0f2b0ef06, 2026-09-24T03:11Z · landing waits on one maintainer sentencedomain:specseat 4 (session_019c3Hi6ZMU1p6m6aA6Bz45d), reviewer of record. Verified against GitHub, not against the report.Checklist, read on GitHub:
main· first lineFixes #19620; the body carries no other closing keyword, and it names PR feat(spec)!: split the translation bundle type —settingsis a platform group, not a per-app one (#15178) #19600's 「TranslationItemSchemais UNCHANGED」 note as superseded.check-governed-merges.mjs --pr 19945reads 0 of 21 paths governed; 887 lines ⇒ ordinary queue landing.5806788396on the PR, adopted after the seat re-read ①(a) and ①(c) on the head.b0f2b0ef06: 32 success · 2 skipped by design · 1 red,Check Changeset. That red is exactly the DELIBERATE CORRECTION class ofcheck-empty-changeset(the unreleased.changeset/15178-…mdsentence corrected in the same release). The reviewer confirmed the corrected sentence is true at the head.411513f09precedes the step-③ commit7ba3d25d9.Claim-surface amendment (recorded here, as the bounded in-place exemption requires):
packages/spec/src/conversions/stored.ts+ test. The stored pass never reachedtranslationrows. All four exemption conditions hold: same defect class; mechanical (a one-entry map); no other open PR touches the file (36-PR census this act); same gate family.packages/metadata-protocol/src/protocol.invalid-metadata-422-face-inventory.test.ts, test-only and on thedomain:engineside. Cross-lane note: no engine claim or open PR touches the file.content/docs/ui/translations.mdxandcontent/docs/protocol/kernel/i18n-standard.mdx(PR docs(i18n): restate the orphan-key table from the emitter, sibling packages included #19839 also edits the latter; ordinary concurrency, the later lander resolves);docs/qa/platform-checklist/areas/i18n.json;packages/spec/liveness/README.md;undrilled-containers.baseline.json(a shrink).Deviations, the seat's reading: (1)–(3) and (5)–(8) accepted as measured. (4) — the
15178correction — is the one open item: it is the maintainer's to confirm (below). (9)–(10) need no action.Out-of-scope findings: the objectui pin's
TranslationPreview.tsxstill lists asettingsgroup, andclientValidation.tsprose counts "19 keys" (dead UI / stale prose, not class a/b/c). Recorded in Acceptance notes, not filed.Landing condition: the maintainer's one-sentence confirmation of the
15178correction, carried to the PR with provenance (the route #19746 and #19747 landed by). Then ready + auto-merge. ⛔ Noskip-changesetlabel.domain:spec#4·session_019c3Hi6ZMU1p6m6aA6Bz45d· read at 2026-09-24T03:11ZGenerated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
Landed — PR #19945 →
4ec39873d4, 2026-09-24T16:07Zdomain:specseat 4 (session_019c3Hi6ZMU1p6m6aA6Bz45d), landing record. Authority: this seat's own dispatch; the DELIBERATE CORRECTION of the pending 15178 note was confirmed by the maintainer (5816288904on the PR). Landed through the merge queue only; ⛔ no hand approval, no hand merge.completedthroughFixes #19620, the PR body's only closing keyword.4ec39873d4has one parent and is an ancestor oforigin/main. Content probe:git patch-id --stableof the squash's own diff equals that of the PR's diff from its merge base to the head the queue merged, so the squash carries exactly the reviewed change.pm:dispatchedand the assignee are removed in one label write. The claim was this seat's, so this is the release. Nothing on this card remains in flight.Generated by Claude Code
feat(spec)!: the translation item door refuses `settings` — platform-…
fix(spec): the protocol 16 → 17 conversion summaries and three descri…
fix(spec): the protocol 17 → 18 conversion summaries state each decis…
test(spec): contracts and conversions test titles state each cited de…
test(spec): the last system/ file group's test titles state each cite…