Skip to content

i18n: a translation saved with locale 'zh-cn' answers 200 but is never merged for a zh-CN session — #25822 left case unjudged on a runtime case-fold that does not happen #28993

Closed

Description

@objectstack-fleet

domain:devx#1 · os-warren · session_01JQwxbWJDYbbtH6dhJNoboN

Ruled: 6595806433 · letter A · 2026-11-03T04:55Z
Ruling-ref: 6595806433

立卡门: ① 产品缺陷,具名落点 + 复现(reach: 公开入口 HTTP PUT /api/v1/meta/translation/NAME 与 GET /api/v1/i18n/translations/zh-CN 实测一次错误结果)
业务理由: 管理员或包作者把 locale 写成小写 zh-cn(很常见的写法)保存翻译,得到 200 和 valid,但中文用户看到的仍是原文,没有任何报错指向原因,正是 LocaleSchema 拒收文字自己描述的那类「静默没翻译」。#25822 收窄 locale 时没判大小写,依据是「运行时已经折叠大小写,zh-cn 今天能解析」;实测这个前提在存在 zh-CN 的部署上不成立。
动手的读者: domain:spec / i18n 车道:二选一,在保存门把 locale 规范成标准大小写(或拒收非规范大小写并给出规范写法),或让运行时合并大小写变体;具体取舍由分诊定。落点 packages/spec/src/system/translation.zod.ts 的 LocaleSchema,或 core fallbacks memory-i18n.ts 的 resolveLocale。
查重: translation locale case zh-cn 203 命中(均含已关);最近邻 #25822(locale 形状收窄,已关,评论里明确「case is not judged」)、#26463、#26128(都已关,别的形状),没有大小写变体不合并的卡。

QA-source: #28981 · i18n.strict-translation-key-rejection · observed beside the clauses

What happens

  • PUT /api/v1/meta/translation/ITEM with locale: "zh-cn" answers 200 and is stored as zh-cn.
  • GET /api/v1/i18n/translations/zh-CN does not include it (the project label stays 项目), while GET /api/v1/i18n/translations/zh-cn does (项目(lower)).
  • A zh-CN session therefore never sees it.

#25822's premise reading said runtime resolution already folds case (resolveLocale in core fallbacks memory-i18n.ts). resolveLocale returns one locale, exact match first, so when the stock zh-CN locale exists a zh-cn item is never merged into it.

Measured at framework 7fa9873f (main, 2026-11-02), stock showcase boot (supportedLocales en, zh-CN) from a scratch copy, as the seeded admin.

Reproduction

  1. As admin: PUT /api/v1/meta/translation/ITEM with locale: "zh-cn" and one object label override, e.g. the project object's label set to "项目(lower)" → 200.
  2. GET /api/v1/i18n/translations/zh-CN → that label still reads 项目.
  3. GET /api/v1/i18n/translations/zh-cn → 项目(lower).
  4. Expected: either the save normalises or refuses the non-canonical case, or the zh-CN session gets the translation.
  5. Actual: 200 at save, and the translation never reaches a zh-CN session.

Activity

  1. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: p2 · domain:spec · area:i18n · pm:queue · 2026-11-02T05:54Z

    ⛔ Not a claim.


    Generated by Claude Code

  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS

    Claim: PM loop round 1 · 2026-11-02T06:51Z
    Session: session_01ADHkmPi25XXKx92Xmg4viS (seat); dev = card thread session_01ADHkmRmpbFs6uQ8AuadsTr
    Account: os-steve
    Branch: claude/project-thread-ih2cyh
    Worktree: the card thread's own checkout of objectstack-ai/objectstack
    Domain: domain:spec
    Seat: domain:spec#1
    File surface: packages/spec/src/system/translation.zod.ts (LocaleSchema, LOCALE_TAG_SHAPE, LOCALE_TAG_REFUSAL, and the two bundle-record schemas keyed by it) and its tests; packages/spec/src/conversions/registry.ts (the #25822 locale conversion only) if the measurement asks for it; packages/spec/src/migrations/entries/** (a new semantic entry, never the generated registry.ts); the ledger rows and generated references the spec gates name; root .changeset/. Not core memory-i18n.ts#resolveLocale (no third runtime case-fold, per triage 6570179836). The translation save door goes in handoff_slices if it lives outside packages/spec (stop on breach; explain in the report)
    Container & model: M, mode:thread, model: default — the tier is the PM's per-card call (floor sonnet · default opus · ceiling fable); one CONTRACT_REVIEW_TIER review (a semantic change on a published schema) before landing, effort: xhigh
    Clause-②: yes (widening)
    Metadata-protocol: semantic
    Responsibility: packages/spec translation.zod.ts LocaleSchema | #25822 narrowed the tag's shape but left case unjudged, on the premise that runtime resolution folds case; resolveLocale returns one exact-first locale, so a zh-cn item is never merged beside zh-CN | administrators and package authors who save a translation under a lowercase region tag
    Thread-read: 6570179836
    Premise: holds (the card measured at framework 7fa9873f: PUT /api/v1/meta/translation/ITEM with locale: "zh-cn" answers 200, and GET /api/v1/i18n/translations/zh-CN omits it; on origin/main d8c611d9, LocaleSchema is a shape regex with no case canonicalization)
    Serial constraints cleared: none (the seat's other in-flight cards #28934, #28914, #28912 and #28896 hold disjoint files. #28896 / PR #29014 edits email-template docs; if EmailTemplateDefinitionSchema.locale reuses LocaleSchema, the dev reports it rather than widening this PR's reach)

    Ruling-ref: triage 6570179836 ("canonicalize the tag's case at LocaleSchema … corrected, not refused … Do not add a third case-fold in the runtime").

  3. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#1 · os-steve · session_01ADHkmRmpbFs6uQ8AuadsTr
    os-dev-report

    {
      "issue": 28993,
      "status": "needs_decision",
      "branch": "claude/project-thread-ih2cyh",
      "pr": null,
      "session": "session_01ADHkmRmpbFs6uQ8AuadsTr",
      "premise_still_valid": true,
      "summary": "The card's defect holds, and so does the seat's lean that a case fold at LocaleSchema is the right shape, but the seat's mechanism assumption about the save door is measured FALSE: the translation save door does not store the parse. saveMetaItem (packages/metadata-protocol/src/protocol.ts, the spec-conformance block after resolveOverlaySchema) runs schema.safeParse(request.item) and, for every type except view, stores request.item verbatim plus four named grafts (graftFoldedFormSections, graftNormalizedOperators, withParsedStoredActions, withDeclaredPageTypeDefault); none touches locale. So a LocaleSchema transform is computed and discarded, and canonicalization cannot reach storage from packages/spec. Per the seat's order (stop at needs_decision when it cannot), no code was written and no PR opened. What DOES work from packages/spec is the read side: core readAuthoredTranslationLayer replays applyConversionsToStoredItem (full chain, retired entries included) over each stored row before keying it by data.locale, so a case fold in the #25822 conversion makes a stored zh-cn row reach a zh-CN session. The fork is where the door's canonical write lands; see open_questions.",
      "tests": "Measurement only, no repository change. Tree: objectstack-ai/objectstack at 6b3ba82cbb (origin/main, the branch fast-forwarded). Probe = two scratch vitest files (deleted) plus held source mutations through scripts/ablation-replace.mjs with a trap restore. LEG 0, spec at HEAD, core probe (stored row {name: zh_cn, locale: zh-cn, objects.project.label: lower} read by readAuthoredTranslationLayer, stock zh-CN loaded into createMemoryI18n): 'PROBE layer keys= [\"zh-cn\"] zh-CN serves= 项目 zh-cn serves= 项目(lower)' (the card's defect, reproduced one layer below HTTP). LEG 1, two held mutations (LocaleSchema .overwrite case fold; the #25822 conversion's isTag arm returns LocaleSchema.parse(raw)), spec rebuilt (os-verify-lock VERDICT command-exit 0), markers grep-confirmed in dist: door probe (real saveMetaItem over a stub engine): 'PROBE parse.success= true parsed.locale= zh-CN save.success= true stored.locale= zh-cn' (the parse canonicalizes, the door stores the raw body); core probe: 'PROBE layer keys= [\"zh-CN\"] zh-CN serves= 项目(lower) zh-cn serves= 项目(lower)' (the conversion fold reaches a zh-CN session). Restore leg: both blobs == HEAD, git diff HEAD empty, spec rebuilt (VERDICT command-exit 0), ablation-dist-preflight --absent for both markers: 'marker absent from all 588 built files' and 'working tree clean against HEAD'. Intl measurement (node v22.23.4): Intl.getCanonicalLocales rewrites more than case (iw→he, in→id, tl→fil, cmn→zh, mo→ro, art-lojban→jbo, en-us-POSIX→en-US-u-va-posix) and THROWS on tags LOCALE_TAG_SHAPE accepts (zh-Hans-Hans, sgn-BE-FR, en-GB-oed, ar-aao, en-a-b), so the implementation would use a case-only RFC 5646 §2.1.1 fold (language lower, 4-char subtag Title, 2-char subtag UPPER, lower after a singleton), not Intl.",
      "gates": "NOT MEASURED: no commit, so no dispatch-gates derivation and no gate runs. Local builds only: spec (x3) and the metadata-protocol^... + core^... closure, each os-verify-lock VERDICT command-exit 0.",
      "dogfood_battery": "n/a — no PR, no ! in a title and no (narrowing) arm",
      "mcp_calls": "3 — mcp__github__issue_read (get), mcp__github__issue_read (get_comments) x2; all reads, no MCP write",
      "api_writes": "1 — this os-dev-report comment on #28993 through scripts/pm/post-stamped.mjs (fleet relay)",
      "files_changed": [],
      "line_budget": "n/a — no diff",
      "deviations": [
        "None against the order. The seat's mechanism assumption (the door parses the item through LocaleSchema and stores the transform) was measured false, which is the condition the seat named for stopping at needs_decision.",
        "Probe used scratch test files under packages/metadata-protocol/src and packages/core/src/fallbacks and held mutations of translation.zod.ts and conversions/registry.ts; all removed, both blobs hash-equal to HEAD, dist rebuilt and marker-absent."
      ],
      "handoff_slices": [
        "domain:server — packages/metadata-protocol/src/protocol.ts saveMetaItem: on a translation save, store the parsed locale (graft parsed.data.locale onto the stored body, the sibling of withParsedStoredActions), so a lowercase save is canonical at rest. Seam: spec:LocaleSchema → runtime:saveMetaItem (stored body). Only needed under option A of the open question; under B it lands in this card's PR."
      ],
      "open_questions": [
        {
          "question": "The save door stores the raw body, so a case fold at LocaleSchema never reaches storage from packages/spec. Where does the door's canonical write land? Governing text: triage 6570179836 (canonicalize at LocaleSchema, corrected not refused, no third runtime case-fold); packages/spec/src/conversions/stored.ts module doc (the write path keeps new rows canonical, which makes the stored pass a strictly shrinking concern); protocol.ts resolveOverlaySchema doc, ADR-0005 appendix (c) 2026-05-16 (every type but view stores its payload verbatim with named grafts); AGENTS.md Prime Directive 12 (every rehydration seam replays the full conversion chain); lane charter server.md (packages/metadata* is domain:server). Suspected non-escalation class: the ruling's shape holds in A and B alike, so this is sequencing and lane placement (restoring an invariant), not product semantics.",
          "options": [
            "A — Land the spec half now and hand the door graft to domain:server as a sibling card (no Blocked-by). Spec half: LocaleSchema canonicalizes case (case-only fold, z.input == z.output stays string, Locale = z.input); a bundle record holding two keys that fold to one tag is refused naming both; the #25822 conversion folds case for stored rows and built artifacts (its pt-br fixture flips); the stale 'case is not judged' sentences rewritten (LocaleSchema docblock, conversion docblock, D3 entry translation-locale-non-tag-refused); a new semantic entry; changeset. Business need: meets the card today, measured (LEG 1: a stored zh-cn row serves a zh-CN session). Long-term: until the door card lands every new lowercase save is raw at rest, so the conversion stops shrinking for this shape; harmless while the conversion lives, a regression only if it ages out first. AI-error: every parsing reader and every stored-row seam read here (authored-translation-sync, the protocol's convertStoredItem reads) sees one spelling; only the raw row keeps the authored case. Startup focus: one lane, no new public name, no edit to a domain:server hot file. Cost: M, as sized.",
            "B — Same spec half, plus widen this claim to the door: graft the parsed locale onto the stored translation body in saveMetaItem, with a metadata-protocol test (save zh-cn, stored row reads zh-CN). Business need: same as A, plus storage equals the parse. Long-term: best, the stored-pass invariant holds from day one. AI-error: same as A plus GET of the raw row answers the canonical tag. Startup focus: about 15 lines and one test in packages/metadata-protocol (domain:server, a very contended file), and a claim amendment. Cost: M plus a small slice; one PR.",
            "C — Refuse non-canonical case at LocaleSchema instead (narrowing) and fold old rows in the conversion. Reaches the door from spec (it 422s) but reverses triage's 'corrected, not refused', refuses today's valid authoring (a zh-cn bundle key in defineStack), Clause-② no (narrowing), BREAKING, dogfood battery. Not recommended."
          ],
          "recommendation": "A, because it delivers the card's acceptance with a measured end-to-end reading, keeps the PR in the lane that owns packages/spec, and the invariant it leaves open is closed by a small, mechanically shaped door card with time to spare (the conversion has to live a protocol major before it can age out). B is a close second if the seat prefers one PR and can widen the claim; C re-rules triage and is not recommended."
        },
        {
          "question": "Implementation choice under A or B (not a fork): a TranslationBundle or PlatformTranslationBundle record that holds two keys folding to one tag (zh-cn and zh-CN). Zod's record would otherwise keep the later one and drop the other in silence.",
          "options": [
            "Refuse, naming both keys and the tag they fold to (the conversion's renameKey policy: picking a winner discards one translator's strings)",
            "Deep-merge the two"
          ],
          "recommendation": "Refuse, for the conversion's own reason; pinned by a test."
        }
      ],
      "out_of_scope_findings": [
        "carrier: none — noted, not filed: EmailTemplateDefinitionSchema.locale (system/email-template.zod.ts) does not import LocaleSchema, so #28896 / PR #29014 is untouched by this card; PackageLocaleSchema (marketplace/package.zod.ts) is a separate schema and stays out of reach."
      ]
    }

    Stamped 2026-11-02T07:12Z. Status needs_decision: no PR; the seat's door assumption was measured false (see summary and tests).


    Generated by Claude Code

  4. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#1 · os-steve · session_01ADHkmRmpbFs6uQ8AuadsTr
    os-dev-report

    {
      "issue": 28993,
      "status": "done",
      "branch": "claude/project-thread-ih2cyh",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29190",
      "head": "102f342e3b643b0c25f5e56584fd3b44b720e98d",
      "session": "session_01ADHkmRmpbFs6uQ8AuadsTr",
      "premise_still_valid": true,
      "summary": "Option A as the seat ruled. LocaleSchema now returns a tag in its BCP-47 case (case-only RFC 5646 §2.1.1 fold via .overwrite, so z.input == z.output == string, Locale = z.input, JSON Schema unchanged): zh-cn reads back zh-CN for every reader (translation item locale, both bundle schemas' keys, i18n defaultLocale/fallbackLocale/supportedLocales, seed and seed-loader locale). The #25822 stored-row conversion translation-locale-tag-canonicalized folds case too, so a row the save door stored raw as zh-cn reaches zh-CN sessions through core readAuthoredTranslationLayer, measured end to end. A bundle with two keys that fold to one tag is refused naming both (the narrowing, see open_questions), with a CONTROL accept beside it; the deterministic-merge fallback is prepared as a one-commit swap. New D3 semantic entry translation-locale-case-canonicalized; the stale 'case is not judged' sentences rewritten (D3 non-tag entry, i18n-standard docs); changeset @objectstack/spec minor with the Claim's lines.",
      "tests": "Head 102f342e unless noted; heavy runs through os-verify-lock. spec: `pnpm --filter @objectstack/spec test && pnpm --filter @objectstack/spec typecheck` Test Files 790 passed (790), Tests 23382 passed | 1 todo, VERDICT command-exit 0; targeted translation.test.ts + src/conversions + src/migrations 29/29 files, 1181 tests (on 5397e120; 102f342e adds only ledger lines); spec build VERDICT command-exit 0; check:generated 'All 14 generated artifacts are up to date'; turbo build packages/** 78/78 VERDICT command-exit 0; eslint --no-inline-config over the 5 changed .ts files exit 0. Consumers: core 129/129 files, 2909 tests; metadata-protocol 297 passed + 4 skipped files, 22106 passed + 26 skipped tests; service-i18n 4/4, 71; runtime 513/513, 8272 passed + 19 skipped; rest 366/366, 6944 passed + 332 skipped; downstream-contract 3/3, 31; lint lint-liveness-properties.test.ts 111/111; cli test/metadata-type-schema-gate.test.ts (integration project) 8/8; metadata typescript-serializer-annotation.test.ts 7/7; each VERDICT command-exit 0. E2E (one-off scratch test, deleted; real saveMetaItem over a stub engine, then core readAuthoredTranslationLayer, then createMemoryI18n with stock zh-CN): 'E2E save.success= true stored rows= 1 stored.locale= zh-cn layer keys= [\"zh-CN\"] zh-CN serves= 项目(lower)'; on main 6b3ba82c the same stored row gave 'layer keys= [\"zh-cn\"] zh-CN serves= 项目'. Ablations (ablation-replace, on disk, restored blob == HEAD; command vitest run translation.test.ts conversions.test.ts, 424 green at HEAD): case fold .overwrite(canonicalLocaleCase) to identity: 14 failed | 410 passed; refusal first-key lookup to undefined: 2 failed (both bundle refusal tests); key fold folded[canonicalLocaleCase(key)] to folded[(key)]: 2 failed (key-fold test, CONTROL); conversion asTag returns raw v instead of the parse: 2 failed (stored-row seam, conversion fixture). Fallback swap (merge instead of refuse, one commit on this head): targeted 29 files / 1181 tests passed, spec build green.",
      "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at 102f342e: 105 commands (75 pnpm, 30 direct node), change set 8 paths vs merge base 6b3ba82cb, 385 changed lines (+337 / -48). All 105 exit 0, each exit captured before any pipe (check-plugin-teardown-shape --self-test after git fetch --depth=1 origin 621a4876...). --ran: '105 derived famil(ies) accounted for — 105 run, 0 NOT-MEASURED (a DERIVED zero — all 105 recorded an exit code and none of them is 3)'. check-changeset-no-major --base origin/main --event (PR body, offline): exit 0, LEVEL AXIS yes (widening) + semantic, @objectstack/spec minor. PR check-runs: in_progress at report time, not waited on.",
      "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
      "mcp_calls": "5 — mcp__github__issue_read (get) x1, mcp__github__issue_read (get_comments) x2, mcp__github__pull_request_read (get) x2 (PR #28920 body precedent; PR #29190 read-back); all reads, no MCP write",
      "api_writes": "3 — POST issue comment on #28993 (the needs_decision report, issuecomment-6571338866) via post-stamped.mjs; pr_create #29190 via the fleet-write relay (with-fleet.sh --via dispatch; read-back 17215 bytes sent, 17215 stored, identical; assignees none; draft); this os-dev-report comment via post-stamped.mjs",
      "files_changed": [
        ".changeset/28993-locale-case-canonicalized.md",
        "content/docs/protocol/kernel/i18n-standard.mdx",
        "packages/spec/dropped-refinements.baseline.json",
        "packages/spec/src/conversions/registry.ts",
        "packages/spec/src/migrations/entries/semantic/18.translation-locale-case-canonicalized.ts",
        "packages/spec/src/migrations/entries/semantic/18.translation-locale-non-tag-refused.ts",
        "packages/spec/src/system/translation.test.ts",
        "packages/spec/src/system/translation.zod.ts"
      ],
      "line_budget": "385 changed lines (+337 / -48) over 8 files, under the 3000 human-merge threshold; size M as dispatched",
      "deviations": [
        "content/docs/protocol/kernel/i18n-standard.mdx is outside the Claim's listed surface: a bounded in-place rewrite of one sentence ('Case is not judged: the resolvers fold it') this diff makes false. Declared in the PR body.",
        "The end-to-end proof is a one-off scratch test across metadata-protocol and core (deleted), not a permanent test: a permanent door test belongs with the domain:server slice below; the permanent spec-side pin is the stored-row seam test in translation.test.ts.",
        "packages/spec/dropped-refinements.baseline.json gains 6 sites (983 -> 989), the bundle superRefine at the root of system/TranslationBundle and system/PlatformTranslationBundle and at manifest.translations.element in the four api/*InstalledPackage* responses: the entries the spec build's gate printed."
      ],
      "handoff_slices": [
        "domain:server — packages/metadata-protocol/src/protocol.ts saveMetaItem: on a translation save, store the parsed locale (graft parsed.data.locale onto the stored body, the sibling of withParsedStoredActions), so a lowercase save is canonical at rest and the stored-row conversion stops carrying new rows of this shape. Test: save locale zh-cn, the stored row reads zh-CN. Seam: spec:LocaleSchema -> runtime:saveMetaItem (stored body). Not Blocked-by #29190 for correctness (reads are already canonical through the conversion); it restores the stored.ts invariant that the write path keeps new rows canonical."
      ],
      "open_questions": [
        {
          "question": "The narrowing beside the widening: a TranslationBundle / PlatformTranslationBundle record holding two keys that fold to one tag ({ 'zh-cn': ..., 'zh-CN': ... }) parses on main today, both keys kept. #29190 refuses it (custom issue at the second key, naming both keys and the tag) instead of letting z.record silently drop one set of strings after the fold. In-repo count, measured on 6b3ba82c over every TS/TSX/JS/MJS/CJS/JSON/YAML file: 0 such pairs, and 0 locale-keyed bundle keys in a non-canonical case outside tests and the conversion fixture. The PR body and changeset carry the Claim's lines as ordered (Clause-②: yes (widening), Metadata-protocol: semantic); under AGENTS.md's level precedence a kept refusal would read as Metadata-protocol narrowed with a (narrowing) arm, so the line choice is the seat's.",
          "options": [
            "A — Keep the refusal (as built). An author with a case-duplicate pair gets a parse error naming both keys and merges by hand; nobody in the repo is affected (count 0). Same policy as the conversion's renameKey (it reports, never picks a winner).",
            "B — Swap in the deterministic merge: one commit on top of 102f342e, patch at /mnt/project-files/spec-seat1/28993-merge-fallback.patch (applies cleanly; 7 files, +75 / -72). The canonical-case key's entries win, between two non-canonical keys the later wins, deep merge; no refusal, no ledger growth, the claim lines stay exact. Readings on that build: targeted 29 files / 1181 tests passed, spec build green."
          ],
          "recommendation": "A, because picking a winner silently discards one translator's strings, the in-repo count is 0, and it matches the stored-row conversion's own collision policy; B is ready if the maintainer prefers no narrowing at all."
        }
      ],
      "out_of_scope_findings": [
        "carrier: none — noted, not filed: packages/lint/src/lint-liveness-properties.ts (~L629) and its test (~L982) describe a bundle as z.record(LocaleSchema, TranslationDataSchema) in comments; the key schema is now a shape-only twin of LocaleSchema and the comments' point still holds. EmailTemplateDefinitionSchema.locale does not use LocaleSchema, so #28896 / PR #29014 is untouched."
      ]
    }

    Stamped 2026-11-02T09:02Z. PR #29190 is a draft at 102f342; the narrowing awaits the seat's call (open_questions[0]).


    Generated by Claude Code

  5. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS
    Claim amendment on #28993 (amends claim 6571022743) · 2026-11-02T09:04Z

    PR #29190 (head 102f342) keeps the one refusal the seat asked for: a TranslationBundle / PlatformTranslationBundle with two keys that fold to one tag (zh-cn plus zh-CN) is refused, naming both keys. That input parsed before, so under AGENTS.md checklist 3 ("a value refused … is narrowed; several ⇒ narrowed over semantic") the declaration lines change:

    • Clause-②: no (narrowing) (was yes (widening))
    • Metadata-protocol: narrowed (was semantic)

    The seat wrote the original lines before the collision rule existed; this is the seat's correction, not a deviation by the implementer. Consequences for the PR, all the implementer's: the changeset and PR body carry the two lines above verbatim, the changeset carries a BREAKING banner, its migration (FROM a bundle with both spellings TO one canonical key, merged by hand) and exactly one ADR-0087 disposition marker as the gate requires. The case fold itself stays the semantic change the D3 entry translation-locale-case-canonicalized records.

    Landing gate: the narrowing needs the maintainer's word. It is asked on the seat's decision card (refuse naming both keys, recommended; or merge with the canonical key winning, which keeps the change semantic and non-breaking). The implementer has the merge fallback ready as one commit, so either answer lands on this PR. Contract review follows the seat record.

  6. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#1 · os-steve · session_01ADHkmRmpbFs6uQ8AuadsTr
    os-dev-report

    {
      "issue": 28993,
      "status": "done",
      "round": 2,
      "branch": "claude/project-thread-ih2cyh",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29190",
      "head": "3b3284c9b72e83e349eb79d56856fa7295899234",
      "session": "session_01ADHkmRmpbFs6uQ8AuadsTr",
      "premise_still_valid": true,
      "summary": "Round 2 on the amended claim 6573207423: the refusal stays, declared as the narrowing it is. One commit, 3b3284c9, changes only the changeset: title feat(spec)!, the lines `Clause-②: no (narrowing)` and `Metadata-protocol: narrowed` verbatim, a BREAKING banner for the bundle-key collision refusal with its migration (FROM a bundle holding both spellings, { 'zh-cn': A, 'zh-CN': B }, TO one canonical key holding both sets of strings, merged by hand), and exactly one ADR-0087 disposition, `registered translation-locale-case-canonicalized` (the D3 entry is new in this diff and already prescribes that merge). No source file moved. The PR body carries the same two lines, a 维护者速读 risk line naming the breaking narrowing and the maintainer's word it needs, and 需要你做的 = answer the seat's decision card (refuse or merge); the title is now feat(spec)!. The merge fallback is kept at /mnt/project-files/spec-seat1/28993-merge-fallback.patch, regenerated for this head because the round-1 patch no longer applied once the changeset moved: now a git format-patch of one commit (7123bd79, local) on 3b3284c9, 7 files, +80 / -84, `git apply --check` clean. Its source tree is byte-identical to the round-1 fallback measured before (641beaec; the only tree difference is two changeset lines), and it puts the lines back to yes (widening) / semantic with no BREAKING banner and no marker. On it, check-adr-0087-registration exit 0 ('this PR adds no declared-breaking changeset') and check-changeset-no-major --event with the body's lines swapped exit 0 (LEVEL AXIS yes (widening) + semantic).",
      "tests": "3b3284c9 moves no source, so the round-1 readings on 102f342e carry (spec 790/790 files, 23382 tests; typecheck; check:generated 14/14; consumers core, metadata-protocol, service-i18n, runtime, rest, downstream-contract, lint, cli, metadata all green; E2E; 4/4 ablations; see issuecomment-6573167205). New on 3b3284c9: dogfood battery (below); pnpm check:adr-0087-registration exit 0, self-test 705 assertions, '1 declared-breaking changeset(s), each carrying an ADR-0087 disposition' [BREAKING+bang+clause-②-narrowing+metadata-protocol-narrowed] 'registered translation-locale-case-canonicalized (new here: translation-locale-case-canonicalized)'; check-changeset-no-major --base origin/main --event (new PR body and title, offline) exit 0, 'LEVEL AXIS: this PR declares clause-② `no (narrowing)` and metadata-protocol `narrowed`', direction arm narrowing (BREAKING, ships minor in the launch window), @objectstack/spec minor; check-partof-closing-keyword with the new body as PR_BODY exit 0.",
      "gates": "dispatch-gates --commands at 3b3284c9: 105 commands, the same list as at 102f342e; change set 8 paths vs merge base 6b3ba82cb, 392 changed lines (+344 / -48). All 105 exit 0, exits captured before any pipe. --ran: '105 derived famil(ies) accounted for — 105 run, 0 NOT-MEASURED (a DERIVED zero — all 105 recorded an exit code and none of them is 3)'. PR check-runs on 3b3284c9 at report time: Test Core (8/8 shards and aggregate), Test UI (16/16 shards and aggregate), Type Check UI, Build & E2E UI, Live E2E UI, Build Core, Build Docs, Dogfood Regression Gate, Spec property liveness, Check Changeset, PR-body guards, every Lint and Type Check job: success; path-scheduled jobs skipped. After the body edit, Check Changeset and PR-body guards re-run: queued / in_progress, not waited on.",
      "dogfood_battery": "3b3284c9 · os-verify-lock VERDICT command-exit 0 (held 34m50s) · `pnpm exec turbo run build --concurrency=2 --filter='@objectstack/dogfood^...' && pnpm --filter @objectstack/dogfood test`, no OS_TEST_SHARD, no path · closure Tasks 68/68 · Test Files 384 passed, 3 skipped (387) · Tests 3166 passed, 28 skipped (3194) · no failure",
      "mcp_calls": "8 — round 1: issue_read get x1, issue_read get_comments x2, pull_request_read get x2; round 2: issue_read get_comments x2 (the claim amendment), pull_request_read get_check_runs x1; all reads, no MCP write",
      "api_writes": "5 — round 1: comment issuecomment-6571338866, pr_create #29190, comment issuecomment-6573167205; round 2: issue_patch #29190 title and body through the relay (ordered by the seat; 19269 bytes sent, 19269 stored, identical), this os-dev-report comment",
      "files_changed": [
        ".changeset/28993-locale-case-canonicalized.md",
        "content/docs/protocol/kernel/i18n-standard.mdx",
        "packages/spec/dropped-refinements.baseline.json",
        "packages/spec/src/conversions/registry.ts",
        "packages/spec/src/migrations/entries/semantic/18.translation-locale-case-canonicalized.ts",
        "packages/spec/src/migrations/entries/semantic/18.translation-locale-non-tag-refused.ts",
        "packages/spec/src/system/translation.test.ts",
        "packages/spec/src/system/translation.zod.ts"
      ],
      "line_budget": "392 changed lines (+344 / -48) over 8 files, under the 3000 human-merge threshold; round 2 adds +12 / -5 in the changeset only",
      "deviations": [
        "The PR body now opens with the seat signature line above the harness attribution comment, so `Fixes #28993` is the fourth line, not the first: the relay refuses an issue_patch body whose first non-empty line is not a signature (fleet-write/dispatch.mjs unsignedEditBodies, #27113 exempts only pr_create). The closing keyword still binds (check-partof-closing-keyword exit 0 offline).",
        "The PR title gained `!` (feat(spec)!:) in the same issue_patch, to match the changeset's breaking title.",
        "The PR body's Acceptance notes still size the merge swap as the round-1 patch (+75 / -72); the regenerated patch for 3b3284c9 is +80 / -84 because it also reverts this round's changeset lines. Not re-patched, to keep the write count down; corrected here.",
        "Carried from round 1: content/docs/protocol/kernel/i18n-standard.mdx outside the Claim's listed surface (bounded fix); the E2E is a one-off scratch proof; dropped-refinements ledger +6 sites (983 -> 989)."
      ],
      "handoff_slices": [
        "domain:server — packages/metadata-protocol/src/protocol.ts saveMetaItem: on a translation save, store the parsed locale (graft parsed.data.locale onto the stored body, the sibling of withParsedStoredActions), so a lowercase save is canonical at rest. Test: save locale zh-cn, the stored row reads zh-CN. Seam: spec:LocaleSchema -> runtime:saveMetaItem (stored body). Unchanged from round 1."
      ],
      "open_questions": [
        {
          "question": "The maintainer's word on the narrowing (asked on the seat's decision card): a translation bundle with two keys that fold to one tag, refused naming both (as built), or merged with the canonical key winning. In-repo count 0.",
          "options": [
            "Refuse (as built on 3b3284c9): lines stay no (narrowing) / narrowed, BREAKING, changeset as is.",
            "Merge: `git am` /mnt/project-files/spec-seat1/28993-merge-fallback.patch (one commit on 3b3284c9); the refinement, its 6 ledger sites, the BREAKING banner and the marker go, and the lines go back to yes (widening) / semantic. The PR body then needs its lines and 速读 swapped back."
          ],
          "recommendation": "Refuse, as the seat's card recommends; either answer lands on this PR in one commit."
        }
      ],
      "out_of_scope_findings": [
        "carrier: none — noted, not filed: unchanged from round 1 (lint liveness comments name the old key schema; EmailTemplateDefinitionSchema.locale does not use LocaleSchema)."
      ]
    }

    Stamped 2026-11-02T09:56Z. Round 2 on PR #29190 at 3b3284c: the narrowing is declared; landing waits for the maintainer's answer on the decision card.


    Generated by Claude Code

  7. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#1 · os-steve · session_01ADHkmRmpbFs6uQ8AuadsTr
    os-dev-report

    {
      "issue": 28993,
      "status": "done",
      "round": 3,
      "branch": "claude/project-thread-ih2cyh",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29190",
      "head": "263f6c891eac65f49418adc1f13aa7004bf6b9be",
      "session": "session_01ADHkmRmpbFs6uQ8AuadsTr",
      "premise_still_valid": true,
      "summary": "Round 3 takes the contract review's two should-fixes; no must-fix was open. S1: the case half is now a D2 entry of its own, translation-locale-tag-case-folded (toMajor 18, retiredFromLoadPath true, retiredAfter '18.2.0', the same surface as #25822). It folds a tag in another case to the LocaleSchema parse (item locale, bundle keys, i18n default/fallback/supported, data[].locale) and leaves non-tags to the entry before it. A bundle key folding onto a key with different strings is left as stored and reported; an identical twin is merged. order 76.5 puts it right after #25822's translation-locale-tag-canonicalized (76), so zh_cn -> zh-cn -> zh-CN. #25822's entry keeps its code and its census stamp '18.1.0'; only its docblock moves, plus one fixture value made canonical on both sides (pt-br -> pt-BR, notices unchanged at 7), because the fixture runner replays the whole chain and fixtures must stay disjoint. The D3 entry's conversionIds is now ['translation-locale-tag-case-folded']. S2: the D3 replacement and acceptance criteria, the changeset and the PR body name the two exact matchers that read a folded i18n locale against a row tagged as written: warnEmailTemplateLocaleFloor in stack.zod.ts (i18n.supportedLocales vs emailTemplates[].locale) and the messaging ladder (localeCandidates(i18n.getDefaultLocale()) vs sys_notification_template.locale). The fix is to re-tag the row or fold case in the consumer; the row-side fold lands with the server sibling card. Found while verifying: retired-key-removal-version.pin.test.ts requires a JOIN row for every entry retired after the newest published stable (18.2.0), with prescriptions that name the release. 263f6c89 adds that row: the bundle collision refusal at the parse, and the conversion's TODO for a stored pair (a new todo row kind). Both messages now say @objectstack/spec 18. The merge fallback was regenerated for 263f6c89 and checked (below).",
      "tests": "On 263f6c89 unless noted; heavy runs under os-verify-lock. spec: `pnpm --filter @objectstack/spec test && pnpm --filter @objectstack/spec typecheck` Test Files 789 passed | 1 skipped (790), Tests 23384 passed | 1 skipped | 1 todo, VERDICT command-exit 0. The skipped file is scripts/root-entry-type-nameability.pin.test.ts, which skips itself while spec dist is stale; rerun after the build, 2/2 passed. spec build && check:generated: 'All 14 generated artifacts are up to date', VERDICT command-exit 0. turbo build packages/** 78/78, VERDICT command-exit 0. Targeted translation.test.ts + src/conversions + src/migrations on 713814b9: 29 files, 1184 tests. Census pin + conversions-major18-merge + translation.test.ts: 289 passed. eslint --no-inline-config on the changed .ts files: exit 0. Consumers on 263f6c89: core 129/129 files, 2909 tests; metadata-protocol 297 passed + 4 skipped files, 22106 passed + 26 skipped tests; service-i18n 4/4, 71; each VERDICT command-exit 0. Carried from 102f342e: runtime, rest, downstream-contract, lint, cli and metadata readings and the E2E (round 3 moves no reader outside packages/spec; the seam test pins zh-cn and zh_cn -> zh-CN). Ablations on 263f6c89 in a side worktree via ablation-replace on registry.ts, restored blob 966e69e5 == HEAD; command vitest run translation.test.ts src/conversions retired-key-removal-version.pin.test.ts, 1049 green at HEAD. Case entry order 76.5 -> 75.5: 1 failed (the stored-row seam: zh_cn stays zh-cn). Case entry fold parsed.data -> v: 4 failed (seam, collision test, census TODO row, entry fixture). Census row wording, edited by hand and restored from a copy: dropping the release from the refusal, then from the TODO, each turned the row red (1 of 60). Merge fallback on 263f6c89 (local 4f8cfe7a, 8 files +85 / -92, git apply --check clean, re-read after 10 s): targeted translation + conversions + migrations + census pin 30 files / 1244 tests passed, spec build && check:generated VERDICT command-exit 0, check:adr-0087-registration 'this PR adds no declared-breaking changeset', check-changeset-no-major --event with the lines swapped exit 0 (LEVEL AXIS yes (widening) + semantic).",
      "gates": "dispatch-gates --commands at 263f6c89: 105 commands (75 pnpm, 30 direct node), the same list as at 3b3284c9; change set 9 paths vs merge base 6b3ba82cb, 565 changed lines (+543 / -22). All 105 exit 0, exits captured before any pipe. --ran: '105 derived famil(ies) accounted for — 105 run, 0 NOT-MEASURED (a DERIVED zero — all 105 recorded an exit code and none of them is 3)'. check-adr-0087-registration --base origin/main (one of the 105): '1 declared-breaking changeset(s), each carrying an ADR-0087 disposition', registered translation-locale-case-canonicalized (new here). check-changeset-no-major --base origin/main --event (new body, offline): exit 0, LEVEL AXIS no (narrowing) + narrowed, direction arm narrowing, @objectstack/spec minor. check-partof-closing-keyword with the new body: exit 0. PR check-runs on 263f6c89 before the body edit: 65 completed, every one success or skipped (Test Core 8/8 and aggregate, Test UI 16/16 and aggregate, Type Check UI, Build & E2E UI, Live E2E UI, Build Core, Build Docs, Dogfood Regression Gate aggregate (its shard matrix skipped, so it ran no battery), Spec property liveness, Check Changeset, PR-body guards, every Lint and Type Check job). After the body edit, the body-triggered checks re-run; not waited on.",
      "dogfood_battery": "263f6c89 · os-verify-lock VERDICT command-exit 1 (held 36m56s) · `pnpm exec turbo run build --concurrency=2 --filter='@objectstack/dogfood^...' && pnpm --filter @objectstack/dogfood test`, no OS_TEST_SHARD, no path · closure Tasks 68/68 · Test Files 1 failed | 383 passed | 3 skipped (387) · Tests 3161 passed | 33 skipped (3194) · failure: test/package-first-authoring.dogfood.test.ts. Its first beforeAll, bootStack(crmStack), hit the 10000 ms hook timeout. The other three describes then refused to boot because 'com.example.crm already has a live bootStack kernel in this process', so their 5 tests were skipped (33 skipped against round 2's 28). The battery ran beside the 105-gate loop on the same 4 cores. That file alone under the lock, run twice: 1/1 file, 5/5 tests both times, VERDICT command-exit 0 (held 41 s and 36 s). Read as a boot timeout under contention, not a consequence of this change; reported as found, nothing skipped or narrowed.",
      "mcp_calls": "9 — rounds 1–2: 8 reads (see the round-2 report); round 3: pull_request_read get_check_runs x1. All reads, no MCP write.",
      "api_writes": "7 — rounds 1–2: 5 (comment issuecomment-6571338866, pr_create #29190, comment issuecomment-6573167205, issue_patch #29190, round-2 report); round 3: issue_patch #29190 body through the relay (24297 bytes sent, 24297 stored, identical), this os-dev-report comment. Plus git pushes 713814b9 and 263f6c89 to the branch.",
      "files_changed": [
        ".changeset/28993-locale-case-canonicalized.md",
        "content/docs/protocol/kernel/i18n-standard.mdx",
        "packages/spec/dropped-refinements.baseline.json",
        "packages/spec/src/conversions/registry.ts",
        "packages/spec/src/migrations/entries/semantic/18.translation-locale-case-canonicalized.ts",
        "packages/spec/src/migrations/entries/semantic/18.translation-locale-non-tag-refused.ts",
        "packages/spec/src/shared/retired-key-removal-version.pin.test.ts",
        "packages/spec/src/system/translation.test.ts",
        "packages/spec/src/system/translation.zod.ts"
      ],
      "line_budget": "565 changed lines (+543 / -22) over 9 files, under the 3000 human-merge threshold",
      "deviations": [
        "packages/spec/src/shared/retired-key-removal-version.pin.test.ts is a ninth file, which the census pin forces once the case entry is stamped 18.2.0. It gains a JOIN row and a `todo` row kind, and its header names the new kind. The bundle collision refusal and the conversion's TODO now name `@objectstack/spec 18`.",
        "#25822's fixture changes one value (pt-br -> pt-BR on both sides, notices unchanged at 7) so the chain-wide fixture run stays disjoint. Its code and its retiredAfter '18.1.0' are unchanged.",
        "The dogfood battery's one red file (above) is reported, not re-run as a whole battery: the order runs the battery once on the final head. The file passed twice on its own.",
        "Ablations and the merge fallback were checked in a side worktree (pnpm install --frozen-lockfile --prefer-offline, about 17 s), outside the lock (light single-run vitest), while the main worktree ran the gate loop.",
        "Carried from earlier rounds: the PR body opens with the seat signature line (the relay refuses an unsigned issue_patch body), so `Fixes #28993` is the fourth line (check-partof-closing-keyword exit 0); the docs file is outside the Claim's listed surface; the E2E is a one-off scratch proof; the dropped-refinements ledger has +6 sites."
      ],
      "handoff_slices": [
        "domain:server — packages/metadata-protocol/src/protocol.ts saveMetaItem: on a translation save, store the parsed locale (graft parsed.data.locale onto the stored body, the sibling of withParsedStoredActions), so a lowercase save is canonical at rest. Test: save locale zh-cn, the stored row reads zh-CN. Seam: spec:LocaleSchema -> runtime:saveMetaItem (stored body). Round 3 adds to its acceptance: the rows the two exact matchers read (emailTemplates[].locale, sys_notification_template.locale) get the same fold, or their consumers fold case."
      ],
      "open_questions": [
        {
          "question": "Unchanged from round 2: the maintainer's word on the narrowing (asked on the seat's decision card). A translation bundle with two keys that fold to one tag: refuse naming both (as built), or merge with the canonical key winning. In-repo count 0.",
          "options": [
            "Refuse (as built on 263f6c89): lines stay no (narrowing) / narrowed, BREAKING, changeset as is.",
            "Merge: `git am` /mnt/project-files/spec-seat1/28993-merge-fallback.patch (one commit on 263f6c89, 8 files +85 / -92). The refinement, its 6 ledger sites, the BREAKING banner and the marker go; the census row keeps only the TODO; the lines go back to yes (widening) / semantic. The PR body then needs its lines and 速读 swapped back."
          ],
          "recommendation": "Refuse, as the seat's card recommends; either answer lands on this PR in one commit."
        }
      ],
      "out_of_scope_findings": [
        "carrier: none — noted, not filed: lint liveness comments name the old key schema; EmailTemplateDefinitionSchema.locale does not use LocaleSchema (unchanged from round 1). The two exact matchers are named in the D3 entry and in the server hand-off above."
      ]
    }

    Stamped 2026-11-02T12:41Z. Round 3 on PR #29190 at 263f6c8: S1 and S2 are taken, ready for the delta review. Landing still waits for the maintainer's answer on the decision card.


    Generated by Claude Code

  8. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS

    Decision needed · refuse or merge a case-colliding bundle · 2026-11-02T14:41Z

    维护者速读

    • 要你定:翻译包同时有 zh-cn 和 zh-CN 两个键(本卡统一 locale 大小写后会撞成同一个)时,要不要拒收?
    • A(席位推荐)拒收并点名两个键:保存时报错,指出两个键,作者自己合并。一处小的收窄(BREAKING,已带迁移说明和 ADR-0087 条目)。静默合并会悄悄丢掉一边的翻译;报错符合「出错要大声说」。
    • B 自动合并,规范写法优先:不报错,条目合到 zh-CN 下,同名条目以 zh-CN 为准。不收窄,但合并是静默的。
    • 现状:PR feat(spec)!: a locale tag written in another case is corrected to its BCP-47 case #29190 按 A 实现,契约复审两轮 PASS;B 的替换补丁已备好(/mnt/project-files/spec-seat1/28993-merge-fallback.patch)。保持草稿,等决定。

    Seat recommendation: A. The seat lands PR #29190 as built on A, or applies the prepared patch on B, once the ruling is on this card.

  9. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS

    Hand-off · spec seat 1 going offline · 2026-11-02T21:52Z

    Spec seat 1 is closing for the day on the maintainer's word 「spec 车道当前任务处理完就下班」. This card is still waiting on the "Decision needed" above (6579975796), so it stays needs-user-decision, and the seat releases its hold on it.

    For whichever spec seat picks it up once ruled:

    • PR feat(spec)!: a locale tag written in another case is corrected to its BCP-47 case #29190 (branch claude/project-thread-ih2cyh, head 263f6c89, draft) is built on A (refuse a case-colliding bundle and name both keys). The contract review passed it twice.
    • On A: the head is hours old. Regen-merge origin/main with scripts/pm/os-regen-merge.sh, then run the full spec test + test:repo. Carry the PASS forward as merge-only, then ready + automerge.
    • On B: apply /mnt/project-files/spec-seat1/28993-merge-fallback.patch (silent merge, canonical spelling wins). Amend the claim to drop the narrowing lines, then run a delta review on the new head.
    • The seat-1 brief for this card is /mnt/project-files/spec-seat1/28993-delta.md.
  10. os-support-ai commented on Nov 3, 2026

    @os-support-ai
    Collaborator

    decision thread · os-support-ai · session_01WGufzepGFmf3XtoZaZuheq

    Ruling: decision thread "Discuss pending spec rulings" item 2 · letter A · maintainer 「同意」 2026-11-03T04:55Z

    The maintainer was presented the "Decision needed" above (6579975796) with the recommendation A in the project thread, and answered 「同意」 = the recommendation on every item presented, so this card's letter is A.

    What the ruling binds (letter A):

    • A TranslationBundle / PlatformTranslationBundle record holding two keys that fold to one tag (zh-cn beside zh-CN) is refused at the parse, naming both keys and the tag they fold to; the author merges by hand. Picking a winner silently would discard one translator's strings; the in-repo count of such pairs is 0.
    • The refusal is declared as the narrowing it is: Clause-②: no (narrowing) · Metadata-protocol: narrowed, BREAKING with its migration and the ADR-0087 disposition, as PR feat(spec)!: a locale tag written in another case is corrected to its BCP-47 case #29190 carries at head 263f6c89. The case fold itself stays the semantic change the D3 entry translation-locale-case-canonicalized records.
    • Not taken: B (deterministic merge with the canonical key winning; the prepared patch stays unused).
    • Out of this card: the save door stores the raw body, so the stored-row canonical write (saveMetaItem grafting the parsed locale) is the domain:server sibling card named in the dev's handoff_slices; reads are already canonical through the conversion.

    Workload of the letter: none beyond PR #29190 as built (two contract-review PASS records); the seat regen-merges origin/main, reruns the full spec test + test:repo, carries the PASS forward as merge-only, then ready + automerge (the hand-off's "On A" steps, 6589396717).

    Execution: spec seat 2 (which holds PR #29190) lands it with Ruling-ref: naming this comment; the needs-user-decision → pm:dispatched stroke is that seat's. The Ruled: body line is written by this act.


    Generated by Claude Code

  11. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#2 · os-support-ai · session_01WGufzhJG3tKnksHVa1krBz

    Claim: PM loop round 3 (resume, land as built)
    Session: session_01WGufzhJG3tKnksHVa1krBz
    Account: os-support-ai
    Branch: claude/project-thread-bo9b0g (card thread, executor session_01WGufzfTZqxsVg4dG5puUE2; the thread can push only its own branch, the measured platform exception to claude/issue-28993-…)
    Worktree: objectstack-issue-28993
    Domain: domain:spec
    Seat: domain:spec#2
    File surface: PR #29190's nine files as built (.changeset/28993-locale-case-canonicalized.md, content/docs/protocol/kernel/i18n-standard.mdx, packages/spec/dropped-refinements.baseline.json, packages/spec/src/conversions/registry.ts, packages/spec/src/migrations/entries/semantic/18.translation-locale-case-canonicalized.ts, packages/spec/src/migrations/entries/semantic/18.translation-locale-non-tag-refused.ts, packages/spec/src/shared/retired-key-removal-version.pin.test.ts, packages/spec/src/system/translation.test.ts, packages/spec/src/system/translation.zod.ts); merge of origin/main only
    Container & model: S, mode:thread, model: TIER_DEFAULT, effort: high
    Clause-②: no (narrowing)
    Metadata-protocol: narrowed
    Ruling-ref: 6595806433 (letter A, maintainer 「同意」 2026-11-03T04:55Z)
    Resume-from: claude/project-thread-ih2cyh@263f6c891eac65f49418adc1f13aa7004bf6b9be (PR #29190, contract review PASS 6577781599 and 6577787009 on that head)
    Thread-read: 6595806433
    Premise: holds — the ruling takes the case-collision refusal as built; seat 1 released the card (6589396717) and this seat took it over on the maintainer's word. A narrowing: the full dogfood battery is pinned to the final head.
    Serial constraints cleared: no open PR of this seat edits the translation bundle schemas or the migration entries this PR adds; the saveMetaItem canonical-write slice stays the domain:server sibling card.

  12. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#2 · os-support-ai · session_01WGufzfTZqxsVg4dG5puUE2
    os-dev-report

    {
      "issue": 28993,
      "status": "done",
      "branch": "claude/project-thread-bo9b0g",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29833",
      "session": "session_01WGufzfTZqxsVg4dG5puUE2",
      "premise_still_valid": true,
      "summary": "Resumed #29190 as built (ruling A, 6595806433): the pinned branch starts at 263f6c89 and adds one merge of origin/main (62f265f0, main at b3d14150). One conflict, packages/spec/dropped-refinements.baseline.json (count line and two re-sorted site lists, a ledger outside the stop list), resolved as main's sites plus this PR's 6 translations sites, counts 250 / 1018; os-regen-merge.sh restored no generated path. The other eight files' added/removed lines against main hash-equal #29190's against its base, so the locale fold, collision refusal, D3 entry and conversion registry are unchanged. No behaviour change. Draft PR #29833 opened with #29190's body, Supersedes #29190 and Ruling-ref: 6595806433 lines, the ruling written into 维护者速读 and Acceptance notes, and a new Verification section for the resumed head. Not governed (check-governed-merges --branch: 0 of 9 paths), so 需要你做的 reads 无,席位落地. #29190 is left for the seat to close.",
      "tests": "All on 62f265f0, under os-verify-lock. turbo run build --filter=!@objectstack/docs --concurrency=2: Tasks 117 successful, 117 total. spec check:generated --fix: All 14 generated artifacts are up to date, nothing regenerated. spec test: Test Files 804 passed (804), Tests 23909 passed, 1 todo, exit 0. spec typecheck: exit 0. spec test:repo: Test Files 89 passed (89), Tests 1522 passed, exit 0. Merge only, so no ablation was rerun; #29190's ablations on 263f6c89 are carried in the body.",
      "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at 62f265f0: 104 commands (74 pnpm, 30 node), 9 paths vs merge base b3d14150; all 104 exit 0 (each captured before any pipe), --ran: 104 run, 0 NOT-MEASURED, 0 UNRUN. check-changeset-no-major --base origin/main --event with the PR title and body: exit 0 (no (narrowing) / narrowed, arm narrowing, no major). check-partof-closing-keyword with the body: exit 0. check-governed-merges --branch HEAD: NOT governed, 561 changed lines. The derivation warned the tree was 5 commits behind origin/main with package.json moved; that move is a svelte devDependency bump only, so the families do not change. Before pr_create origin/main was 23 commits ahead with no conflict (merge-tree). PR check-runs: in_progress at report time; the known PR-body guards duplicate-fix red is expected until #29190 closes.",
      "dogfood_battery": "62f265f0 · os-verify-lock: VERDICT command-exit 0 · held the lock 2598s (43m18s, spec test + typecheck + test:repo + dogfood in one locked script, after the full build) · Test Files 404 passed, 3 skipped (407) · Tests 3326 passed, 28 skipped (3354) · no failure",
      "mcp_calls": "0 — no GitHub MCP calls",
      "api_writes": "2 — relay pr_create (#29833, read back byte-identical, no assignees), this os-dev-report comment via post-stamped.mjs; plus one git push of claude/project-thread-bo9b0g (not REST). No assignee or reviewer was set: os-dev rule 2 beats the harness default.",
      "open_questions": [],
      "security_findings": "withheld",
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  13. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#2 · os-support-ai · session_01WGufzhJG3tKnksHVa1krBz

    Landed. PR #29833 merged through the queue (merge commit 86d0a8cd); this card closed as completed by its Fixes line.

    Reading Value
    PR #29833 MERGED at 2026-11-03T07:33Z, head 62f265f0, contract review PASS 6577781599 + 6577787009 (on 263f6c89) + delta 6597563334, dogfood on the final head 404 passed / 0 failed
    Card #28993 closed (completed)

    State change: pm:dispatched removed. Ruling A (6595806433) is what shipped: a case-colliding bundle is refused naming both keys. Left elsewhere: the saveMetaItem canonical write of the stored row, the domain:server sibling card the dev report's handoff_slices names. #29190 stays closed as superseded.

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

Metadata

Metadata

Assignees

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