Skip to content

[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

Closed

Description

@os-warren

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是)。逐字引用,⛔ 不译:

  1. packages/spec TranslationDataSchema becomes two exports (names per the file's convention): the platform bundle schema (eleven groups, settings included) and the per-app bundle schema (settings absent, strict — an authored settings in 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.
  2. The card's original "removal" disposition is struck: settings is a live platform key (pickSettingsEntry, i18n-resolver.ts:2305; console useSettingsLabel).

⚠️ 这条裁定到不到条目门:不到。 它的 item 1 逐字只说 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

⚠️ 那 12 条 ADR 命中全部是 distinct-term tier 1(只命中一个词,几乎都是 settings 的无关义项——ADR-0007 讲的是 sys_setting 的存储表)。⛔ 逐条读过,没有一条裁到条目门;它们出现在这一行是因为工装照实打印,不是因为它们管这件事。

协议声明

本卡 ⛔ 不改任何协议。它请求的是对一个已声明键的处置裁定。

前提,附 re-check 命令

⚠️ PR #19600 正在改这个文件,所以下列读数写明 ref,裁定时请按命令重取:

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 明文裁定条目门故意留开,并要求文件门的拒收文案加一句说明 行为不变,但拒收信息从"平台专属"变成"这里不行,那里可以",作者不再被一句半真的话打发走

⚠️ 三个选项都不需要动 PR #19600,它落的是已裁的那一半。

业务含义直译

  • 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 是三者里唯一增加永久义务的。

置信缺口(看不见什么):

  1. ⛔ 条目门 settings 今天有没有生产用量,完全未测。 这是 B 唯一真正的风险:若有人在用,B 会静默丢掉他们写的字。⇒ 若裁 B,执行的第一步必须是先取这个读数,而不是先改 schema。
  2. ⛔ deepMerge(static, authored) 的覆盖语义取自 dev 报告,席位未复现;若实际只能补空而不能覆盖,本卡的严重性下降一档,但 ③ 的 AI 陷阱不变。
  3. ⛔ 未查重即立卡(按常设规则,查重词在末尾)。

裁后执行段

  • 裁 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 明令的 major changeset 在落地时测出不可执行 —— PR #19610 已按 minor + BREAKING banner 出,请追认或否决 #19611)。
  • 裁 C ⇒ 本卡转 pm:queue,范围只有一句拒收文案外加一条 pin,⛔ 不碰 schema;同时把"两扇门语义相反"写进 content/docs 的对应页,否则 C 的代价会以误读的形式回来。

相关单与 PR

查重词

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

  1. os-support-ai commented on Sep 22, 2026

    @os-support-ai
    Collaborator

    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): settings is 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): settings leaves TranslationItemSchema (packages/spec/src/system/translation.zod.ts:1445; shape from translationDataShape() at :655, settings: at :1173), together with the singular alias setting: 'settings' in the item alias table (:1449). The item door is strictObject with guidance, so an item carrying settings is refused at parse with the platform-only prescription — loud, never a silent strip. The settings row of packages/spec/liveness/translation.json retires with it; the D2 conversion translation-per-app-settings-removed learns the item shape (an entry carrying locale); 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 settings first — does any stored translation item in a reachable rig carry settings; 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 「TranslationItemSchema is UNCHANGED」 acceptance note. Clause-②: no (accept-set narrowing). Changeset minor under 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:spec stays; priority:* and type are 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

  2. objectstack-fleet commented on Sep 23, 2026

    @objectstack-fleet
    Contributor

    补定级 priority:p2 · area:i18n —— 已裁(batch #210 item 2,字母 B)的执行卡;⚠️ 卡面仍写着「请裁一个字母」

    分诊席(session_01Tw7jnJinGHvoGSi8aFkhPJ),2026-09-23T04:26Z。本卡在 pm:queue 里缺 priority:*(三析取第 ④ 项);裁决 5770445203 明写「priority:* and type are triage's to set」。

    p2 判据

    • 这是维护者 2026-09-22T02:40Z 刚裁的契约对齐:文件门与条目门是同一种应用元数据的两个书写入口,必须接受同一种形状;今天条目门还能覆盖平台设置文案,正是批 🔗 Broken links detected in documentation #132 item 2 判定不该发生的事。
    • NORTH-STAR.md 第 2 条「能跑但出错 ⇒ P2」:能力在跑,但与裁定的归属相反。
    • ⚠️ 第 3 条:产品仓还有开放的 P0/P1 时各车道不派 p2/p3 的契约卫生卡 ⇒ 它可能要排在那之后。本席照实写出来,⛔ 不为让它先走而抬到 p1。

    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

  3. added
    area:i18nThe customer's own language, across UI, metadata and notifications
    and removed on Sep 23, 2026
  4. objectstack-fleet commented on Sep 23, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 1
    Session: session_01VWsFyWDp8Rjb2Ma6a3Cyo8
    Branch: claude/issue-19620-item-door-settings-retire
    Worktree: objectstack-issue-19620
    Domain: domain:spec
    Seat: domain:spec#4
    File surface: packages/spec/src/system/translation.zod.ts (TranslationItemSchema + its alias table) + tests · packages/spec/liveness/translation.json (the settings row) · the D2 conversion translation-per-app-settings-removed and 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 on packages/spec/src/** (dispatch-gates --tier)
    Clause-②: no — accept-set narrowing, as ruling 5770445203 states
    Thread-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.ts or liveness/translation.json; migrations/registry.ts / conversions/registry.ts are shared with PRs #19750 · #19657 · #19637 · #19618 — ordinary concurrency, later lander resolves.

    Claimed 2026-09-23T05:53Z by seat post #18917. The dispatched os-dev runs under this same session id and writes ⛔ no second claim.


    Generated by Claude Code

  5. objectstack-fleet commented on Sep 23, 2026

    @objectstack-fleet
    Contributor

    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. 6 remaining items

  7. objectstack-fleet commented on Sep 23, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 1
    Session: session_019c3Hi6ZMU1p6m6aA6Bz45d
    Branch: claude/issue-19620-item-door-settings-retire
    Worktree: objectstack-issue-19620
    Domain: domain:spec
    Seat: domain:spec#4
    File surface: packages/spec/src/system/translation.zod.ts (TranslationItemSchema + its alias table) + its tests · packages/spec/liveness/translation.json (the settings row) · the D2 conversion translation-per-app-settings-removed (packages/spec/src/conversions/registry.ts and its source) + the ADR-0087 semantic entry 18.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 --tier at fdeeea0cc9: no path-derived mandate, clause-② SUSPECT hint on packages/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.ts or authored-translation-sync.ts; migrations/registry.ts is 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 beyond main (head fae870352e), reused.
    Ruling-ref: 5770445203

    Claimed 2026-09-23T23:57Z by seat post #18917 (re-claim after the Release: 5789899347; step ① waived per 5796717943). The dispatched os-dev runs under this same session id and writes ⛔ no second claim.


    Generated by Claude Code

  8. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    Contributor

    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

  9. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    Contributor

    ACCEPT — PR #19945 at head b0f2b0ef06, 2026-09-24T03:11Z · landing waits on one maintainer sentence

    domain:spec seat 4 (session_019c3Hi6ZMU1p6m6aA6Bz45d), reviewer of record. Verified against GitHub, not against the report.

    Checklist, read on GitHub:

    • PR shape: draft · base main · first line Fixes #19620; the body carries no other closing keyword, and it names PR feat(spec)!: split the translation bundle type — settings is a platform group, not a per-app one (#15178) #19600's 「TranslationItemSchema is UNCHANGED」 note as superseded.
    • Governed surface: check-governed-merges.mjs --pr 19945 reads 0 of 21 paths governed; 887 lines ⇒ ordinary queue landing.
    • Contract review: an at-tier PASS on this head, record 5806788396 on the PR, adopted after the seat re-read ①(a) and ①(c) on the head.
    • CI on b0f2b0ef06: 32 success · 2 skipped by design · 1 red, Check Changeset. That red is exactly the DELIBERATE CORRECTION class of check-empty-changeset (the unreleased .changeset/15178-…md sentence corrected in the same release). The reviewer confirmed the corrected sentence is true at the head.
    • Step order kept: the step-② commit 411513f09 precedes the step-③ commit 7ba3d25d9.

    Claim-surface amendment (recorded here, as the bounded in-place exemption requires):

    • Forced, and inside the ruling's shape: packages/spec/src/conversions/stored.ts + test. The stored pass never reached translation rows. 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.
    • Forced by the pin the ruling requires: packages/metadata-protocol/src/protocol.invalid-metadata-422-face-inventory.test.ts, test-only and on the domain:engine side. Cross-lane note: no engine claim or open PR touches the file.
    • Published text that this change made false: content/docs/ui/translations.mdx and content/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 15178 correction — 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.tsx still lists a settings group, and clientValidation.ts prose 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 15178 correction, carried to the PR with provenance (the route #19746 and #19747 landed by). Then ready + auto-merge. ⛔ No skip-changeset label.

    domain:spec#4 · session_019c3Hi6ZMU1p6m6aA6Bz45d · read at 2026-09-24T03:11Z


    Generated by Claude Code

  10. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    Contributor

    Landed — PR #19945 → 4ec39873d4, 2026-09-24T16:07Z

    domain:spec seat 4 (session_019c3Hi6ZMU1p6m6aA6Bz45d), landing record. Authority: this seat's own dispatch; the DELIBERATE CORRECTION of the pending 15178 note was confirmed by the maintainer (5816288904 on the PR). Landed through the merge queue only; ⛔ no hand approval, no hand merge.

    • Merged by the queue at 2026-09-24T15:03Z. The card closed completed through Fixes #19620, the PR body's only closing keyword.
    • The squash 4ec39873d4 has one parent and is an ancestor of origin/main. Content probe: git patch-id --stable of 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.
    • Mis-close check: every card closed since 2026-09-24T14:30Z was closed by its own PR, and none by a stray keyword.
    • pm:dispatched and 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:i18nThe customer's own language, across UI, metadata and notificationsdomain:specpriority:p2Medium: important, M3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions