发现于 #6290 的实现(PR #6584 ),不在该单范围 ,按纪律另立存档交分诊定级。
事实
ADR-0068 D1 把 current_user、user、ctx.user 定为同一个 EvalUser 对象的三种拼写 —— formula/stdlib.ts:312-322 的 buildScope 就是这么挂的(同一个 currentUser 引用同时赋给 scope.current_user / scope.user / scope.ctx.user / os.user),validate.ts:261-269 的四条 role-catalog 正则也全按 (?:current_user|user|ctx\.user) 三选一写。
但字段级 visibleWhen / readonlyWhen / requiredWhen 一个用户根都不绑(#6146 实测:evalFieldPredicate / resolveFieldRuleState 只绑 record + previous + parent;objectui#1582 的作者端补全 FIELD_RULE_ROOTS 钉的是同一套,注释明写 "nothing else (no current_user)")。
PR #6584 在 packages/lint/src/validate-expressions.ts 加了 checkFieldRuleUserRoot,对字段级三槽的 current_user 给出面级拒绝 + 正确处方。该判定只匹配 current_user 一个拼写 :
if ( ! roots . ok || ! roots . roots . includes ( 'current_user' ) ) return ;
于是同一个语义错误,写 'admin' in current_user.positions 拿到诊断,写 'admin' in user.positions 或 'admin' in ctx.user.positions 则完全静默 —— 后两者一直在 SCOPE_ROOTS 里(cel-engine.ts:56 的 user、:60 的 ctx),裸引用检查从不报它们。
后果
失败方向与 #6146 同:未绑定标识符 ⇒ fault ⇒ 可见性 fallback 为 true ⇒ 本想按角色藏起来的字段对所有人恒可见 ,且无任何构建期信号。作者拿不拿得到诊断,取决于他挑了 ADR 认定等价的三个拼写里的哪一个 —— 这正是 AI 作者最容易踩、也最难自查的一类分岔。
现状与范围(实测)
为什么没在 #6290 里一并修
user / ctx 在字段级的静默放行是收下 current_user 之前就存在 的行为(两者一直是 SCOPE_ROOTS 成员,从未被该面拒过),不是 #6584 引入的回归;而把拒绝面从一个拼写扩到三个是一次独立的行为变更,ctx 尤其还是 ActionEngine 的谓词根,爆炸半径要单独量。#6584 的新文案指向的是面 (「字段级条件规则只绑 record/previous/parent」)而非拼写,所以它本身不会把作者推去写 user.positions。
可能的修法(留给分诊,不自选)
把三个拼写并入同一条面级拒绝 —— checkFieldRuleUserRoot 的根集合从 ['current_user'] 扩为 ['current_user', 'user', 'ctx'](ctx 需确认只在 ctx.user 形态下判,还是整根都判 —— 字段级也不绑 ctx 的其余部分);一行改动,但要先扫一遍真实 metadata 与下游仓确认无合法用法;
让别名在字段级根本不进 SCOPE_ROOTS —— 与 finding: packages/formula 一包两话 —— SCOPE_ROOTS 不含 current_user 而 introspectScope 宣告它;字段级 visibleWhen 的 lint 拒绝还附错误修法「Write record.current_user」 #6290 刚确立的「基线宽、逐面窄」分工相反,不建议,记录以备对照。
Blocked-by: #6584 (修法 1 的落点就是该 PR 引入的 checkFieldRuleUserRoot,合并前改动会冲突)
Refs: #6290 、#6146 、#5149 、ADR-0068 D1、objectui#1582
发现于 #6290 的实现(PR #6584),不在该单范围,按纪律另立存档交分诊定级。
事实
ADR-0068 D1 把
current_user、user、ctx.user定为同一个EvalUser对象的三种拼写 ——formula/stdlib.ts:312-322的buildScope就是这么挂的(同一个currentUser引用同时赋给scope.current_user/scope.user/scope.ctx.user/os.user),validate.ts:261-269的四条 role-catalog 正则也全按(?:current_user|user|ctx\.user)三选一写。但字段级
visibleWhen/readonlyWhen/requiredWhen一个用户根都不绑(#6146 实测:evalFieldPredicate/resolveFieldRuleState只绑record+previous+parent;objectui#1582 的作者端补全FIELD_RULE_ROOTS钉的是同一套,注释明写 "nothing else (nocurrent_user)")。PR #6584 在
packages/lint/src/validate-expressions.ts加了checkFieldRuleUserRoot,对字段级三槽的current_user给出面级拒绝 + 正确处方。该判定只匹配current_user一个拼写:于是同一个语义错误,写
'admin' in current_user.positions拿到诊断,写'admin' in user.positions或'admin' in ctx.user.positions则完全静默 —— 后两者一直在SCOPE_ROOTS里(cel-engine.ts:56的user、:60的ctx),裸引用检查从不报它们。后果
失败方向与 #6146 同:未绑定标识符 ⇒ fault ⇒ 可见性 fallback 为
true⇒ 本想按角色藏起来的字段对所有人恒可见,且无任何构建期信号。作者拿不拿得到诊断,取决于他挑了 ADR 认定等价的三个拼写里的哪一个 —— 这正是 AI 作者最容易踩、也最难自查的一类分岔。现状与范围(实测)
examples/与packages/的字段级*When零处使用任一用户根(grep 实测),所以今天没有已发布 metadata 受影响 —— 这是覆盖洞,不是在线故障;current_user合法用法都在别的面上:examples/app-showcase/src/ui/pages/my-work.page.ts:85(page component,另一条规则管)与.../cascading-select.object.ts:85(option 级,PR fix(formula,lint): current_user 收进 SCOPE_ROOTS,字段级拒绝改为按面的真规则 (#6290) #6584 已钉为合法)。为什么没在 #6290 里一并修
user/ctx在字段级的静默放行是收下current_user之前就存在的行为(两者一直是SCOPE_ROOTS成员,从未被该面拒过),不是 #6584 引入的回归;而把拒绝面从一个拼写扩到三个是一次独立的行为变更,ctx尤其还是 ActionEngine 的谓词根,爆炸半径要单独量。#6584 的新文案指向的是面(「字段级条件规则只绑 record/previous/parent」)而非拼写,所以它本身不会把作者推去写user.positions。可能的修法(留给分诊,不自选)
checkFieldRuleUserRoot的根集合从['current_user']扩为['current_user', 'user', 'ctx'](ctx需确认只在ctx.user形态下判,还是整根都判 —— 字段级也不绑ctx的其余部分);一行改动,但要先扫一遍真实 metadata 与下游仓确认无合法用法;SCOPE_ROOTS—— 与 finding: packages/formula 一包两话 —— SCOPE_ROOTS 不含 current_user 而 introspectScope 宣告它;字段级 visibleWhen 的 lint 拒绝还附错误修法「Write record.current_user」 #6290 刚确立的「基线宽、逐面窄」分工相反,不建议,记录以备对照。Blocked-by: #6584(修法 1 的落点就是该 PR 引入的
checkFieldRuleUserRoot,合并前改动会冲突)Refs: #6290、#6146、#5149、ADR-0068 D1、objectui#1582