Skip to content

[Decision] May a user set their own sys_user.locale? — the ADR-0092 D2 self-service whitelist stays {name, image} after #13881 (column lands readonly, system-context writes only) #14787

Description

@claude

Filed by the domain:spec seat (session_017RbbUMnxkUnWhE4j94v8FE) from the contract review of PR #14775 (#13881, isolated reviewer verdict 5518923117, "Decisions to file for the maintainer" item 1). Decision inbox card — needs-user-decision; no domain:* / type set by this seat (triage's). Depends on PR #14775 landing (the column itself); nothing here blocks that landing.

背景

#13881 的裁决(2026-09-01,A 路线)给 sys_user 加了一等列 locale(PR #14775)。落地形状:该列 readonly,只有系统上下文(system context)能写 —— 不在 ADR-0092 D2 的用户自助编辑白名单 SYS_USER_PROFILE_EDIT_FIELDS = {name, image} 里,也不在 plugin-auth 的 MANAGED_EXTENSION_EDITABLE_FIELDS.sys_user 里;今天没有任何管理员界面写它。首个消费者 hotcrm#1185 可以在系统上下文里盖章,所以 PR 可以不等本决策先落地。剩下的问题是身份表写路径的安全边界,裁决没有覆盖,座位不能自裁。

带 re-check 命令的前提

  • 白名单今天是 {name, image}:git grep -n "SYS_USER_PROFILE_EDIT_FIELDS" origin/main -- packages/plugins/plugin-auth/src/sys-user-writable-fields.ts
  • 可编辑扩展字段表没有 sys_user 条目:git grep -n "MANAGED_EXTENSION_EDITABLE_FIELDS" origin/main -- packages/plugins/plugin-auth/src/managed-extension-fields.ts
  • 列已落地且 readonly(PR feat(spec,platform-objects,service-messaging,plugin-auth): sys_user.locale + per-recipient notification locale (#13881) #14775 合并后成立):git grep -n -A3 "locale: Field.text" origin/main -- packages/platform-objects/src/identity/sys-user.object.ts
  • better-auth 的 /update-user 走不到这列(它不是 better-auth 字段):git grep -n "additionalFields" origin/main -- packages/plugins/plugin-auth/src/auth-schema-config.ts(只有 invitation 模型有)

一句话问题

一个中文用户登录进来,想把自己的通知语言从英文改成中文 —— 今天没有任何地方让他自己改,只能等管理员或系统替他填。

选项 × 真实代价

选项 做什么 真实代价
A 保持现状 readonly,只有系统上下文写(hotcrm 之类的应用在系统上下文里盖章) 用户收到的通知语言由别人决定;平台自己的 Studio/Console 没有入口让用户改语言;每个应用都要自己造一条「系统替用户填语言」的通道
B 放开自助 白名单加 locale({name, image, locale}),MANAGED_EXTENSION_EDITABLE_FIELDS.sys_userlocale,去掉 readonly,身份写守卫的 pin 同步翻转 身份表多开一个用户可写字段(安全边界扩一格);用户填错格式由列的 BCP-47 形状校验响亮拒绝;objectui 侧还要一个「我的语言」表单项才真正可达(另开 ui 卡)

业务上:A = 像早期企业软件,语言是 IT 管理员配置的;B = 像 Salesforce / Slack 的个人设置页里的 Language 下拉,用户自己选。

四轴(从业务立场)

  • 实际业务需求:hotcrm 发布 4 种语言、16 个 notify 节点,zh-CN / ja-JP / es-ES 用户此前全收英文 —— 拉动已实测(hotcrm#1185)。但「谁来填这一列」在应用侧还是空的:A 下每个应用都得自己写一段系统上下文盖章逻辑;B 下用户第一次登录就能自己选。
  • 项目长远合理性(权重 ≥50%):用户自己的语言是主流平台的一等个人属性,长远终态就是用户自助;A 是过渡态,过渡态在创业阶段不留。B 不扩大契约(列已在),只是把已有的白名单机制多列一项,特例没有增生。
  • 防 AI 犯错:B 下 AI 写应用元数据时不需要为「怎么替用户填语言」发明系统上下文写路径(那正是 AI 最容易写错权限的地方);用户填错值由 LOCALE_TAG_SHAPE 校验响亮拒绝、回退到部署默认,不会静默死信。A 下 AI 会在应用侧造绕行。
  • 创业阶段不扩散:B 的改动是三行(两个白名单各一项 + 去 readonly)+ 一个 pin,不新增能力面;A 反而会让每个应用各自扩散一套盖章逻辑。

推荐 + 回退 + 置信缺口

  • 推荐 B(长远终态领起,实测拉动在,防错与不扩散同向)。安全边界扩一格是本卡进决策箱的唯一原因:身份表的用户可写集从 2 个字段变 3 个。
  • 回退 A:列保持只读,由应用在系统上下文里盖章;hotcrm 照常可用。
  • 本分析看不见什么:objectui 是否已有「个人设置」表单可以挂 locale(未测);cloud 仓是否有自己的用户语言设置面(未测,本容器不可达 objectstack-ai/cloud)。

裁后我会怎么执行(你不用管)

Refs

#13881(裁决 5494464459)· PR #14775(复审 5518923117)· hotcrm#1185 · #14641(邀请邮件梯级)· #14762(auth 短信/邮件读列)· ADR-0092 D2 / D4 · ADR-0105 D7

os-decision-facets

  • ① 长远合理性:用户自选语言是终态;B 只在既有白名单机制上加一项,不扩大契约、不增特例 —— 荐 B。
  • ② 实际业务拉动:已实测(hotcrm 4 语言 × 16 节点 × 0 可本地化);A 下每个应用要各造一条系统盖章路径才能填列。
  • ③ 防 AI 犯错:B 让 AI 写应用时不必发明权限绕行;错值由 BCP-47 形状校验响亮拒绝并回退部署默认。
  • ④ 创业阶段不扩散:B = 三行 + 一个 pin,无新能力面;A 会让盖章逻辑在应用间扩散。
  • 推荐:B(回退 A)。
  • 置信缺口:objectui 个人设置表单是否存在、cloud 仓是否另有用户语言面 —— 均未测。

Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions