Observation-class finding,来自 #5521 / PR #6295 的实施测量。今天没有用户会撞到 ,也不改变任何运行行为 —— 记录的是一处永远取不到值的兜底支 (#4984 死肢家族),不是缺陷。
观察到的事实
packages/runtime/src/sandbox/body-runner.ts 的两个写入方各带一条两支的 ?? 链:
body-runner.ts:315 buildSandboxContext —— user: engineCtx?.user ?? engineCtx?.session?.user
body-runner.ts:340 buildActionSandboxContext —— user: actionCtx?.user ?? actionCtx?.session?.user
第二支在两处都不可达 ,因为这个接缝上出现的两种 session 形状都既不声明、也不生产 user 键:
面
session 类型
键集
唯一生产者
写 user 吗
hook
HookContext['session']
userId / actor / organizationId / accessToken / isSystem / skipTriggers / skipAutomations / positions / preserveAudit(+ roles 墓碑)—— packages/spec/src/data/hook.zod.ts:386-536
ObjectQLEngine.buildSession(),packages/objectql/src/engine.ts:1647-1690,逐字段构造
❌
action
ActionSession
userId / organizationId / positions / roles(弃用别名)—— packages/spec/src/ui/action-params.zod.ts:237-336
buildActionSession(),packages/runtime/src/action-execution.ts:811-823,只写这四个键
❌
两个写入方的形参都是 any,所以 tsc 从来没有、将来也不会对这条链说一句话。
为什么按 observation 归档而不是缺陷
#5521 / PR #6295 把 ScriptContext.user 从 unknown 收窄成 ScriptUser = ActorUser | HookContext['user'],过程中把这条链逐支测了一遍 —— 结论就是上表。那个 PR 刻意没有动它 :本单授权是"给接缝钉类型",顺手删一个运行时表达式属于扩范围,且删除与保留是两种不同的判断,应当单独裁。测量结论已写进 ScriptUser 的 docblock,所以即使这条 finding 一直不处理,下一个读者也不会再被这条链误导。
可能的方向(未裁决)
删掉第二支 ,两处都只留 engineCtx?.user / actionCtx?.user —— 与 validateOrgAxisRedLines 读的 sharing-rule 键是 spec 拒收的:ADR-0105 D6 ① 在 criteria 路径上从不触发 #4984 的处置一致(死肢就是删);
保留但立即可证伪 :若认为将来某个引擎可能在 session 上挂 user,那应当把它声明 到相应的 session 契约里(HookContextSchema.session / ActionSessionSchema),而不是留一条没有契约支撑的消费者侧 ?? —— 后者正是 Prime Directive Add comprehensive test suite for Zod schema validation #12 点名的"消费者侧宽容"反模式;
维持现状,仅靠 ScriptUser docblock 里已写下的测量结论备案。
倾向 1:今天没有任何生产者写这个键,而"以防万一"的第二支恰恰是 #12 说的把错误约定固化成第二套事实上的契约。但这条链跨 hook / action 两个面,删除属于行为面判断,留给分诊裁。
⛔ 不认领,仅记录。会话:session_01Wbxm29qPKnLf44AbSxizqW
Observation-class finding,来自 #5521 / PR #6295 的实施测量。今天没有用户会撞到,也不改变任何运行行为 —— 记录的是一处永远取不到值的兜底支(#4984 死肢家族),不是缺陷。
观察到的事实
packages/runtime/src/sandbox/body-runner.ts的两个写入方各带一条两支的??链:body-runner.ts:315buildSandboxContext——user: engineCtx?.user ?? engineCtx?.session?.userbody-runner.ts:340buildActionSandboxContext——user: actionCtx?.user ?? actionCtx?.session?.user第二支在两处都不可达,因为这个接缝上出现的两种 session 形状都既不声明、也不生产
user键:user吗HookContext['session']userId/actor/organizationId/accessToken/isSystem/skipTriggers/skipAutomations/positions/preserveAudit(+roles墓碑)——packages/spec/src/data/hook.zod.ts:386-536ObjectQLEngine.buildSession(),packages/objectql/src/engine.ts:1647-1690,逐字段构造ActionSessionuserId/organizationId/positions/roles(弃用别名)——packages/spec/src/ui/action-params.zod.ts:237-336buildActionSession(),packages/runtime/src/action-execution.ts:811-823,只写这四个键两个写入方的形参都是
any,所以 tsc 从来没有、将来也不会对这条链说一句话。为什么按 observation 归档而不是缺陷
HookContext['user'],action 侧ActorUser),第二支只是永远返回undefined后被丢弃 —— 结果与没有它时一模一样;ScopedRepo.execute()(packages/objectql/src/engine.ts:7400-7407)传的 ctx 既无user也无session,两支都取不到值,ctx.user恒为undefined—— 这也是正确的语义(该路径确实没有调用者身份);??链只有最后一支可达,读者会误以为存在第二个数据来源。与 #5521 的关系
#5521 / PR #6295 把
ScriptContext.user从unknown收窄成ScriptUser = ActorUser | HookContext['user'],过程中把这条链逐支测了一遍 —— 结论就是上表。那个 PR 刻意没有动它:本单授权是"给接缝钉类型",顺手删一个运行时表达式属于扩范围,且删除与保留是两种不同的判断,应当单独裁。测量结论已写进ScriptUser的 docblock,所以即使这条 finding 一直不处理,下一个读者也不会再被这条链误导。可能的方向(未裁决)
engineCtx?.user/actionCtx?.user—— 与 validateOrgAxisRedLines 读的 sharing-rule 键是 spec 拒收的:ADR-0105 D6 ① 在 criteria 路径上从不触发 #4984 的处置一致(死肢就是删);user,那应当把它声明到相应的 session 契约里(HookContextSchema.session/ActionSessionSchema),而不是留一条没有契约支撑的消费者侧??—— 后者正是 Prime Directive Add comprehensive test suite for Zod schema validation #12 点名的"消费者侧宽容"反模式;ScriptUserdocblock 里已写下的测量结论备案。倾向 1:今天没有任何生产者写这个键,而"以防万一"的第二支恰恰是 #12 说的把错误约定固化成第二套事实上的契约。但这条链跨 hook / action 两个面,删除属于行为面判断,留给分诊裁。
⛔ 不认领,仅记录。会话:
session_01Wbxm29qPKnLf44AbSxizqW