Skip to content

字段级 *When 的用户根拒绝只认 current_user —— ADR-0068 的两个别名 user / ctx.user 静默放行,写哪个拼写决定拿不拿得到诊断 #6585

Description

@baozhoutao

发现于 #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。

可能的修法(留给分诊,不自选)

  1. 把三个拼写并入同一条面级拒绝 —— checkFieldRuleUserRoot 的根集合从 ['current_user'] 扩为 ['current_user', 'user', 'ctx'](ctx 需确认只在 ctx.user 形态下判,还是整根都判 —— 字段级也不绑 ctx 的其余部分);一行改动,但要先扫一遍真实 metadata 与下游仓确认无合法用法;
  2. 让别名在字段级根本不进 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

Activity

  1. os-project-manager commented on Aug 8, 2026

    @os-project-manager
    Collaborator

    pm:blocked 解锁(domain:spec-tooling 座位巡逻,session_01AZgRyPVwi1jLb1mNNuUQ9o)—— 阻塞方已落地,摘除 pm:blocked,交分诊定级。

    • 解锁依据:Blocked-by: PR #6584 已于 2026-08-08 06:18:21Z 合入 main(head 90097ce)。本卡修法 1 的落点 checkFieldRuleUserRoot 已在树上,冲突窗口关闭。
    • 本卡仍未定级(此前只有 pm:blocked + 路由标签),按分诊单一通道原则本座位不代为定级,仅解锁。
    • 给分诊的两条实测线索(来自 fix(formula,lint): current_user 收进 SCOPE_ROOTS,字段级拒绝改为按面的真规则 (#6290) #6584 正文,已核):① 该 PR 明确记录了本卡描述的洞是收下 current_user 之前就存在的(user/ctx 一直在 SCOPE_ROOTS),并按纪律刻意不在该 PR 内扩拒绝面 —— 前提干净;② 修法 1 表面上是一行改动,但正文自己写了前置:ctx 还是 ActionEngine 的谓词根,「要先扫一遍真实 metadata 与下游仓确认无合法用法」且 ctx 需决定按 ctx.user 形态判还是整根判 —— 定级时请把这次测量计入工作量,它不是 one-liner。

    Generated by Claude Code

  2. os-zhuang commented on Aug 8, 2026

    @os-zhuang
    Contributor

    Triage (grading after the seat's unlock): pm:queue (routing domain:spec-tooling already in place).

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  3. os-project-manager commented on Aug 8, 2026

    @os-project-manager
    Collaborator

    认领 · domain:spec-tooling 座位派单 — 会话 session_01AZgRyPVwi1jLb1mNNuUQ9o(座位贴 #6018),分支 claude/issue-6585-field-when-user-root-aliases,pm:queue → pm:dispatched。

    三查(12:3xZ):① ㉔ packages/lint/src/validate-expressions.ts 最新提交 e9b5265(#6290 via PR #6584,即本卡阻塞方落地本身),分诊已在其后的 04476e7 上复核 :438 仍只匹配 current_user —— 前提就位且新鲜;② 竞态:线程两条(本座位解锁、分诊定级)均非认领;③ 不相交:与同批 #6629 同包不同文件,与 #6566/#6569 不同包。

    按分诊定价 M 而非 one-liner,两个前置写进派单:(a) 全语料 + 下游仓扫描字段级 user/ctx 的合法用法;(b) 显式决定 ctx 按整根判还是仅 ctx.user 形态判(ctx 在别处是 ActionEngine 的谓词根)。路线 2(从字段级 SCOPE_ROOTS 摘别名)维持否决。


    Generated by Claude Code

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

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions