Blocked-by: #13881
Observation (found while implementing #13881; not fixed there — scope fence)
#13881 lands sys_user.locale as a first-class column and moves the notification locale (service-messaging: email / inbox / SMS channels) to a per-recipient resolution: sys_user.locale → deployment default. It deliberately does NOT touch plugin-auth's own sends, which keep their existing ladders:
Why it is its own card
The 2026-09-01 ruling on #13881 names the messaging seam (email-channel.ts) as the insertion point and the triage fence says the messaging path ends with exactly one per-recipient read point. Adding rungs to plugin-auth's ladders is a second surface with its own precedence question (does the user's stored locale outrank the request's Accept-Language for a signed-in user's own sends?) that the ruling did not decide.
Suggested shape (for triage, not a ruling)
- OTP SMS: the recipient IS the user; read
sys_user.locale off the row sendPhoneOtp already has, fall to the deployment default (same normalizer as @objectstack/service-messaging's normalizeRecipientLocale — refuse the literal undefined shape). - Request-less mail to an existing user: same rung, between request and deployment.
- Both keep the documented
en-US floor; nothing dead-letters.
Refs: #13881 (column + messaging chain) · #14641 (invitation rung) · #14319 (request rung) · #2815 (closed; original SMS locale card).
Blocked-by: #13881
Observation (found while implementing #13881; not fixed there — scope fence)
#13881 lands
sys_user.localeas a first-class column and moves the notification locale (service-messaging: email / inbox / SMS channels) to a per-recipient resolution:sys_user.locale→ deployment default. It deliberately does NOT touch plugin-auth's own sends, which keep their existing ladders:packages/plugins/plugin-auth/src/phone-sms-texts.ts,auth-manager.tsloadPhoneSmsTemplateBody/builtinPhoneSmsBody): locale = the deployment default (localization.locale, bound onkernel:readyviasetDefaultSmsLocale). The file's own docblock said "when it grows one, resolution should prefer it (tracked in feat(sms/i18n): 邀请/OTP 短信文案国际化(按用户 locale 渲染) #2815)" — the column has grown; feat(sms/i18n): 邀请/OTP 短信文案国际化(按用户 locale 渲染) #2815 is closed; this card is the tracker now.AuthManager.emailLocaleArgis request rung (Accept-Language, auth 邮件(验证/重置)不按用户语言选模板:中文界面注册收到英文主题与正文 #14319) → deployment rung (setDefaultEmailLocale). A stored per-user column would be the natural rung between them. Invitations are finding(plugin-auth): 邀请邮件的语言仍取部署默认——等用户级语言列落地后给邀请单独一梯级(#14319 裁 A-now/C-later 的追踪) #14641's (the invitee has nosys_userrow until acceptance) and are not this card.Why it is its own card
The 2026-09-01 ruling on #13881 names the messaging seam (
email-channel.ts) as the insertion point and the triage fence says the messaging path ends with exactly one per-recipient read point. Adding rungs to plugin-auth's ladders is a second surface with its own precedence question (does the user's stored locale outrank the request'sAccept-Languagefor a signed-in user's own sends?) that the ruling did not decide.Suggested shape (for triage, not a ruling)
sys_user.localeoff the rowsendPhoneOtpalready has, fall to the deployment default (same normalizer as@objectstack/service-messaging'snormalizeRecipientLocale— refuse the literalundefinedshape).en-USfloor; nothing dead-letters.Refs: #13881 (column + messaging chain) · #14641 (invitation rung) · #14319 (request rung) · #2815 (closed; original SMS locale card).