Found while implementing #14762 (auth OTP SMS + auth mail read sys_user.locale); left out of that PR by the scope fence rather than fixed there.
Observation
#14788 was ruled option D on 2026-09-03: sys_user.locale when set -> the request's Accept-Language -> the deployment default. #14762 layered that stored rung onto AuthManager.emailLocaleArg and threaded it at the three sends that hold a recipient row:
sendResetPassword (user.id)
sendVerificationEmail (user.id)
sendChangeEmailNotice (from.id)
sendMagicLink was left on the two-rung #14319 ladder (request -> deployment). It is the fifth auth send and the only one to an existing account that does not read the column. Measured at origin/main after #14762's branch: auth-manager.ts calls this.emailLocaleArg(ctx) at the magic-link site with no stored argument.
Why it was fenced out rather than fixed
Every other send is handed a user object carrying an id; sendMagicLink is handed only { email, url, token }. Reading the column there needs a lookup keyed by email rather than by id — a query shape the card did not name, on a path the card did not name. Adding it silently would have been scope creep on a p3.
What it costs today
A user who set their language to zh-CN gets Chinese password-reset mail and English magic-link mail from the same deployment, decided by whichever browser happened to send the request. That is the exact inconsistency ruling D exists to remove — the ladder is meant to be one answer per recipient, not one per callback.
Suggested shape (for triage, not a ruling)
One findOne('sys_user', { where: { email }, fields: ['locale'] }) behind the existing placeholder-address refusal, threaded into emailLocaleArg's stored parameter exactly as the other three sends do. The helper (AuthManager.storedRecipientLocale) already exists and takes an arbitrary where, so this is a call site, not a mechanism.
Open question worth deciding rather than assuming: a magic link is requested BY the recipient, so the request rung is legitimate here in a way it is not for an admin-initiated reset. Ruling D still puts the stored column on top, but this send is the weakest case for it and the cheapest to leave alone.
Refs: #14762 (the rungs that did land) - #14788 (the ruled ladder) - #14319 (the request rung) - #14641 (invitations, a separate rung with a separate reason)
Generated by Claude Code
Found while implementing #14762 (auth OTP SMS + auth mail read
sys_user.locale); left out of that PR by the scope fence rather than fixed there.Observation
#14788 was ruled option D on 2026-09-03:
sys_user.localewhen set -> the request'sAccept-Language-> the deployment default. #14762 layered that stored rung ontoAuthManager.emailLocaleArgand threaded it at the three sends that hold a recipient row:sendResetPassword(user.id)sendVerificationEmail(user.id)sendChangeEmailNotice(from.id)sendMagicLinkwas left on the two-rung #14319 ladder (request -> deployment). It is the fifth auth send and the only one to an existing account that does not read the column. Measured atorigin/mainafter #14762's branch:auth-manager.tscallsthis.emailLocaleArg(ctx)at the magic-link site with no stored argument.Why it was fenced out rather than fixed
Every other send is handed a
userobject carrying anid;sendMagicLinkis handed only{ email, url, token }. Reading the column there needs a lookup keyed by email rather than by id — a query shape the card did not name, on a path the card did not name. Adding it silently would have been scope creep on ap3.What it costs today
A user who set their language to
zh-CNgets Chinese password-reset mail and English magic-link mail from the same deployment, decided by whichever browser happened to send the request. That is the exact inconsistency ruling D exists to remove — the ladder is meant to be one answer per recipient, not one per callback.Suggested shape (for triage, not a ruling)
One
findOne('sys_user', { where: { email }, fields: ['locale'] })behind the existing placeholder-address refusal, threaded intoemailLocaleArg's stored parameter exactly as the other three sends do. The helper (AuthManager.storedRecipientLocale) already exists and takes an arbitrarywhere, so this is a call site, not a mechanism.Open question worth deciding rather than assuming: a magic link is requested BY the recipient, so the request rung is legitimate here in a way it is not for an admin-initiated reset. Ruling D still puts the stored column on top, but this send is the weakest case for it and the cheapest to leave alone.
Refs: #14762 (the rungs that did land) - #14788 (the ruled ladder) - #14319 (the request rung) - #14641 (invitations, a separate rung with a separate reason)
Generated by Claude Code