Repository navigation
Description
Filing gate ②: the framework half of the ruling the maintainer gave on objectstack-ai/cloud#3125 (batch #387 item 1, column 现在立 card 5: 「用户语言偏好随云账号进入每个环境」, platform first). This is the producer half that #25261 (the reader, landed as 829cb779) assumed exists. Filed by repo:cloud#1 (seat post #6026, session session_01FSg7JRUiTPo8GrgT38tAWP). ⛔ Not a claim.
Reader who acts: the objectstack lane. objectstack-ai/cloud#3159, the cloud half, is Blocked-by: this card. It resumes as a pin bump plus a local-stack reading.
Measured (pinned tree f0fdd45bbc83, better-auth 1.7.12, @better-auth/oauth-provider 1.7.12)
The source is the cloud dev report on objectstack-ai/cloud#3159 (comment 6373436798).
- The OP is composed only inside plugin-auth:
packages/plugins/plugin-auth/src/auth-manager.ts:4252(return [jwtPlugin, oauthProvider({; line 4241 onmain1ff43b67). It passescustomAccessTokenClaims, but nocustomUserInfoClaimsand nocustomIdTokenClaims. A host only switches the OP on (plugins.oidcProvider: true); it gets no claim seam and no host-plugin seam. - The platform's own RP reads the claim from userinfo. That RP is plugin-auth
genericOAuthwith no customgetUserInfo. better-auth uses the decoded ID token only when it carries bothsubandemail. oauth-provider 1.7.12 keeps the standard profile claims out of the ID token (ID_TOKEN_SCOPE_CLAIM_GUARDS), so the RP GETs/oauth2/userinfo. That body becomessource.oauth.profile, whichidp-locale-claim.tsreads. - So today a hosted environment never receives
locale, and Identity: the JIT user path reads the IdP locale claim into the user language preference (hosted environments follow the cloud account language) #25261's reader never fires on the cloud path. localeis not a better-authadditionalField(sys-user.object.ts, the [Decision] Per-user notification locale —— 2026-08-13 裁决所等的「实测拉力」已到(hotcrm:4 个已发布语言 × 16 个 notify 节点 × 0 可本地化) #13881 block), so the user object the provider hands a claims hook carries nolocale. The value has to be read through the data engine.
Wanted
- In plugin-auth's
oauthProvider({...}), addcustomUserInfoClaims. It returns{ locale }when the granted scopes includeprofileand the user'ssys_user.locale(read through the data engine, in system context) is a non-empty BCP-47 tag. Otherwise it returns nothing. - ⛔ No new host configuration key and no new column. Every ObjectStack OP emits the standard claim (OIDC Core 5.1), not only the cloud's.
Acceptance
- A test on the OP: userinfo carries
locale: "zh-CN"for a user withzh-CNunder theprofilescope. It omits the claim for a user with no locale, and whenprofilewas not granted. - Ablation: drop the hook and the test goes red.
- If the suite can drive one round trip, the platform RP's JIT path stores the claim (Identity: the JIT user path reads the IdP locale claim into the user language preference (hosted environments follow the cloud account language) #25261's reader) end to end.
Dedupe
repo:objectstack-ai/objectstack userinfo locale claim returns 1 hit: #25261 (closed, the RP reader; it does not cover the OP). No other card was found.
Activity
objectstack-fleet commentedon Oct 26, 2026
Claim: PM loop round 6 · card thread
Session:
session_01R1q2xdtYgU6FKrZ7LgjPCC(the seat); the card thread issession_01XCJ2r3w7jCsheYguravx4HAccount:
os-project-manager(the seat's linked user; the card's assignee)Branch:
claude/project-thread-v17ls7(the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#1Ruling-ref: the card's Wanted: plugin-auth's
oauthProvidergainscustomUserInfoClaimsreturning{ locale }only under theprofilescope and a non-empty BCP-47sys_user.localeread through the data engine in system context; userinfo only, ⛔ not the ID token; ⛔ no new host key, column oradditionalFieldFile surface:
packages/plugins/plugin-auth/**(auth-manager.ts's OP composition and a claims helper), its tests, one changeset. ⛔ Nopackages/spec/**(stop on breach; explain in the report)Container & model:
M,mode:thread, default model and effort (set at thread open)Clause-②: yes (expected: an added standard claim; the dev measures)
Metadata-protocol: none
Responsibility: a hosted environment's JIT sign-in never receives
locale, because the platform OP passes nocustomUserInfoClaims, so #25261's RP reader never fires on the cloud path (card #25529, pinned treef0fdd45bbc83)Thread-read: none
Premise: holds (read 2026-10-26T08:15Z at
origin/main05f7758f05: card open, unassigned,pm:queue, no other seat's claim; the dev reproduces red first)Serial constraints cleared: no open PR names #25529; the dev re-measures open PRs on its file surface before
pr_createobjectstack-fleet commentedon Oct 26, 2026
os-dev-report
{ "issue": 25529, "status": "done", "branch": "claude/project-thread-v17ls7", "pr": "https://github.com/objectstack-ai/objectstack/pull/26012", "session": "session_01XCJ2r3w7jCsheYguravx4H", "premise_still_valid": true, "summary": "plugin-auth's oauthProvider composition (now auth-manager-plugin-list.ts:500; auth-manager.ts:4252 on the filing tree moved there in the split) gains customUserInfoClaims, which returns { locale } only when the access token's scopes include profile and sys_user.locale, read through the data engine in system context, passes normalizeRecipientLocale; otherwise {}. The rule lives in the new oidc-userinfo-locale-claim.ts. Userinfo only (the ID token is asserted locale-free); no host key, column or additionalField. A failed read omits the claim and warns once via the host logger with the user id. Measured: better-auth and @better-auth/oauth-provider are 1.7.12 on main; customUserInfoClaims(info: { user, scopes, jwt, requestedClaims }) exists there and its result is merged after the provider's claims with sub re-pinned.", "tests": "New oidc-userinfo-locale-claim.test.ts drives a REAL AuthManager OP (oidcProvider on, shared in-memory engine) over HTTP: sign-up, create-client, authorize+PKCE, consent, token, userinfo; plus one RP round trip end to end (second AuthManager as the platform RP, genericOAuth objectstack-cloud via OIDC discovery, fetch routed to the OP handler) where the RP JIT path stores zh-CN. Red on main+test (b6c90526): 3 failed / 13 passed (zh-CN leg: expected undefined to be 'zh-CN'; read-failure leg: warn called 0 times; round trip: RP user locale undefined). Green at 630e39c2: 16/16. Ablation via scripts/ablation-replace.mjs (anchor 'customUserInfoClaims: ({ user, scopes }) =>' renamed; test resolves src, no dist): anchor 1->0, blob dade996dd516->2de5c1541934, same 3 legs red, restored to HEAD blob dade996dd516, git diff HEAD empty. pnpm --filter @objectstack/plugin-auth typecheck exit 0 (check:test-typecheck OK, no new test-layer errors); full plugin-auth vitest 196 files / 3737 passed / 10 skipped exit 0; eslint --no-inline-config packages/plugins/plugin-auth exit 0.", "gates": "Local, head 630e39c2: dispatch-gates --commands = 68 commands, each exit captured before any pipe; --ran reconciliation 68 derived / 68 run / 0 NOT-MEASURED, exit 0. First-run exit 3 (prerequisite, nothing measured) re-run green: check-plugin-teardown-shape --self-test (shallow clone; control commit fetched), check:dual-build-cjs-loads (after turbo build 120/120), check:type-check-debt (lock budget spent behind my build). Also check:dts-closure re-run green post-build. CI check-runs on #26012: in_progress at report time (not re-run locally).", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "mcp_calls": "0 — no GitHub MCP calls; reads went through REST", "api_writes": "2 relay actions — pr_create (POST /repos/objectstack-ai/objectstack/pulls draft + POST .../issues/26012/assignees os-project-manager) via fleet-write run 40296768726; this os-dev-report comment (POST .../issues/25529/comments via post-stamped). Plus git pushes (not REST).", "open_questions": [], "out_of_scope_findings": [ "carrier: none — noted in PR Acceptance notes only: discovery claims_supported does not list locale (the provider overwrites its claims list from its own standard-claim table; only advertisedMetadata.claims_supported would replace it wholesale). OIDC Discovery makes the list RECOMMENDED and non-exhaustive, and the platform RP never reads it; zero-pull, not filed.", "carrier: none — open PR #25983 (spec: a locale must be a BCP-47 tag) touches no file on this surface; re-measured all 20 open PRs, none touches packages/plugins/plugin-auth/src." ], "clause2": "Clause-②: yes / Metadata-protocol: none / changeset patch on @objectstack/plugin-auth — as the claim; measured: an added standard claim widens the userinfo response, no author-facing metadata key." }Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Landed
PR #26012 merged at 2026-10-26T10:00Z by the merge queue as
431f065177onmain(Fixes #25529; ACCEPT6412370789).git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-26012-*'origin/mainby contentgit cat-file -e origin/main:packages/plugins/plugin-auth/src/oidc-userinfo-locale-claim.tsgit merge-base --is-ancestor 431f065177 origin/mainState: this card closed by
Fixes(completed);pm:dispatchedremoved.Generated by Claude Code