fix(runtime): ctx.user.name 交付真实 display name —— 三条 dispatch 路径统一 user 形状 (#5372) - #5518
Merged
Merged
Conversation
…patch paths (#5372) The REST /actions dispatcher hardcoded `name: ec.userId` — a declared key delivering a plausible WRONG value, undetectable by any consumer-side fallback. The MCP and AI-route dispatchers read `ec.userName` / `ec.userDisplayName`, neither declared on ExecutionContextSchema nor ever assigned, so their `??` chains also landed on the id; the AI route additionally read `ec.userEmail` (declared field: `ec.email`), leaving `user.email` permanently undefined. One shared producer (security/actor-user.ts) now builds the envelope for all three paths plus the AI routes' second producer. `name` comes from `sys_user.name`, resolved once per request (memo keyed on the ExecutionContext; ~0.22ms per cold read against real SQLite) and falling back to the id quietly — so `name === id` means exactly "no display name", which is what lets an app-side workaround self-retire. [ADR-0068 D1] The identity core is built through the spec's own `createEvalUser`, the same factory the predicate surface mounts under `ctx.user`, so a body and the predicate beside it see one shape. The transport keys (`userId`, `displayName`, `roles`, `permissions`, `systemPermissions`) sit on top; nothing was removed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016FNvXhtSdnEGEfLEsMmvxh
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 21 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…ion-ctx-user-name
baozhoutao
marked this pull request as ready for review
August 5, 2026 15:08
baozhoutao
enabled auto-merge
August 5, 2026 15:08
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5372
前提核验(issue 正文基于 17.0.0-rc.2 dist,行号对 src 无效)
三条路径已在
origin/main的 src 上逐条定位,premise 成立且未漂移:/actionspackages/runtime/src/domains/actions.ts:279—name: ec.userIdrun_actionpackages/runtime/src/action-execution.ts:878—ec.userName ?? ec.userDisplayName ?? ec.userIdpackages/runtime/src/domains/ai.ts:178—displayName: ec.userDisplayName ?? ec.userName ?? ec.userId全仓 grep 确认:
ec.userName/ec.userDisplayName只有读点没有赋值点,ExecutionContextSchema也没有声明这两个键 —— 即 #4984 的"死枝??链"家族,唯一可达分支就是最后一个 id。buildActionSandboxContext(sandbox/body-runner.ts:326)确为 pass-through,名字不是在 sandbox 丢的。顺带核出同类的第二个静默错值:AI route 的
email: ec.userEmail—— 声明的字段是ec.email,所以该路径的user.email一直是undefined。它与本单要统一的是同一个对象、同一处构造,故一并修正(未另开 issue)。改了什么
新增唯一生产者
packages/runtime/src/security/actor-user.ts,三条路径 + AI route 的第二个生产者(dispatcher-plugin.ts的resolveRequestUser)全部经它构造。name取sys_user.name(平台自己的 profile 显示名列,对象上required: true),每请求解析一次:memo 以请求的 ExecutionContext 对象身份为 key(WeakMap),同一请求内 N 次 action dispatch 只读一次,且跨请求不缓存(改名下一请求即生效,无需失效钩子)。name === id精确地只表示"该用户没有可解析的 display name" —— 这正是下游 workaround(name !== id即信任)得以自退的条件。resolveAuthzContext自己那次sys_user读一致):判断"调用者是谁"不能依赖调用者对身份表的读权限。统一形状的键集与依据(PM 授权由实测消费方决定)
先 grep 了下游读键,结论把形状锚到已有的 spec 契约而不是新造:
[ADR-0068 D1]
EvalUser是仓里声明的唯一 user-context 契约,谓词面(formula/stdlib.tsbuildScope)正是以current_user/user/ctx.user三个别名挂同一个对象,且其上name的声明就是 "Display name"。也就是说 REST 的硬编码不只是与兄弟 dispatcher 不一致,它是在一个 spec 已经定义过的键上交付了别的东西。一个 action 的visible谓词和它的body并排写、都拼作ctx.user,两者形状不同正是 ADR-0068 当初要消灭的缺陷。所以身份内核改由 spec 自己的
createEvalUser构造 ——id/name/email/positions/isPlatformAdmin/organizationId;在此之上保留两个 dispatch 面本来就已发布的传输键:userId、displayName(id/name的别名,同值)—— AI route 消费方读displayName,按 PM 口径两键并存而非删键;roles(positions的 ADR-0090 前别名),permissions(权限集名)与systemPermissions(capability)—— AI 路由的 req.user 丢失 capability 通道 —— ec.systemPermissions 从不透传,AI 域无法做任何 capability gate #4705 明确的两条独立通道,仍并列不合并。对所有既有读者是纯增量,未删任何键。匿名 / 自调用仍是
systemprincipal(#2701),AI route 匿名仍返回undefined(handler 以user缺席判断无调用者)—— 这两处语义不同,故不强行合一。resolveRequestUser的?? user.email中间档被去掉:它的 display name 直接取 session 的user.name(better-auth 对sys_user.name的投影,无额外读),这样name === id在每个生产者上含义一致;邮箱仍在自己的email键上。性能实测
真实 ObjectQL + better-sqlite3(200 行
sys_user,主键读),500 次取均值:即每请求首次 0.22ms,同请求内后续 dispatch 约 0.0006ms。未新增连接或缓存层。
测试
新增
packages/runtime/src/action-ctx-user-shape.test.ts(15 例),按三族组织:值(REST 为主,MCP / AI 各一条同值断言,并有一条真 QuickJS sandbox bodyreturn ctx.user.name的端到端)、反向(无 name / 空白 name / 无行 →name === id,双向闭合)、失败模式与形状(读抛错仍 200、system-elevated 读、每请求一次、三路径键集与值逐一相等)。另在ai-request-user-capability-channel.test.ts给第二个生产者加了形状与 fallback 断言,并把该文件里userEmail:这个谁也不赋值的拼写改回声明的email:。反向验证(方向先判后跑):此处是标准 RED,不是 #5018 的 inverted —— 被删的枝就是错值的生产者,不存在 canonical-first 的
??链。预判"把 REST 硬编码还原 → REST 值断言与形状断言转红";实跑 7 failed / 8 passed,红的正是 REST 值、sandbox 端到端、形状两条、system-elevated 读、每请求一次、匿名 principal 的systemPermissions。值得如实记一笔:留绿的 4 条全是"无 name 时回退 id"方向 —— 单靠那一侧抓不到这个回归,双向断言里的另一侧才是探针。命令与结果:
pnpm --filter @objectstack/runtime test→Test Files 94 passed (94) / Tests 1362 passed (1362)pnpm --filter @objectstack/runtime typecheck→tsc --noEmit,无输出pnpm --filter @objectstack/runtime build→ CJS/ESM/DTS build successcheck:authz-resolver✓、check:role-word✓、check:adr-anchors✓、check:route-envelope✓、check:org-identifier✓、check:slot-lookup✓、check-nul-bytesOK文件面
只动
packages/runtime/src/**与该包测试 + 一个 changeset。未触packages/rest(#5487/#5462 领地),未触 hotcrm 仓,未动content/docs/releases/。🤖 Generated with Claude Code
https://claude.ai/code/session_016FNvXhtSdnEGEfLEsMmvxh
Generated by Claude Code