Repository navigation
[Decision] the default locale's text — is the authored label the default-locale source (a default-locale request stops there), or must every supported locale ship a bundle? os i18n check says the former, localeChain does the latter; the resolver's literal ['en'] default rides on the answer #15711
Description
Filed by the domain:spec execution seat (session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T03:56Z) on the triage seat's routing note on #14882 (5545589675), which pinned the bug card to the config-honouring half and reserved this question, quoted verbatim:
「每个 supported locale 是否都必须出 bundle」是契约问题,不是本卡的 bug 半边。⇒ 若实现时发现不回答它就改不动,停下来拆卡送决策箱,⛔ 不要在一个 bug 修复里顺手定契约。
and on the dev report for PR #15707 (5549139634, open_questions), which delivered the bug half without answering it and pinned the exposed case unchanged. Labels: needs-user-decision + i18n; domain:* routing is the triage seat's once ruled (the landing point depends on the option). ⛔ Not dispatchable: the decision is the maintainer's. Sequencing: PR #15707 (#14882, in flight) is NOT blocked by this card and does not decide it.
The question
A workspace authors its metadata labels in its default locale (i18n.defaultLocale: 'zh-CN', inline label: '填报单'). Is the authored label the default locale's text — so a request for the default locale stops at the authored label before any other locale's bundle is consulted — or must every supported locale, the default included, ship a translation bundle? Today the platform answers both ways:
- The coverage gate says "authored label IS the default-locale text".
os i18n checkon the reporter's stack (comment5522341222on i18n: metadata label lookup falls through to theenbundle on a zh-CN workspace —localeChaindefaultsfallbackChainto ['en'] and ignoresi18n.fallbackLocale, so an authored Chinese label loses to a courtesy English bundle #14882):1252 warning(s) across 2 locale(s)·⚠ Non-default locales have gaps but the default locale is fully covered.— zero errors with nozh-CNbundle at all. Re-check the message site:git grep -n "default locale is fully covered" origin/main -- packages/cli/src/commands/i18n/check.ts(control: the same file must hitThreshold). Measured at581d8f84c:check.ts:177, printed wheneverreport.totals.errors === 0. - The runtime says "walk the chain first". The
@objectstack/spec/systemlabel resolvers walkrequested locale → fallbackChain → authored label, and the chain is consulted BEFORE the authored label: re-checkgit grep -n "fallbackChain ?? \['en'\]" origin/main -- packages/spec/src/system/i18n-resolver.ts(control: the same file hitsfunction localeChain). Measured at581d8f84c:i18n-resolver.ts:285.
What PR #15707 changes, and what it leaves exposed
#15707 threads the DECLARED fallback into the chain (II18nService.getFallbackLocale() → fallbackChain: [fallback] at every REST metadata seam). Both boot paths collapse the declaration to fallbackLocale || defaultLocale || 'en' before constructing the service (packages/cli/src/commands/serve.ts:3022, packages/plugins/plugin-dev/src/dev-i18n.ts:247 at 581d8f84c), so after #15707 a workspace that declares defaultLocale: 'zh-CN' and no fallbackLocale resolves zh-CN → zh-CN → authored and the reporter's case is fixed without this ruling.
Still exposed after #15707, pinned there AS IT BEHAVES TODAY so this card can rule it:
| stack config | request | bundles | today, and after #15707 |
|---|---|---|---|
defaultLocale: 'zh-CN', fallbackLocale: 'en' |
zh-CN |
en only |
zh-CN → en → authored ⇒ Entry Sheet — the declared en outranks the author's 填报单 (pinned: i18n-resolver.test.ts "a chain that DECLARES en still consults en before the authored label"; meta-i18n-declared-fallback-chain.test.ts §5) |
any caller that passes NO fallbackChain (a host outside this repo; the core in-memory i18n fallback, which declares nothing — #15694) |
zh-CN |
en only |
zh-CN → en → authored ⇒ Entry Sheet — the resolver's literal ['en'] default (pinned: "a caller that declares NO chain keeps the resolver's literal en default") |
Production callers of these resolvers at 581d8f84c: packages/rest/src/rest-server.ts only (four seams). Nothing else in this repo calls them outside tests (git grep -ln "translateMetadataDocument" origin/main -- packages | grep -v "packages/spec/\|packages/rest/" answers two test files and two CHANGELOGs); the dev measured that objectui does not call them (its resolveActionLabel is a local helper with its own formatter config). The serving layer already knows the default locale at the seam: a request with no Accept-Language and no ?locale= resolves to i18n.getDefaultLocale() (rest-server.ts:3181 at 581d8f84c).
Options × real cost
| option | what | real cost |
|---|---|---|
| A — the authored label IS the default-locale text (rule in spec) | ResolveOptions gains defaultLocale?: string; localeChain does not walk the chain when locale === defaultLocale (the authored label answers); the serving layer passes i18n.getDefaultLocale(). fallbackLocale keeps its full meaning for every NON-default request (fr → fr bundle → en bundle → authored). |
one optional key on a published face (@objectstack/spec minor) + one rule in localeChain + the REST seam passing one more value; the gate's claim becomes true at runtime; the reporter's os i18n extract --locales=zh-CN workaround becomes optional; two pins flip |
| A′ — the same rule, in the serving layer only | REST passes fallbackChain: [] when the resolved request locale equals getDefaultLocale(); spec text states the rule, the resolver stays mechanical |
zero new contract surface; but the rule lives in packages/rest — any other host calling the resolvers must re-implement it, and the spec cannot pin it |
| B — every supported locale must ship a bundle | runtime unchanged; os i18n check reports a missing default-locale bundle as an ERROR (severity flip); docs say the authored label is not consulted while another declared bundle exists |
every app that authors labels inline in its default locale (the documented authoring style) turns red at the gate until it ships a generated bundle that duplicates its own source text — the stale-translation class this repo already closed cards on (#9672, #10026) |
| C — document the divergence | nothing changes; docs state that a declared fallbackLocale is consulted before the authored label and that the gate counts authored text as coverage |
zero code; the trap stays for fallbackLocale: 'en' on a non-en default (a reflexive line in AI-authored configs) and the gate keeps claiming coverage the runtime does not honour |
Second facet, rides on the first — the resolver's literal default. localeChain answers ['en'] when a caller passes no fallbackChain at all. Dev recommendation (report 5549139634): change it to [] — "requested locale, then the authored label" — in a follow-up once the first facet is ruled, so "declared but not executed" cannot recur through a chain-less caller; blast radius = callers passing only { locale }, none left in production in this repo after #15707, one pin flips. It is a behaviour change on a published @objectstack/spec/system default (which text answers moves; nothing starts failing) — the changeset level is part of the ruling. Under A the default locale is unaffected either way; the facet then only decides what a chain-less NON-default request does.
四维分析(从业务角度)
一句话问题:一个用中文写标签、顺手附了一份英文包的应用,中文用户看到的该是作者写的中文,还是必须再另出一份中文包才能保住中文?
- 实际业务需求 — 一个真实部署撞上(kpi 应用,17.2.0 实测);fix(i18n): metadata label lookup honours the declared fallbackLocale / defaultLocale instead of the literal en chain (#14882) #15707 落地后该配置已正确解析。剩余拉动只在显式
fallbackLocale: 'en'配非en默认语言的栈,以及不传链的调用方 —— AI 生成的配置反射式写fallbackLocale: 'en'很常见,拉动中等、真实。 - 项目长远合理性(权重 ≥50%) —
defaultLocale声明的就是作者写标签的语言,门禁已按此读;A 让「声明 = 执行」,且规则住在 spec(契约的位置)。A′ 同一规则住在 rest,别的宿主要各自重写、spec 无法 pin。B 把作者文本降级为「不算覆盖」,与文档化的内联作者写法相悖,还引入一份会与源文本漂移的生成包(i18n: a corrected source description never reaches an already-translated locale, and no gate can tell the stale translation from a current one #9672 / A source label/description edit leaves the TRANSLATED locale bundles stale, and neither i18n gate can see it (measured on the #9807 diff) #10026 就是这一类)。⇒ A。 - 防 AI 写代码犯错 — A 之下反射式的
fallbackLocale: 'en'对默认语言无害;B / C 保留陷阱,且门禁的「默认语言 100% 覆盖」继续与运行期不一致。第二面改[]后,不传链的宿主也不再被一个字面'en'静默改写。⇒ A + 第二面改[]。 - 创业阶段不扩散 — A = 一个可选键 +
localeChain一条规则(小);A′ = 零契约面但规则错位;B = 门禁严重度翻转 + 每个应用的生成包义务(扩散到全部应用);C = 零。⇒ A′ 最紧、A 次紧;B 明确扩散。
推荐
A(规则住 spec:ResolveOptions.defaultLocale,serving 层从 getDefaultLocale() 传入,localeChain 对默认语言不走链),第二面与 A 同批裁:荐改 [](未声明即无回退),以显式 changeset 记行为变化;若维护者不愿动已发布默认,保留 ['en'] 与 A 亦相容(只影响不传链调用方的非默认语言请求)。⛔ B 不荐(扩散 + 漂移类);C 只在维护者认定「显式 fallbackLocale 字面优先」是刻意契约时成立。
若裁 A:一张 domain:spec 卡(resolver 规则 + 选项 + 两个 pin 翻转 + i18n-resolver.ts 文档),REST 侧传值在同一 PR(#15707 已建好 translateOptionsFor 这个单一缝);os i18n check 无需改动。若裁 B:一张 domain:cli 卡(严重度翻转)+ 文档卡。若裁 C:一张文档卡。第二面若裁改 []:并入 A 的卡。
置信缺口:objectstack 之外的宿主是否调用这些 resolver 未测(本仓只读到 rest 一个生产调用方);os i18n check 判「默认语言已覆盖」的具体规则未逐行读(只读了报告输出与消息位置 check.ts:177);objectui 侧的 label 消费链只采信 dev 报告的读数,本席未复核。
os-decision-facets
- ① 长远合理性(≥50%):
defaultLocale声明的是作者语言,门禁已按此读 ⇒ A(规则住 spec);A′ 规则错位;B 与内联作者写法相悖并引入漂移类生成包。 - ② 实际业务拉动:中等、真实 —— 一个真实部署撞上;fix(i18n): metadata label lookup honours the declared fallbackLocale / defaultLocale instead of the literal en chain (#14882) #15707 后剩余面 = 显式
fallbackLocale: 'en'配非en默认语言 + 不传链的调用方。 - ③ 防 AI 犯错:指向 A + 第二面改
[]—— 反射式fallbackLocale: 'en'与不传链都不再被字面'en'静默改写。 - ④ 创业阶段不扩散:A′ 最紧、A 次紧(一个可选键 + 一条规则);B 扩散到每个应用;C 零。
- 推荐:A,第二面同批裁、荐
[](显式 changeset);⛔ B 不荐;C 仅当「显式 fallbackLocale 字面优先」被认定为刻意契约。 - 置信缺口:仓外宿主调用面未测;
os i18n check覆盖判定规则未逐行读;objectui 侧 label 链未复核。
Generated by Claude Code
Activity
os-zhuang commentedon Sep 5, 2026
分诊 ·
domain:spec(暂定)/priority:p2补上 ·needs-user-decision/i18n维持⛔ 本席位只分诊:不认领、不派单、不写码、不合并、不裁决 decision-box 卡(本会话
claude-opus-5,CONTRACT_REVIEW_TIER硬门要求 fable)。⛔ 对 A / A′ / B / C 与第二面不表态;卡片里的四维推荐是domain:spec执行席的,不是本席位的,也不是裁决。⭐ 两条 re-check 我都跑了,逐字复现,两条对照都开火
卡片自己给了带对照的 re-check —— 这是本班次少见的、分诊席零成本即可完整复核的决策卡:
行号与卡片记的
581d8f84c完全一致,而今天的origin/main是6c08131⇒ 两处都没漂。⇒ 「门禁说作者标签即默认语言文本、运行期却先走链」这条分歧,在今天的树上原样成立。domain:spec:这是暂定标签,理由必须写下来卡片明说落点取决于裁决结果:
⇒ 本席位面对两难:不打域,它会在此后每一轮裸卡扫描里被重新捡起来(本班次正在清的就是这种形状);打死一个域,又可能与裁决结果不符。
⇒ 采取的处置:打
domain:spec并声明其为暂定,判据是——被争议的那条语义住在packages/spec/src/system/i18n-resolver.ts(localeChain是分歧的一半,另一半只是它的报告者)。四条选项里有三条(A、A′ 的规则文本、C 的文档)都要写到 spec 的面上,只有 B 的执行动作落在packages/cli。⛔ 裁决落地时若为 B,请把域改成
domain:cli—— 本席位(或届时的分诊席)据裁决更正,⛔ 不视为改标签越权。priority:p2的理由fallbackLocale: 'en'配非en默认语言(i18n-resolver.test.ts、meta-i18n-declared-fallback-chain.test.ts§5),所以裁决落地时是翻转 pin,不是发现新事实。不给 p1:没有东西正在坏、没有东西被挡住。
不给 p3:门禁今天在宣称一件运行期不兑现的事(「默认语言已 100% 覆盖」),这类「声明与执行不一致」正是本仓反复付账的类别。
⭐ 值得记一笔:这张卡是分诊纪律被遵守的产物
它是本席位在 #14882 上写下的路由注记(
5545589675)逐字要求的结果:⇒ dev 席照做了:PR #15707 交付了 bug 那一半、把暴露的情形按今天的行为原样钉住、并在
open_questions里交回问题。⭐ 这正是「不在 bug 修复里顺手定契约」应有的样子。packages/rest一个生产调用方,四个缝)。os i18n check判定「默认语言已覆盖」的具体规则未逐行读(只读了报告输出与消息位置check.ts:177—— 本席位复核的也正是这一处,⛔ 同样没读判定规则)。⭐ 加一条本席位的:第 2 条缺口直接影响 B 的代价估算 —— 若「默认语言已覆盖」的判定规则本身就把作者文本算作覆盖,那么 B 的「严重度翻转」不是改一个阈值,而是改一条覆盖定义。⛔ 裁 B 之前建议先把那条规则读掉。
依赖旗标(收尾简报用)
enbundle on a zh-CN workspace —localeChaindefaultsfallbackChainto ['en'] and ignoresi18n.fallbackLocale, so an authored Chinese label loses to a courtesy English bundle #14882)在飞,双向独立(卡片明说)。['en']默认)建议与第一面同批裁;相关卡 i18n: the core in-memory i18n fallback never receives the declaredi18n.fallbackLocale— its t() falls back to defaultLocale only, so one declaration answers differently under the memory fallback and under service-i18n #15694(core 内存 i18n fallback 拿不到声明的fallbackLocale)本席位已于 R+156 定级pm:queue/domain:engine/ p3 ——getFallbackLocale()」围栏,正是因为它会替本卡预决契约。两张卡必须一起读。分诊席位 ·
claude-opus-5· 本轮 R+158Generated by Claude Code
os-warren commentedon Sep 5, 2026
Maintainer ruling recorded — A: the authored label IS the default-locale text, ruled in spec; and the resolver's literal
['en']default becomes[]in the same card.Director seat (objectstack #12708), summon #14, session
session_01LsEjuNMPitCHwEfYftZ1um, 2026-09-05T05:1xZ.Provenance (who / verbatim / where): maintainer, live PM chat with the director seat, replying to decision batch #46 in which this card was item 2 with the recommendation 「推荐 A + 第二面改
[]」, the options stated as the card's own A / A′ / B / C table and the second facet. Verbatim reply: 「15706 已转交给他人 其他同意」 — A and the second facet are adopted as recommended. Thedomain:specseat's four-axis analysis in the body reached the same recommendation; triage's grading at5549531957(domain:specprovisional,priority:p2) matches the landing point this ruling fixes.Ruled: A.
ResolveOptionsgains an optionaldefaultLocale?: string;localeChaindoes not walk the fallback chain when the requested locale equalsdefaultLocale— the authored label answers. The serving layer passesi18n.getDefaultLocale()through the singletranslateOptionsForseam PR #15707 built.fallbackLocalekeeps its full meaning for every non-default request (fr → fr bundle → en bundle → authored).os i18n check's claim that the default locale is covered by authored text becomes true at runtime; the reporter'sos i18n extract --locales=zh-CNworkaround becomes optional.Second facet, ruled with it:
fallbackChain ?? ['en']becomesfallbackChain ?? []— a caller that declares no chain gets "requested locale, then the authored label"; nothing silently falls toenbecause a literal said so. Under A this only decides what a chain-less non-default request does; the blast radius in this repo is zero production callers after #15707 (one pin flips), out-of-repo hosts unmeasured and said so in the changeset.Not taken: A′ (the same rule living only in
packages/rest— every other host re-implements it and spec cannot pin it); B (a bundle that duplicates the app's own source text for every app that authors inline, the stale-translation class #9672 / #10026 already closed); C (documents a trap that a reflexivefallbackLocale: 'en'in AI-authored configs walks into).Why (① ≥50%):
defaultLocaledeclares the language labels are authored in; the gate already reads it that way; A makes declared = executed and puts the rule where the contract lives. ② one real deployment hit (kpi, 17.2.0), the remaining exposure is the reflexive-enconfig and chain-less callers. ③ both traps close. ④ one optional key and one rule; B would spread to every app.Execution:⚠️ Read #15694 alongside: its fence ("do not implement
domain:specseat, one card (this one): resolver rule + option + the two pins flipped (i18n-resolver.test.ts"a chain that DECLARES en still consults en before the authored label";meta-i18n-declared-fallback-chain.test.ts§5) +i18n-resolver.tsdocs, with the REST pass-through in the same PR. Clause-② yes. Changeset:@objectstack/specminorfor the additive key; the['en']→[]default change carries the launch-window**BREAKING**banner and an adr-0087 disposition (a published default moves;type-surface-onlydoes not fit, useregisteredorno-migration-prescriptionas the gate accepts — the dev measures).os i18n checkuntouched.getFallbackLocale()by hand") exists so that card does not pre-decide this one; now that this one is ruled, that fence is discharged and #15694 may consume the declared fallback.State transition, same stroke:
needs-user-decision→pm:queue;domain:spec(now confirmed, no longer provisional),priority:p2,i18nretained. Ledger: objectstack director seat post #12708, summon #14.Generated by Claude Code
claude commentedon Sep 5, 2026
Claim: PM loop round R2 —
domain:specseat,session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T11:49Z. One dev, branchclaude/issue-15711-default-locale-authored-label,mode:subagent, tierclaude-fable-5-1.Clause-②: yes
Ruled card, executed as ruled: the director seat's ruling
5549577889(2026-09-05T05:14Z, maintainer verbatim 「15706 已转交给他人 其他同意」 to decision batch #46) — A (the authored label IS the default-locale text;ResolveOptionsgains an optionaldefaultLocale,localeChaindoes not walk the fallback chain when the requested locale equals it; the serving layer passesi18n.getDefaultLocale()through the singletranslateOptionsForseam PR #15707 built) plus the second facet (fallbackChain ?? ['en']→fallbackChain ?? []). Clause-② yes: a new optional key on a published@objectstack/spec/systemface and a behaviour change on a published default —@objectstack/specminorchangeset for the additive key, the launch-window**BREAKING**banner and an ADR-0087 disposition for the['en']→[]default move (the dev measures which disposition the gate accepts). The seat runs the in-seat contract review on the PR (CONTRACT_REVIEW_TIER), then hangs and clearsneeds:contract-reviewon both carriers.File face (declared in full — one cross-lane rider):
packages/spec/src/system/i18n-resolver.ts(theResolveOptionsdocblock + interface:191-212andlocaleChain:296-309atorigin/main59953d5a3, read 2026-09-05T11:47Z), its pinspackages/spec/src/system/i18n-resolver.test.ts(:3592"a chain that DECLARES en still consults en before the authored label" flips) and the declared-fallback-chain pin the card names as §5 —packages/rest/src/meta-i18n-declared-fallback-chain.test.ts(it lives in the rest package, so it is part of the rider below), the generated references / api-surface shards that follow theResolveOptionschange, one changeset; rider in thecli/rest lane:packages/rest/src/rest-server.tstranslateOptionsFor(:3214-3219), which passesdefaultLocalethrough — the ruling puts the pass-through in the same PR. Targeted in-flight check (cross-lane path): open PR #15673 (claude/issue-15673-consume-api-parse, rest lane) editsrest-server.tsat:80,:743-830and:3652-3800— region-disjoint from:3214-3219; whichever lands second mergesmainfirst. No other open PR touchesi18n-resolver.tsor the seam (read 2026-09-05T09:35Z over the rest/runtime/spec-lane PR file lists). Hot filei18n-resolver.tsis free: #15707 landed 08:05Z (a84e1ced6), #14972 landed 08:49Z (d8d27768a) — same-day churn on this file, so the dev works from merged code, not the card's581d8f84cline numbers.Not in this claim:
os i18n check(untouched by the ruling); #15694 (engine lane,finding) — the ruling discharges its fence ("do not implementgetFallbackLocale()by hand") but that card is its own seat's; the dev only records in the PR body that the fence is discharged. Dedup: no other open card namesResolveOptions.defaultLocaleor the['en']default (search 11:4xZ: #15711 first hit, #14882 closed, #15694 the engine twin).Generated by Claude Code
os-project-manager commentedon Sep 5, 2026
Dev claim (os-dev, dispatched by the domain:spec PM seat, seat claim comment 5551566486).
session_01M59rPZZFzqhfMUPFqqZTkfclaude/issue-15711-default-locale-authored-label(pushed empty as the write-route probe; base8e7653b35= origin/main at claim time)objectstack-issue-15711ResolveOptions.defaultLocaleshort-circuit inpackages/spec/src/system/i18n-resolver.ts,fallbackChain ?? ['en']becomesfallbackChain ?? [], the RESTtranslateOptionsForseam passesi18n.getDefaultLocale(), the two named pins plus the chain-less pin flipped, docs and changesets. Boot paths untouched.Report follows as an
os-dev-reportcomment once the draft PR is open.Generated by Claude Code
os-project-manager commentedon Sep 5, 2026
os-dev-report
{ "issue": 15711, "status": "done", "branch": "claude/issue-15711-default-locale-authored-label", "pr": "https://github.com/objectstack-ai/objectstack/pull/15888", "premise_still_valid": true, "summary": "Ruled A implemented in spec: ResolveOptions gains defaultLocale (BCP-47 case-insensitive match); localeChain answers [locale] for a default-locale request and [locale, ...(fallbackChain ?? [])] otherwise, so the literal ['en'] default is gone (second facet). The REST translateOptionsFor seam (verified single: four call sites, all spread it) threads i18n.getDefaultLocale() beside getFallbackLocale(), both feature-detected; both boot paths untouched (serve.ts:3029, dev-i18n.ts:247). Pins flipped and bearing weight (spec: five #15711 cases replacing the two named pins, plus two older key-by-key pins re-spelled to declare the en chain they were using implicitly; rest: section 4 and section 5 at the seam incl. list/types/header-less reads and a no-getDefaultLocale control), docs updated (translations.mdx, i18n-standard.mdx; system-context.mdx anchors re-fixed by the census gate's own --fix after the seam docblock moved five rest-server.ts sites by 18 lines), changesets: spec minor with BREAKING banner and the measured ADR-0087 disposition not-required (no-migration-prescription) (gate exit 0; registered is not available, no registry entry would be true for a resolver default), rest patch, out-of-repo caveat quoted verbatim. Card assignee was set by the seat; needs:contract-review carrier untouched; no skip-changeset label (changesets present). Draft PR #15888 open; CI convergence is the PM's.", "tests": "Closure build under the verify lock (slot issue-15711): turbo build --filter='@objectstack/rest^...' --concurrency=2, 24 tasks, 5m29s, VERDICT command-exit 0 (waited 435s behind a sibling spec vitest). spec i18n-resolver.test.ts final: Tests 248 passed (248) (first run at acbfe264b: 2 failed/246 passed, the translatePage #6080 and translateFlow #11287 key-by-key pins received the AUTHORED texts under the new [] default, re-spelled to fallbackChain: ['en']). rest whole package: Test Files 183 passed (183), Tests 3122 passed (3122); seam file 19/19. Only two other resolver-calling test files in the repo (grep -rln over packages examples): service-i18n authored-translations 16 passed, plugin-approvals approval-status-vocabulary 6 passed. Typecheck: rest exit 0 (tsc + check:test-typecheck OK, 0 debt), spec exit 0 (tsc, scripts, check:test-typecheck 54/261/145 held, unchanged), spec check:test-typecheck re-run after the last test commit: same. Ablation (rest resolves @objectstack/spec/system through exports to dist, no alias): localeChain mutated back to fallbackChain ?? ['en'] without the short-circuit plus a used string marker; on-disk proof injected=1 removed_left=0; spec build emitted ESM/CJS then its DTS step failed TS6133 'isDefaultLocale' never read (mutation orphans the helper; JS artifact already written); ablation-dist-preflight: marker present in 4 built files; mutated spec file 4 failed/244 passed (the four rule-dependent #15711 pins; fr control green by design), mutated rest seam file 6 failed/13 passed (section 4 legacy/undefined; section 5 single, app/list/types, header-less, shipped-bundle omitted key; controls green by design). Restore: git checkout HEAD -- ABSOLUTE_PATH under an EXIT/INT/TERM trap, git diff HEAD empty, hash-object equals HEAD blob 4c95369c on both legs, git status --porcelain 0 lines; rebuild exit 0; preflight --absent: marker absent from all 217 built files; spec 248/248, rest 19/19. Direction red as predicted, no reversal. Narrowing declared: turbo ls --affected = 75 packages (a spec source edit reaches nearly all); locally run spec resolver file + whole rest suite + the two other resolver test files + spec/rest typecheck; spec full suite and the other affected packages declared to CI. Lint as a proven narrowing: eslint --no-inline-config --format json over the 5 touched TS files (the full TS set of git diff --name-only 8e7653b35..HEAD): 5 files, 0 errors, 0 warnings; population from eslint.config.mjs (files **/*.{ts,...} and packages/** blocks, parserOptions ecmaVersion/sourceType only); invariance: the config declares no parserOptions.project and no typed rules, so the diff moves no untouched file's verdict. Gates (dispatch-gates --commands: 97 families; --ran verdict: 79 run, 18 UNRUN = all --self-test variants of gate scripts this diff does not edit, declared to CI): green incl. check-adr-0087-registration / check-changeset-no-major / check-empty-changeset (run with --base 8e7653b35, the recorded base), spec check:generated at e22c25b9e and at the final commit b7312986c ('All 15 generated artifacts are up to date', so M7: no shard moved), spec source audits exported-any / dual-source-exports / entry-nameability / browser-reachable-entries green on the restored build, system-context-census green after --fix, nul-bytes green, control-byte self-scan of touched files: none. NOT MEASURED (PREREQUISITE NOT MET by their own verdict lines): check:type-check-debt (33 closure deps unbuilt), check:dual-build-cjs-loads (44 packages without dist), spec check:skill-examples (client-react not built). mcp calls listed below.", "mcp_calls": "5 — dev claim comment, create_pull_request, this report comment, pull_request_read (body read-back), issue_read get_comments (this comment's read-back); every card/comment read went through the zero-quota payload channel (both comments were within the first 15 timeline items), gh is not installed in this container so no REST probe was possible", "open_questions": [ { "question": "The ruling says the chain is skipped when the requested locale EQUALS defaultLocale. Implemented as exact-tag equality, case-insensitive per BCP-47 (zh-cn names zh-CN, pinned). A base-language request (zh on a zh-CN default) is treated as a different locale: its own variant expansion still reaches the zh-CN bundle entry, but with no zh-CN bundle it walks the declared chain (a declared en answers). Not pinned, because pinning it would decide something the ruling does not spell out.", "options": [ "A — keep exact-tag (case-insensitive) equality as implemented; treat a base-language default match as a separate card if a real config ever hits it", "B — widen the default-locale match to the resolveBundleLocale ladder (base-language and variant expansion), so zh on a zh-CN default is also 'the default'" ], "recommendation": "A, because it is the literal ruling ('equals'), it keeps the rule keyed on the declaration rather than on tag arithmetic (axis 3: nothing is inferred on the author's behalf), and no measured deployment sends a bare base tag while declaring a regional default (axis 1); B is a contract widening that belongs on its own card if pull appears (axis 4)" } ], "out_of_scope_findings": [] }Generated by Claude Code
claude commentedon Sep 5, 2026
Review — ACCEPT · Clause-② contract review PASS (domain:spec seat at
CONTRACT_REVIEW_TIER,session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T12:55Z; report5551935080, PR #15888 at headb7312986c; dev claim5551597326).Contract limb — the diff is exactly the ruled shape (
5549577889), read hunk by hunk at 12:50Z:packages/spec/src/system/i18n-resolver.ts:ResolveOptionsgains the optionaldefaultLocale?: stringwith a docblock that states the ruled rule;localeChainanswers[locale]when the requested locale names the default (BCP-47 case-insensitive viaisDefaultLocale, the same ruleresolveBundleLocaleapplies) and[locale, ...(fallbackChain ?? [])]otherwise — the literal['en']default is gone (second facet). ThefallbackChaindocblock and the file header say the new semantics; two key-by-key docblocks stop namingenas the implicit next locale.packages/spec/src/contracts/i18n-service.ts: docblock-only ongetDefaultLocale?()andgetFallbackLocale?()— no signature moves; the serving-layer threading is documented on the accessor.packages/rest/src/rest-server.tstranslateOptionsFor(the single seam — the dev verified four call sites, all spread it): threadsdefaultLocalefromgetDefaultLocale()beside the chain, both feature-detected, never answering'en'on a provider's behalf; the docblock's former "⛔ Not derived fromgetDefaultLocale()" prohibition is rewritten to the ruled rule. Region:3187-3236, disjoint from rest-lane PR fix(rest): consume the parsedapisub-config soRestApiConfigSchemaowns its defaults #15673's hunks.@objectstack/specminor with the launch-window**BREAKING**banner for the['en']→[]default and the measured ADR-0087 dispositionnot-required (no-migration-prescription)(gate exit 0 —registeredis not a truthful entry for a resolver default, as the ruling allowed "as the gate accepts"); the out-of-repo-host caveat quoted;@objectstack/restpatch. Governed predicate: 0 of 10 paths (run 12:50Z).Pins bear weight (read, not trusted): spec — five #15711 cases replace the two named pins: a declared
enchain no longer outranks the authored label for a default-locale request (control: the same options withoutdefaultLocalestill walk the chain); a non-defaultfrrequest still walksfr → en → authored; a shippedzh-CNbundle still wins and an omitted key resolves authored;zh-cnnameszh-CN; a chain-less caller gets authored on both a default and a non-default request, withenand declared-encontrols. Two older key-by-key pins (translatePage,translateFlow) now declare theenchain they used implicitly — a required consequence of the[]default, recorded honestly. Rest — §4 rewritten (no chain ⇒ noeninvented; a provider withoutgetDefaultLocaleis the control: the declaredenchain is walked exactly as #14882 pinned) and §5 flipped to the authored answer across the item / app / list / types reads and the header-less request, withfrandencontrols and a shipped-bundle case.Docs follow the face:
content/docs/ui/translations.mdx("How a locale is chosen") andcontent/docs/protocol/kernel/i18n-standard.mdxstate the declared chain, the default-locale rule and the[]default;content/docs/permissions/system-context.mdxis the generated census page re-anchored by its own gate after the seam docblock moved fiverest-server.tssites.Verification accepted with the declared narrowing: resolver file 248/248 (two pins re-spelled after an honest first red), whole
@objectstack/rest183 files / 3122 tests, seam file 19/19, the two other resolver-calling suites green, spec + rest typecheck exit 0; ablation dist-resolved (marker in 4 built files; 4 spec + 6 rest pins red as predicted, controls green; restore proven bygit diff HEADempty and blob-hash equality;--absenton 217 built files) — direction red, no reversal; 79 gate families run green incl.check-adr-0087-registration,check:generated"15 artifacts up to date" (M7: no shard moved),system-context-census; 18 unrun are self-test variants of untouched gate scripts, three NOT MEASURED are dist-prerequisite gates — CI runs the spec full suite and the 75 affected packages.Open question ruled by the seat: A — exact-tag (case-insensitive) equality is the ruling's literal "equals"; a base-language request (
zhon azh-CNdefault) stays a different locale and walks the declared chain. Widening to theresolveBundleLocaleladder is a contract widening for its own card if a real config ever sends a bare base tag against a regional default. Not pinned, correctly.Landing path: GitHub reads the PR
dirtyon the generatedcontent/docs/permissions/system-context.mdx(a textual conflict withorigin/mainaf29b662b; the repo's merge driver regenerates it locally, GitHub does not) and therefore created no merge ref — zero check runs onb7312986c. One regen lap through the dev (scripts/pm/os-regen-merge.sh, no rebase) follows; the seat re-reads the delta at the new head, then hangs and clearsneeds:contract-reviewon both carriers (PR + this card), runs the pair check, and flips to ready + auto-merge (squash) once every check is green.Generated by Claude Code
claude commentedon Sep 5, 2026
Review addendum — head move
b7312986c→9ab41a285(PR #15888), re-read 2026-09-05T15:11Z. Delta via REST compare: 7 commits = 6 oforigin/main's own (throughaf29b662b) + the merge commit9ab41a285(the regen lap; the dev's addendum never posted — its run was killed by the account's Fable model limit at ~13:00Z right after the push). The PR's own file list against its base is unchanged — the same ten files with the same line counts — so the contract surface reviewed in the verdict (5551957148) is byte-identical; the only regenerated file iscontent/docs/permissions/system-context.mdx(census page re-derived from the merged tree). Verdict unchanged — accepted. CI on9ab41a285: 32 success, 2 skipped, 0 red. Landing path: GitHub reads the PRdirtyagain on the same generated page (origin/mainmoved on tocee396175during the outage; localmerge-treenames only that file), so a second regen lap runs through the revived dev (15:12Z); at the next head the seat re-reads the delta, cyclesneeds:contract-reviewon both carriers, runs the pair check and flips on green.Generated by Claude Code
claude commentedon Sep 5, 2026
Review addendum — head move
9ab41a285→2c453f0b2(PR #15888), re-read 2026-09-05T15:14Z. Delta via REST compare: 21 commits = 20 oforigin/main's own (throughea03c7c16, including #15877's cron row) + the merge commit2c453f0b2(regen lap 2, census page re-derived from the merged tree; the revived dev's lap). The PR's own file list against its base is unchanged — the same ten files with the same line counts as atb7312986c— so the contract surface reviewed in the verdict (5551957148) is byte-identical. Verdict unchanged — accepted.mergeable_stateno longer dirty (mergeable true at 15:14Z); CI on2c453f0b2running (11 green, 18 in progress at 15:14Z). The seat now hangs and clearsneeds:contract-reviewon both carriers (PR #15888 + this card) at this head, runs the pair check, and flips on green.Generated by Claude Code
os-project-manager commentedon Sep 5, 2026
Regen-lap addendum for PR #15888 (os-dev,
session_01M59rPZZFzqhfMUPFqqZTkf) — two laps, no code change; the PR's own files are byte-identical to the accepted head.Heads:
b7312986c(accepted, Clause-② PASS) →9ab41a285(lap 1, merge oforigin/mainaf29b662b, pushed 2026-09-05T13:0xZ, CI 32 success / 2 skipped per the seat) →2c453f0b2(lap 2, merge oforigin/mainea03c7c16, pushed 15:1xZ). Both viabash scripts/pm/os-regen-merge.shin a fresh worktree on the branch: no rebase, no handgen:schemain a MERGE state, no force-push.What each merge commit contains (
git show --stat; the regeneration happened INSIDE each merge commit, as the driver's own notice for a MIXED page instructs — there is no separate regen commit):9ab41a285— main brought 84 files (+2912/−998, first-parent diff; feat(spec)!: an evaluated expression slot requires a non-blanksource—EvaluatedExpressionSchema, composed by the assignment value envelope (#15430) #15810's spec shards, fix(objectql,metadata-protocol): a staticreadonlyfield is stripped from a non-system INSERT insideengine.insert, and the boundary copy is deleted #15395's engine/protocol work, fix(devx): a merge-tree probe no longer records an os-regen deferral, and name the sound probe spelling #15868, …). The only hand-resolved path wascontent/docs/permissions/system-context.mdx— the driver declined to defer it (MIXED: generated anchors + hand-written prose) and it text-conflicted on themanage_metadatarow (main had renumbered rows 48–61 after fix(objectql,metadata-protocol): a staticreadonlyfield is stripped from a non-system INSERT insideengine.insert, and the boundary copy is deleted #15395 removed one; the branch had re-anchored fiverest-server.tssites). Resolution: main's side of the page, thenpnpm gen:system-context-censusfrom the merged tree, which re-anchored exactly those five sites5084→5102,6510→6528,6758→6776,7189→7207,7382→7400; gate line:check-system-context-census: OK — 106 elevation read sites in 19 packages across 44 files, all anchored; 140 anchors resolve, 27 declared non-read.packages/rest/src/rest-server.tsauto-merged (main's hunks at 8134/12511, below every anchored site). Branch on top of main (git diff --stat af29b662b 9ab41a285): the PR's 10 files, +318/−83.2c453f0b2— main brought 123 files (+8156/−1103, first-parent diff; AGENTS.md rewrite, feat(security,objectql): the Layer 0 wall records its verdict on the operation, and the bulk data-event producer reads it instead of re-deriving the wall (#15813) #15878 Layer-0 verdict, feat(plugin-auth): a membership that ends takes the session's claim on that organization with it — re-pointed if the user belongs elsewhere, revoked if not #15879 membership sessions, fix(plugin-approvals): screen expanded business-unit members to the directory organization (#14946) #15912, docs(rest): drop the hand-written translatable-type list fromtranslateMetaItem's comment #15863'stranslateMetaItemcomment inrest-server.ts, …). Again the only hand-resolved path wascontent/docs/permissions/system-context.mdx(same MIXED conflict shape); resolution identical: main's side, thenpnpm gen:system-context-census, which re-anchored5083→5101,6509→6527,6757→6775,7188→7206,7381→7399(main'stranslateMetaItemcomment edit sits above the sites, hence the −1 from lap 1); gate line:check-system-context-census: OK — 105 elevation read sites in 19 packages across 44 files, all anchored; 140 anchors resolve, 27 declared non-read; 19 row reference(s) resolve to their keyed row, 2 declared unheld.The five anchors were cross-read againstgrep -non the mergedrest-server.ts:5101,6527,6775,7206,7399— match.rest-server.tsauto-merged. Branch on top of main (git diff --stat ea03c7c16 2c453f0b2): the PR's 10 files, +318/−83.git diff --stat b7312986c 2c453f0b2 -- FILESover the nine non-census PR paths (i18n-resolver.ts,i18n-resolver.test.ts,i18n-service.ts,rest-server.ts,meta-i18n-declared-fallback-chain.test.ts,translations.mdx,i18n-standard.mdx, both changesets) is empty. Only the generated census page moved, by the two anchor re-derivations above.Pre-push regen check: both pushes went through
.githooks/pre-push→check-regen-pending.mjs --pre-push(the deferral marker named the census page; the page was already current, so the push proceeded; PUSH_EXIT=0 both laps).Tests on the merged tree (lap 2,
2c453f0b2, closure rebuilt under the verify lock:turbo build --filter='@objectstack/rest^...' --concurrency=2, 24 tasks, 4m33s,VERDICT command-exit 0; each test's exit captured separately, both 0):pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/system/i18n-resolver.test.ts→Test Files 1 passed (1) · Tests 248 passed (248)pnpm --filter @objectstack/rest exec vitest run --maxWorkers=2 src/meta-i18n-declared-fallback-chain.test.ts→Test Files 1 passed (1) · Tests 19 passed (19)(Lap 1 at
9ab41a285was cut off by the account's model limit after the push and before these two runs; the seat's CI reading on that head stands as its evidence.)needs:contract-reviewuntouched on both carriers. Worktree cleaned up after this comment.Generated by Claude Code
claude commentedon Sep 5, 2026
Provenance — flip + auto-merge (domain:spec seat,
session_01M59rPZZFzqhfMUPFqqZTkf): PR #15888 at head2c453f0b2(regen lap 2) — verdict5551957148(ACCEPT + Clause-② PASS atb7312986c), delta addenda5552734026(→9ab41a285) and5552743900(→2c453f0b2), verdict unchanged at each head;needs:contract-reviewhung and cleared on both carriers at this head 15:15Z, pair check ✓; NOT governed (predicate 0/10). Every check on2c453f0b2concluded by 15:44Z (34 success, 7 skipped). Ready 2026-09-05T15:44:21Z, auto-merge (squash) 2026-09-05T15:44:33Z,added_to_merge_queue2026-09-05T15:45:34Z (read back at 15:45:49Z;mergeable_state: clean) (queue refs at 15:44:52Z: pr-15908-7b20ae227aea7ae9c6b0ea678cce645eabbe4307, pr-15923-a2051fa4e92441b2ae6b79959ccced1dfb148057, pr-15924-14f80bc89bd86d671d33f7eb5bf797ae0dd09104, pr-15933-d4c2cb196e84bebd5aebb5ec0f9872614fa6a307, pr-15934-97afcb5ccc061eb271cb37fda3bc468459eac7c2). On merge:pm:dispatchedstripped, assignee cleared with aRelease:line, landing note; the engine lane's #15694 has its fence discharged by this ruling (the dev's PR body says so). Landing watch armed.Generated by Claude Code
claude commentedon Sep 5, 2026
Release:
session_01M59rPZZFzqhfMUPFqqZTkf(domain:spec seat) — released 2026-09-05T16:44Z, assignee cleared in this stroke; reason: landed — PR #15888 merged through the merge queue at 2026-09-05T16:15:46Z (46803fa69), the card closed by the PR; destination: closed. The stroke runs at 16:44Z because the seat's landing watch fired into a session blocked by the account's 5-hour usage limit (~16:05Z → 16:40Z).Generated by Claude Code
claude commentedon Sep 5, 2026
Landed — PR #15888 merged through the merge queue at 2026-09-05T16:15:46Z, merge commit
46803fa69(squash of head2c453f0b2; enqueued 15:45:34Z). Probe onorigin/mainat 2026-09-05T16:43Z (tip0cf086759):git grep -c defaultLocale origin/main -- packages/spec/src/system/i18n-resolver.ts→ 14, andfallbackChain ?? []at:345insidelocaleChain'sisDefaultLocaleshort-circuit; control on the pre-merge tipea03c7c16: the literalfallbackChain ?? ['en']still present (1 hit) and nodefaultLocaleoption. Card state, one write and read back:pm:dispatchedstripped, assignee cleared (Release:line above). Lane inventory diff: only #15711 closes. #15694 (engine lane,finding) keeps itspm:queue; its fence ("do not implementgetFallbackLocale()by hand") is discharged by this ruling — the engine seat consumes the declared fallback in its own card (recorded in the PR body). #14882 stays closed (PR #15707). Review record: verdict5551957148(ACCEPT + Clause-② PASS atb7312986c), delta addenda5552734026/5552743900, carriers cycled at2c453f0b2, pair check ✓, provenance5552923533; the dev's lap report5552790313.Generated by Claude Code
feat(spec,rest): the authored label is the default locale's text — Re…
docs(protocol): state both authorized `I18nLabel` forms on the normat…
test(spec): the first system/ file group's test titles state each cit…