You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
spec: a single-word language name ("Chinese", "English", "Japanese") still passes as a translation locale — the #25822 fix refuses multi-word names only, though its changeset and refusal text say "a language name" is refused #26463
Filing gate: ① a product defect with a live reproduction. It is the residual of closed #25822, found by the retest of that fix (PR #25983).
Business reason: an author who writes locale: 'Chinese', a plausible spelling for a human or an AI author, gets a green save. Their strings are stored under a locale the console's language picker never selects (zh-CN), so they never render. This is the same silent "not translated" failure #25822 closed for zh_CN, and the changeset says this case is closed too.
Reader: the packages/spec lane, at src/system/translation.zod.ts (LOCALE_TAG_SHAPE, LocaleSchema, LOCALE_TAG_REFUSAL) and SYS_USER_LOCALE_TAG_PATTERN in @objectstack/platform-objects/identity, which shares the shape.
Dedupe (open AND closed): searched "LocaleSchema language name accepted English Chinese locale tag shape". The results are #25822 (the closed parent) and #26128 (open: a translation item named zh-CN is refused by the item-name grammar, which is a different defect).
reach: public HTTP entry, and the defineTranslation / defineTranslationBundle authoring functions. Measured on a fresh stock showcase boot at 756220c4 as admin, and against the built packages/spec/dist by both the runner and the coordinator.
Checklist item priority: P1
type: Bug
QA-source: #26456 · i18n.strict-translation-key-rejection · observed beside the clauses
Reproduction
As admin, PUT /api/v1/meta/translation/qa_bad_locale with body {"name":"qa_bad_locale","locale":"Chinese","objects":{"showcase_task":{"label":"x"}}} → 200. The read-back serves locale:"Chinese" with _diagnostics.valid:true.
With the built spec, defineTranslation({name:'qa_x', locale:'Chinese', objects:{t:{label:'x'}}}) (from /system) is accepted, and so is defineTranslationBundle({Chinese:{…}}). English and Japanese behave the same.
Controls refused as designed: not a locale!!, zh_CN, 'zh-CN ', ' zh-CN' and Simplified Chinese → 422 invalid_format. zh-CN is accepted.
Expected, per .changeset/25822-locale-tag-shape.md ("a language name is refused"; "replace a language name with its tag") and LOCALE_TAG_REFUSAL ("… a language name …"): 422 invalid_format at locale. Actual: accepted.
Mechanism
LOCALE_TAG_SHAPE = /^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$/ (translation.zod.ts:31). A 2–8-letter primary subtag admits any single English word of that length. The regression test (translation.test.ts, around line 56) pins only the multi-word 'Chinese (Simplified)'.
The source comment records "shape only, by decision", because there is no registry to check membership against. So the fix is a choice for the spec lane:
tighten the primary subtag to the BCP-47 forms that occur in practice (2–3 letters), or
correct the changeset, refusal text and docs so they stop promising that a language name is refused.
Either way, SYS_USER_LOCALE_TAG_PATTERN shares the shape and should move with it.
Premise check and wait · domain:spec seat 3 · os-support-ai · session session_01WGufzgbJhFoXYsLSe5WSPP · 2026-10-27T05:19Z · ⛔ not a claim, no label change
Premise: holds (origin/mainc91544a4). LOCALE_TAG_SHAPE at packages/spec/src/system/translation.zod.ts:31 is still /^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$/, so a 2–8 letter word such as Chinese passes. The docblock above it (lines 16–30) and LOCALE_TAG_REFUSAL both say a language name is refused. SYS_USER_LOCALE_TAG_PATTERN (packages/platform-objects/src/identity/sys-user.object.ts:33) carries the same string. The refusal row in translation.test.ts:56 pins only 'Chinese (Simplified)'. The triage route (refuse it, Clause-②: yes (narrowing), an ADR-0087 entry, Skills-docs: owed) stands.
Waiting on:#25785 (PR #26159, in flight on domain:spec seat 2). Both cards are area:i18n, and their file surfaces intersect on packages/spec/src/system/translation.zod.ts. So the one-in-flight-per-axis rule holds this card until PR #26159 lands, the same as #26128. Spec seat 1 left this card to spec seat 3's i18n lane, and seat 3 dispatches it when #26159 merges, after judging it by file surface against #26128 (whose edits sit in the TranslationItemSchema region of the same file).
Claim: PM loop round 2 · 2026-10-27T13:18Z
Session: session_01WGufzgbJhFoXYsLSe5WSPP
Account: os-support-ai
Branch: claude/project-thread-zxzeeg (card thread; the platform assigns the thread's branch and it can push only that one, written as measured instead of claude/issue-26463-<slug>)
Worktree: objectstack-issue-26463
Domain: domain:spec
Seat: domain:spec#3
File surface: packages/spec/src/system/translation.zod.ts (LOCALE_TAG_SHAPE / LOCALE_TAG_REFUSAL / LocaleSchema region and its docblock only), packages/platform-objects/src/identity/sys-user.object.ts (SYS_USER_LOCALE_TAG_PATTERN line and docblock only), packages/services/service-messaging/src/recipient-locale.ts (its compiled copy of the shape) and recipient-locale-shape-parity.test.ts, packages/plugins/plugin-auth/src/sys-user-locale-write-contract.test.ts (the shape-not-membership pin), translation.test.ts refusal rows, generated output, one changeset with the ADR-0087 entry (stop on breach; explain in the report)
Container & model: S, mode:thread, model: TIER_DEFAULT — --tier: no path-derived mandate (schema face, narrowing; one CONTRACT_REVIEW_TIER review owed before the queue, run by the seat), effort: medium
Clause-②: no (narrowing)
Metadata-protocol: narrowed
Responsibility: n/a — the published refusal text and the #25822 changeset already promise this refusal; the shape fails to deliver it
Thread-read: 6434121727
Premise: holds — re-read on origin/main43731031. Seat reading on the two pins the thread measured as asserting the opposite: sys-user-locale-write-contract.test.ts:205-215 pins Japanese as accepted, and recipient-locale-shape-parity.test.ts:72-82 pins null as shape-legal and asks readers not to tighten the shared regex. The governing text is the maintainer ruling of 2026-09-03 (option B) the plugin-auth file quotes: "a malformed value is refused loudly by the column's BCP-47 shape check". It names a SHAPE check and fixes no subtag length. A 2–3 letter primary language subtag is still shape, not membership (xx stays accepted, unknown tags still fall to the delivery ladder), so the ruling is unchanged and the two pins are a test author's reading of the old 2–8 shape. They move with this card: Japanese and null become shape-refused on both sides, the parity pin keeps the two spellings byte-identical, and NOTHING_LITERALS stays as a read-side floor. Clause-② is no (narrowing) per the seat rule for narrowing a published accept set (the dispatch order's yes (narrowing) is corrected here; precedent #25983).
Serial constraints cleared: #25785 (PR #26159) landed 11:56Z; siblings #26112+#26142, #26170, #26128 edit other regions of translation.zod.ts (region-disjoint; merge main after each lands, generated files via os-regen-merge.sh); no open PR touches sys-user.object.ts, recipient-locale.ts or the plugin-auth pin
{
"issue": 26463,
"status": "done",
"branch": "claude/project-thread-zxzeeg",
"pr": "https://github.com/objectstack-ai/objectstack/pull/26770",
"session": "session_01WGufziYkxRFm6oPR7CmuDg",
"premise_still_valid": true,
"summary": "The locale shape's primary language subtag goes from 2-8 to 2-3 letters, so Chinese, English, Japanese (and null) are refused, while en, zh-CN, zh-Hans-CN, es-419, yue-HK and case folding stay accepted. The one string moves in LocaleSchema (spec translation.zod.ts, docblock and refusal text re-read and made true), SYS_USER_LOCALE_TAG_PATTERN (platform-objects) and service-messaging's read-side LOCALE_TAG_SHAPE, still byte-identical; the two opposite pins (plugin-auth Japanese-accepted, service-messaging null-shape-legal) are rewritten per the seat's ruling, and the 18.x D3 entry translation-locale-non-tag-refused now says 2-3 letters. Not moved, with reasons in the PR body: the plugin-hono-server fixture rule (tests the endpoint evaluates any declared rule; outside the claim surface), ui i18n provider no-Intl fallback (its Intl branch accepts Chinese, so narrowing the fallback alone splits the two branches) and ui doc-draft add-locale hygiene (a doc translations map keyed by any string; outside the claim surface). MEASURE FIRST: zero examples, fixtures, seeds or bundles carry a locale with a 4-8 letter primary subtag (the one hit was the plugin-auth pin itself).",
"tests": "Pre-merge head ee3058c2: pnpm --filter @objectstack/spec test 731 files, 21928 passed, 1 todo; service-messaging test 61 files 732 passed; plugin-auth test 198 files 3780 passed 10 skipped; platform-objects test 96 files 1361 passed; plugin-hono-server current-user-endpoints-localization.test.ts 13 passed. typecheck exit 0 for spec, platform-objects, service-messaging, plugin-auth (plugin-auth after full build). Merged head 84506a0c: translation.test.ts 189 passed, platform-objects 1361 passed, recipient-locale-shape-parity 19 passed, sys-user-locale-write-contract 24 passed; pnpm build 116/116; spec check:generated all 14 up to date. Ablation via scripts/ablation-replace.mjs (spec pattern back to 2-8, anchor 1->0, blob fa4a2333->1108375e): translation.test.ts 6 failed / 183 passed (Chinese, English, Japanese, chinese-CN, null, the door test); restored blob == HEAD, git diff HEAD empty; test imports src, no build leg. test:repo not run locally (no edited test in it, not derived), declared to CI.",
"gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 87 families on 84506a0c, each run and recorded CMD :: exit N, all 87 exit 0; --ran: 87 derived, 87 run, 0 NOT-MEASURED, 0 UNRUN. First pass on ee3058c2/32f73697: check-adr-0087-registration exit 1 (marker spelled bare already-registered; respelled not-required (already-registered translation-locale-non-tag-refused), exit 0) and dual-build-cjs-loads, check:i18n, check:i18n-walk-parity exit 3 before a full build (exit 0 after). CI check-runs on #26770: in_progress at report time.",
"dogfood_battery": "84506a0c · os-verify-lock: VERDICT command-exit 0 · Test Files 302 passed | 3 skipped (305) · Tests 2505 passed | 20 skipped (2525) · no failures (same counts on pre-merge head 32f73697)",
"mcp_calls": "4 — mcp__github__issue_read x4 (get #26463, get_comments #26463 x2, get #26369); no write tool",
"api_writes": "4 — via fleet-write relay: pr_create (POST /repos/objectstack-ai/objectstack/pulls draft + POST /issues/26770/assignees os-support-ai), pr_request_reviewers (POST /pulls/26770/requested_reviewers os-support-ai, the harness's PR-attribution rule), comment (POST /issues/26463/comments, this report); plus git push x3 (not REST)",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed — ui/packages/i18n/src/provider.tsx:224 isWellFormedLanguageTag accepts a language name through Intl.getCanonicalLocales (and its 2-8 fallback); reads a UI language preference, not stored metadata, no measured wrong answer at a public door",
"carrier: none · noted, not filed — ui/packages/app-shell/src/views/metadata-admin/previews/doc-draft.ts:28 LOCALE_TAG keeps the 2-8 shape for the doc add-locale box; the spec keys a doc's translations by any string, so no contract is violated"
],
"deviations": [
"Order said Clause-②: yes (narrowing); re-judged and the seat agreed: no (narrowing), as the claim carries.",
"main gained 6 unrelated commits after the pre-PR merge (none touch the 8 files); not re-merged to keep the dogfood reading on the final head; CI judges the merge ref.",
"Test comments cite #26463 at the seat's request."
]
}
ACCEPT → PR #26770 comment 6448177860 · domain:spec seat 3 · 2026-10-27T16:59Z · contract review PASS 6446067343 (head 84506a0c; current head 25fe0e3f adds a changeset-only correction and a main merge); ready with auto-merge armed.
Activity
objectstack-fleet commentedon Oct 27, 2026
Triage: first grade —
bug·priority:p2·domain:spec·area:i18n·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-27T04:57Z⛔ Not a claim, ⛔ not a dispatch.
LocaleSchemastill accepts a single-word language name (Chinese), which the spec: a translation item accepts anylocalestring ("not a locale!!" saves 200, valid) — LocaleSchema is a bare z.string() #25822 changeset says is refused. Refuse it with the same refusal text, intranslation.zod.tsand in the sharedSYS_USER_LOCALE_TAG_PATTERN.Clause-②: yes (narrowing), with an ADR-0087 entry.Skills-docs:is owed ([Decision] pm-dispatch: which PRs owe aSkills-docs:line (the #26184 audit Part B rule draft) — protocol-kind only, protocol-kind plus narrowing, or not adopted #26369 B). Found by the 18.2.0 wave-2 sweep ([sweep] v18.2.0 release test sweep #25807).Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Premise check and wait ·
domain:specseat 3 ·os-support-ai· sessionsession_01WGufzgbJhFoXYsLSe5WSPP· 2026-10-27T05:19Z · ⛔ not a claim, no label changePremise: holds (
origin/mainc91544a4).LOCALE_TAG_SHAPEatpackages/spec/src/system/translation.zod.ts:31is still/^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$/, so a 2–8 letter word such asChinesepasses. The docblock above it (lines 16–30) andLOCALE_TAG_REFUSALboth say a language name is refused.SYS_USER_LOCALE_TAG_PATTERN(packages/platform-objects/src/identity/sys-user.object.ts:33) carries the same string. The refusal row intranslation.test.ts:56pins only'Chinese (Simplified)'. The triage route (refuse it,Clause-②: yes (narrowing), an ADR-0087 entry,Skills-docs:owed) stands.Waiting on: #25785 (PR #26159, in flight on
domain:specseat 2). Both cards arearea:i18n, and their file surfaces intersect onpackages/spec/src/system/translation.zod.ts. So the one-in-flight-per-axis rule holds this card until PR #26159 lands, the same as #26128. Spec seat 1 left this card to spec seat 3's i18n lane, and seat 3 dispatches it when #26159 merges, after judging it by file surface against #26128 (whose edits sit in theTranslationItemSchemaregion of the same file).Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Claim: PM loop round 2 · 2026-10-27T13:18Z
Session:
session_01WGufzgbJhFoXYsLSe5WSPPAccount:
os-support-aiBranch:
claude/project-thread-zxzeeg(card thread; the platform assigns the thread's branch and it can push only that one, written as measured instead ofclaude/issue-26463-<slug>)Worktree:
objectstack-issue-26463Domain:
domain:specSeat:
domain:spec#3File surface:
packages/spec/src/system/translation.zod.ts (LOCALE_TAG_SHAPE / LOCALE_TAG_REFUSAL / LocaleSchema region and its docblock only), packages/platform-objects/src/identity/sys-user.object.ts (SYS_USER_LOCALE_TAG_PATTERN line and docblock only), packages/services/service-messaging/src/recipient-locale.ts (its compiled copy of the shape) and recipient-locale-shape-parity.test.ts, packages/plugins/plugin-auth/src/sys-user-locale-write-contract.test.ts (the shape-not-membership pin), translation.test.ts refusal rows, generated output, one changeset with the ADR-0087 entry(stop on breach; explain in the report)Container & model:
S,mode:thread,model: TIER_DEFAULT — --tier: no path-derived mandate (schema face, narrowing; one CONTRACT_REVIEW_TIER review owed before the queue, run by the seat),effort: mediumClause-②: no (narrowing)
Metadata-protocol: narrowed
Responsibility: n/a — the published refusal text and the #25822 changeset already promise this refusal; the shape fails to deliver it
Thread-read: 6434121727
Premise: holds — re-read on
origin/main43731031. Seat reading on the two pins the thread measured as asserting the opposite:sys-user-locale-write-contract.test.ts:205-215pinsJapaneseas accepted, andrecipient-locale-shape-parity.test.ts:72-82pinsnullas shape-legal and asks readers not to tighten the shared regex. The governing text is the maintainer ruling of 2026-09-03 (option B) the plugin-auth file quotes: "a malformed value is refused loudly by the column's BCP-47 shape check". It names a SHAPE check and fixes no subtag length. A 2–3 letter primary language subtag is still shape, not membership (xxstays accepted, unknown tags still fall to the delivery ladder), so the ruling is unchanged and the two pins are a test author's reading of the old 2–8 shape. They move with this card:Japaneseandnullbecome shape-refused on both sides, the parity pin keeps the two spellings byte-identical, andNOTHING_LITERALSstays as a read-side floor. Clause-② isno (narrowing)per the seat rule for narrowing a published accept set (the dispatch order'syes (narrowing)is corrected here; precedent #25983).Serial constraints cleared:
#25785 (PR #26159) landed 11:56Z; siblings #26112+#26142, #26170, #26128 edit other regions of translation.zod.ts (region-disjoint; merge main after each lands, generated files via os-regen-merge.sh); no open PR touches sys-user.object.ts, recipient-locale.ts or the plugin-auth pinobjectstack-fleet commentedon Oct 27, 2026
os-dev-report
{ "issue": 26463, "status": "done", "branch": "claude/project-thread-zxzeeg", "pr": "https://github.com/objectstack-ai/objectstack/pull/26770", "session": "session_01WGufziYkxRFm6oPR7CmuDg", "premise_still_valid": true, "summary": "The locale shape's primary language subtag goes from 2-8 to 2-3 letters, so Chinese, English, Japanese (and null) are refused, while en, zh-CN, zh-Hans-CN, es-419, yue-HK and case folding stay accepted. The one string moves in LocaleSchema (spec translation.zod.ts, docblock and refusal text re-read and made true), SYS_USER_LOCALE_TAG_PATTERN (platform-objects) and service-messaging's read-side LOCALE_TAG_SHAPE, still byte-identical; the two opposite pins (plugin-auth Japanese-accepted, service-messaging null-shape-legal) are rewritten per the seat's ruling, and the 18.x D3 entry translation-locale-non-tag-refused now says 2-3 letters. Not moved, with reasons in the PR body: the plugin-hono-server fixture rule (tests the endpoint evaluates any declared rule; outside the claim surface), ui i18n provider no-Intl fallback (its Intl branch accepts Chinese, so narrowing the fallback alone splits the two branches) and ui doc-draft add-locale hygiene (a doc translations map keyed by any string; outside the claim surface). MEASURE FIRST: zero examples, fixtures, seeds or bundles carry a locale with a 4-8 letter primary subtag (the one hit was the plugin-auth pin itself).", "tests": "Pre-merge head ee3058c2: pnpm --filter @objectstack/spec test 731 files, 21928 passed, 1 todo; service-messaging test 61 files 732 passed; plugin-auth test 198 files 3780 passed 10 skipped; platform-objects test 96 files 1361 passed; plugin-hono-server current-user-endpoints-localization.test.ts 13 passed. typecheck exit 0 for spec, platform-objects, service-messaging, plugin-auth (plugin-auth after full build). Merged head 84506a0c: translation.test.ts 189 passed, platform-objects 1361 passed, recipient-locale-shape-parity 19 passed, sys-user-locale-write-contract 24 passed; pnpm build 116/116; spec check:generated all 14 up to date. Ablation via scripts/ablation-replace.mjs (spec pattern back to 2-8, anchor 1->0, blob fa4a2333->1108375e): translation.test.ts 6 failed / 183 passed (Chinese, English, Japanese, chinese-CN, null, the door test); restored blob == HEAD, git diff HEAD empty; test imports src, no build leg. test:repo not run locally (no edited test in it, not derived), declared to CI.", "gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 87 families on 84506a0c, each run and recorded CMD :: exit N, all 87 exit 0; --ran: 87 derived, 87 run, 0 NOT-MEASURED, 0 UNRUN. First pass on ee3058c2/32f73697: check-adr-0087-registration exit 1 (marker spelled bare already-registered; respelled not-required (already-registered translation-locale-non-tag-refused), exit 0) and dual-build-cjs-loads, check:i18n, check:i18n-walk-parity exit 3 before a full build (exit 0 after). CI check-runs on #26770: in_progress at report time.", "dogfood_battery": "84506a0c · os-verify-lock: VERDICT command-exit 0 · Test Files 302 passed | 3 skipped (305) · Tests 2505 passed | 20 skipped (2525) · no failures (same counts on pre-merge head 32f73697)", "mcp_calls": "4 — mcp__github__issue_read x4 (get #26463, get_comments #26463 x2, get #26369); no write tool", "api_writes": "4 — via fleet-write relay: pr_create (POST /repos/objectstack-ai/objectstack/pulls draft + POST /issues/26770/assignees os-support-ai), pr_request_reviewers (POST /pulls/26770/requested_reviewers os-support-ai, the harness's PR-attribution rule), comment (POST /issues/26463/comments, this report); plus git push x3 (not REST)", "open_questions": [], "out_of_scope_findings": [ "carrier: none · noted, not filed — ui/packages/i18n/src/provider.tsx:224 isWellFormedLanguageTag accepts a language name through Intl.getCanonicalLocales (and its 2-8 fallback); reads a UI language preference, not stored metadata, no measured wrong answer at a public door", "carrier: none · noted, not filed — ui/packages/app-shell/src/views/metadata-admin/previews/doc-draft.ts:28 LOCALE_TAG keeps the 2-8 shape for the doc add-locale box; the spec keys a doc's translations by any string, so no contract is violated" ], "deviations": [ "Order said Clause-②: yes (narrowing); re-judged and the seat agreed: no (narrowing), as the claim carries.", "main gained 6 unrelated commits after the pre-PR merge (none touch the 8 files); not re-merged to keep the dogfood reading on the final head; CI judges the merge ref.", "Test comments cite #26463 at the seat's request." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
ACCEPT → PR #26770 comment 6448177860 ·
domain:specseat 3 · 2026-10-27T16:59Z · contract review PASS 6446067343 (head84506a0c; current head25fe0e3fadds a changeset-only correction and a main merge); ready with auto-merge armed.objectstack-fleet commentedon Oct 27, 2026
Landed ·
domain:specseat 3 ·os-support-ai· 2026-10-27T18:29Z63e38732aaorigin/maind8b943b6contains the merge commit (git merge-base --is-ancestor)State:
Fixes #26463closed this card as completed.pm:dispatchedcomes off in this stroke;domain:spec,area:i18nand the grade stay.Generated by Claude Code