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
i18n: sys_user.locale stores a mis-cased tag (zh-cn, ZH-CN) with 200, and the console then renders that user's session in English — #29833 corrected case for translation locales only #29895
Filing gate: ① a product defect, observed beside the clauses (i18n.notification-localized-and-clears), reach: the public data door PATCH /api/v1/data/sys_user/:id, measured live.
Business reason: a user's language preference written by an import, an identity-provider sync or an admin script as zh-cn is accepted and stored, and that user then gets an English console with no error anywhere — the same silent failure #28993 described for translation files, on the user-preference door. #29833 folded translation locales to BCP-47 case; the user record still stores what it is given, while the server's i18n routes fold case and the console matches exactly, so two surfaces disagree about one user's language.
Reader who acts: the triage seat, then a domain:server implementer on the sys_user.locale write (fold with the same canonical-case helper #29833 added, or refuse an unsupported tag), with the console's exact-match pick as the alternative landing site. QA suggestion only: p2.
Duplicate search (incl. closed): sys_user locale case zh-cn → 66 hits, locale zh-cn console English user → 143 hits. Nearest: #28993 (closed by #29833, translation locales only), #28107 (open, French not a platform locale — a different gap), #29428 (open, platform locale table). None covers case on the user record.
As a member, PATCH /api/v1/data/sys_user/OWN_ID{"locale":"zh-cn"} → 200; the row reads locale: "zh-cn" (ZH-CN likewise).
Reload /_console/apps/com.example.showcase/showcase_task: html lang is en-US, headers read Title / Project / Assignee, "Open inbox".
Control: {"locale":"zh-CN"} → the same page renders lang zh-CN, 标题 / 项目 / 负责人.
Expected: the door stores zh-CN (as #29833 does for translations) or the console resolves case-insensitively, as the i18n routes and Accept-Language already do. Actual: stored verbatim, console English. The same door also stores xx-YY (no such platform locale) and '' with 200, and refuses zh_CN with 400 "Locale must be a BCP-47 language tag". The console's own language switcher writes zh-CN, so only API and import writers hit it.
Mechanism (suspected)
The sys_user.locale write checks BCP-47 shape only, with no case fold; the console's locale pick is exact-match.
QA-source: #29890 · i18n.notification-localized-and-clears · observed beside the clauses
Claim: PM loop round 11
Session: session_01WGufzfsqbTMmPhapjVEeoo (dev: os-dev card thread; seat session_01WGufzibcN9oHa8kDbTTnL5)
Account: os-support-ai
Branch: claude/project-thread-twt5i2 (harness-pinned thread branch; platform exception to claude/issue-<n>-<slug>)
Worktree: the dev thread's own container checkout
Domain: domain:server
Seat: domain:server#6
File surface: the sys_user.locale write validation (canonical-case fold with the #29833 helper) on every write path that reaches the column, real-route pins, one changeset. ⛔ protocol.ts and engine.ts are frozen for today's codemod window. On a breach, stop and explain it in the report.
Container & model: M, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate), effort: high
Clause-②: no
Metadata-protocol: none
Responsibility: the platform's own user-preference door (a mis-cased locale is stored verbatim and the console renders English) | none: #29833 folded translation locales only | every user whose locale is written by an import, an IdP sync or an admin script
Thread-read: 6601888930
Premise: holds (measured live at 589049b0 by the filer; triage 6601888930; the dev re-measures on main)
Serial constraints cleared: none (no in-flight claim of this seat on the file surface; the dev re-checks open PRs at PR time)
Stamp: 2026-11-04T09:39Z
Order: /mnt/project-files/server-orders/29895.md (project files). The declaration is provisional, so the dev measures and declares what the diff actually changes.
{
"issue": 29895,
"status": "done",
"branch": "claude/project-thread-twt5i2",
"pr": "https://github.com/objectstack-ai/objectstack/pull/30049",
"head": "1c72a05faee0465e2b869b65858cbfcd2c4410b2",
"session": "session_01WGufzfsqbTMmPhapjVEeoo",
"premise_still_valid": true,
"summary": "Premise holds on origin/main c618e890: the locale_bcp47_shape format rule checks shape only and nothing folds case. New plugin-auth module sys-user-locale-case.ts: registerUserLocaleCaseFold puts beforeInsert/beforeUpdate hooks on sys_user at priority 5 (every context, ahead of the identity write guard at 10), folding data.locale through LocaleSchema.safeParse from @objectstack/spec/system (the #29833 fold; canonicalLocaleCase is spec-internal by design, so the parse is the reuse and no second fold exists). A value the schema refuses is left as sent, so zh_CN still answers 400 and an empty value still clears: fold only, no new refusal. Write paths measured: data door by-id and bulk (user context), admin-import-users.ts:750 (system, SYS_USER_IMPORT_UPDATE_FIELDS includes locale), idp-locale-claim.ts apply (system); better-auth's adapter never writes locale. resolveIdpLocale now answers the canonical case too, so the idp-locale record and the folded column cannot hold two spellings. Frozen files protocol.ts and engine.ts untouched. Rows already stored mis-cased are not migrated: /auth/me/localization (storedSignedInUserLocale) still serves them verbatim and the console renders English for that user until the column is next written, which folds it.",
"tests": "head 1c72a05f, all under os-verify-lock: plugin-auth closure build VERDICT command-exit 0; plugin-auth typecheck VERDICT command-exit 0 (check:test-typecheck OK); plugin-auth test: Test Files 220 passed (220), Tests 3815 passed | 10 skipped, VERDICT command-exit 0; dogfood^... build VERDICT command-exit 0; dogfood test/user-locale-case-fold.dogfood.test.ts (real PATCH /api/v1/data/sys_user/:id): Tests 6 passed (6) — zh-cn and ZH-CN stored zh-CN, /auth/me/localization serves zh-CN, zh-CN unchanged, zh_CN 400 VALIDATION_FAILED and stored value unchanged, system-context write folded. pnpm lint exit 0. Ablation via ablation-replace on sys-user-locale-case.ts (fold off: const after = before behind marker ABLATION_29895_NO_FOLD), plugin-auth rebuilt, ablation-dist-preflight exit 0: dogfood Tests 4 failed | 2 passed (expected 'zh-cn' to be 'zh-CN', expected 'ZH-CN' to be 'zh-CN', localization door, system write; control and zh_CN case green as expected), unit 4 failed | 59 passed. Restore: blob == HEAD e3fd65676c23, git diff HEAD empty, rebuilt, preflight --absent exit 0, dogfood 6/6 again.",
"gates": "head 1c72a05f: dispatch-gates --commands derived 68, run serially unlocked; 66 exit 0 first sweep, 2 exit 3 on prerequisites (check-plugin-teardown-shape --self-test: shallow clone, fetched fixture 621a4876 then exit 0; check:dual-build-cjs-loads: seven packages without dist, built under the lock then exit 0). dispatch-gates --ran: 68 run, 0 NOT-MEASURED, exit 0 (record /mnt/project-files/issue-29895/gates-ran.list). CI check-runs on PR #30049: in_progress at report time.",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "0 — no MCP GitHub calls (card and comments read through gh api REST GETs)",
"api_writes": "2 — fleet-write relay pr_create (POST /repos/objectstack-ai/objectstack/pulls, PR #30049, read back 9291/9291 bytes identical, no assignees, no labels); post-stamped os-dev-report comment on #29895 (POST /repos/objectstack-ai/objectstack/issues/29895/comments). Plus git push x1 (not REST).",
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": [
"class: a · reach: PATCH /api/v1/data/sys_user/OWN_ID with locale xx-YY or empty string answers 200 and stores it (the card's own reading; still true, xx-yy is now stored xx-YY) · not refused here because refusing narrows the door, per the order · dedupe words: sys_user locale unsupported tag refuse, xx-YY locale stored, empty locale 200",
"carrier: domain:ui · the console's exact-match locale pick still matters for rows stored mis-cased before this release and for canonical tags not spelled exactly like a console locale (zh, zh-Hans-CN, en-GB), which the server's resolveLocale answers and the console does not · noted, not filed"
],
"deviations": [
"pnpm lint was run as the order asked, although os-dev.md assigns the repo-wide scan to CI; exit 0.",
"The session prompt's assignee and reviewer step after pr_create was skipped per os-dev.md rule 2."
]
}
Landed · 2026-11-04T11:36Z · PR #30049 merged through the queue as c23501b2. ACCEPT 6628747931. pm:dispatched is removed with the close. Every write of sys_user.locale (data door, admin import, IdP sync) is now stored in BCP-47 case through LocaleSchema's fold, so zh-cn becomes zh-CN. Nothing new is refused, and legacy rows are not rewritten. @objectstack/plugin-auth patch. Carried: #30050 (xx-YY is stored, and the console's exact-match pick).
Activity
objectstack-fleet commentedon Nov 3, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: first-touch audit · p2 ·
domain:server·pm:queue· 2026-11-03T10:56Z⛔ Not a claim.
sys_user.localestores a mis-cased tag (zh-cn) with 200, and that user's console then renders in English with no error. feat(spec)!: a locale tag written in another case is corrected to its BCP-47 case #29833 corrected case for translation locales only. The sibling of 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 (p2), so p2.Generated by Claude Code
objectstack-fleet commentedon Nov 4, 2026
domain:server#6 · os-support-ai · session_01WGufzibcN9oHa8kDbTTnL5
Claim: PM loop round 11
Session:
session_01WGufzfsqbTMmPhapjVEeoo(dev: os-dev card thread; seatsession_01WGufzibcN9oHa8kDbTTnL5)Account:
os-support-aiBranch:
claude/project-thread-twt5i2(harness-pinned thread branch; platform exception toclaude/issue-<n>-<slug>)Worktree: the dev thread's own container checkout
Domain:
domain:serverSeat:
domain:server#6File surface: the
sys_user.localewrite validation (canonical-case fold with the #29833 helper) on every write path that reaches the column, real-route pins, one changeset. ⛔protocol.tsandengine.tsare frozen for today's codemod window. On a breach, stop and explain it in the report.Container & model:
M,mode:thread,model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate),effort: highClause-②: no
Metadata-protocol: none
Responsibility:
the platform's own user-preference door (a mis-cased locale is stored verbatim and the console renders English) | none: #29833 folded translation locales only | every user whose locale is written by an import, an IdP sync or an admin scriptThread-read: 6601888930
Premise: holds (measured live at
589049b0by the filer; triage 6601888930; the dev re-measures on main)Serial constraints cleared: none (no in-flight claim of this seat on the file surface; the dev re-checks open PRs at PR time)
Stamp: 2026-11-04T09:39Z
/mnt/project-files/server-orders/29895.md(project files). The declaration is provisional, so the dev measures and declares what the diff actually changes.objectstack-fleet commentedon Nov 4, 2026
domain:server#6 · os-support-ai · session_01WGufzfsqbTMmPhapjVEeoo
os-dev-report
{ "issue": 29895, "status": "done", "branch": "claude/project-thread-twt5i2", "pr": "https://github.com/objectstack-ai/objectstack/pull/30049", "head": "1c72a05faee0465e2b869b65858cbfcd2c4410b2", "session": "session_01WGufzfsqbTMmPhapjVEeoo", "premise_still_valid": true, "summary": "Premise holds on origin/main c618e890: the locale_bcp47_shape format rule checks shape only and nothing folds case. New plugin-auth module sys-user-locale-case.ts: registerUserLocaleCaseFold puts beforeInsert/beforeUpdate hooks on sys_user at priority 5 (every context, ahead of the identity write guard at 10), folding data.locale through LocaleSchema.safeParse from @objectstack/spec/system (the #29833 fold; canonicalLocaleCase is spec-internal by design, so the parse is the reuse and no second fold exists). A value the schema refuses is left as sent, so zh_CN still answers 400 and an empty value still clears: fold only, no new refusal. Write paths measured: data door by-id and bulk (user context), admin-import-users.ts:750 (system, SYS_USER_IMPORT_UPDATE_FIELDS includes locale), idp-locale-claim.ts apply (system); better-auth's adapter never writes locale. resolveIdpLocale now answers the canonical case too, so the idp-locale record and the folded column cannot hold two spellings. Frozen files protocol.ts and engine.ts untouched. Rows already stored mis-cased are not migrated: /auth/me/localization (storedSignedInUserLocale) still serves them verbatim and the console renders English for that user until the column is next written, which folds it.", "tests": "head 1c72a05f, all under os-verify-lock: plugin-auth closure build VERDICT command-exit 0; plugin-auth typecheck VERDICT command-exit 0 (check:test-typecheck OK); plugin-auth test: Test Files 220 passed (220), Tests 3815 passed | 10 skipped, VERDICT command-exit 0; dogfood^... build VERDICT command-exit 0; dogfood test/user-locale-case-fold.dogfood.test.ts (real PATCH /api/v1/data/sys_user/:id): Tests 6 passed (6) — zh-cn and ZH-CN stored zh-CN, /auth/me/localization serves zh-CN, zh-CN unchanged, zh_CN 400 VALIDATION_FAILED and stored value unchanged, system-context write folded. pnpm lint exit 0. Ablation via ablation-replace on sys-user-locale-case.ts (fold off: const after = before behind marker ABLATION_29895_NO_FOLD), plugin-auth rebuilt, ablation-dist-preflight exit 0: dogfood Tests 4 failed | 2 passed (expected 'zh-cn' to be 'zh-CN', expected 'ZH-CN' to be 'zh-CN', localization door, system write; control and zh_CN case green as expected), unit 4 failed | 59 passed. Restore: blob == HEAD e3fd65676c23, git diff HEAD empty, rebuilt, preflight --absent exit 0, dogfood 6/6 again.", "gates": "head 1c72a05f: dispatch-gates --commands derived 68, run serially unlocked; 66 exit 0 first sweep, 2 exit 3 on prerequisites (check-plugin-teardown-shape --self-test: shallow clone, fetched fixture 621a4876 then exit 0; check:dual-build-cjs-loads: seven packages without dist, built under the lock then exit 0). dispatch-gates --ran: 68 run, 0 NOT-MEASURED, exit 0 (record /mnt/project-files/issue-29895/gates-ran.list). CI check-runs on PR #30049: in_progress at report time.", "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "0 — no MCP GitHub calls (card and comments read through gh api REST GETs)", "api_writes": "2 — fleet-write relay pr_create (POST /repos/objectstack-ai/objectstack/pulls, PR #30049, read back 9291/9291 bytes identical, no assignees, no labels); post-stamped os-dev-report comment on #29895 (POST /repos/objectstack-ai/objectstack/issues/29895/comments). Plus git push x1 (not REST).", "open_questions": [], "security_findings": "none", "out_of_scope_findings": [ "class: a · reach: PATCH /api/v1/data/sys_user/OWN_ID with locale xx-YY or empty string answers 200 and stores it (the card's own reading; still true, xx-yy is now stored xx-YY) · not refused here because refusing narrows the door, per the order · dedupe words: sys_user locale unsupported tag refuse, xx-YY locale stored, empty locale 200", "carrier: domain:ui · the console's exact-match locale pick still matters for rows stored mis-cased before this release and for canonical tags not spelled exactly like a console locale (zh, zh-Hans-CN, en-GB), which the server's resolveLocale answers and the console does not · noted, not filed" ], "deviations": [ "pnpm lint was run as the order asked, although os-dev.md assigns the repo-wide scan to CI; exit 0.", "The session prompt's assignee and reviewer step after pr_create was skipped per os-dev.md rule 2." ] }Stamp: 2026-11-04T10:45Z
Generated by Claude Code
objectstack-fleet commentedon Nov 4, 2026
domain:server#6 · os-support-ai · session_01WGufzibcN9oHa8kDbTTnL5
Landed · 2026-11-04T11:36Z · PR #30049 merged through the queue as
c23501b2. ACCEPT 6628747931.pm:dispatchedis removed with the close. Every write ofsys_user.locale(data door, admin import, IdP sync) is now stored in BCP-47 case throughLocaleSchema's fold, sozh-cnbecomeszh-CN. Nothing new is refused, and legacy rows are not rewritten.@objectstack/plugin-authpatch. Carried: #30050 (xx-YYis stored, and the console's exact-match pick).