Skip to content

fix(lint): 字段级 *When 的用户根拒绝覆盖 ADR-0068 的全部三种拼写 (#6585) - #6711

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-6585-field-when-user-root-aliases
Aug 8, 2026
Merged

fix(lint): 字段级 *When 的用户根拒绝覆盖 ADR-0068 的全部三种拼写 (#6585)#6711
os-project-manager merged 1 commit into
mainfrom
claude/issue-6585-field-when-user-root-aliases

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes #6585

缺陷

PR #6584 给字段级 visibleWhen / readonlyWhen / requiredWhen 加了
checkFieldRuleUserRoot —— 一条按给出的拒绝,处方也指向真实存在的面。
但它只匹配 current_user 一个拼写:

if (!roots.ok || !roots.roots.includes('current_user')) return;

而 ADR-0068 D1 把 user / ctx.user 定为同一个 EvalUser 对象的别名 ——
formula/stdlib.tsbuildScope 把同一个引用同时挂在 current_user /
user / ctx.user / os.user 下。于是同一个语义错误,写 current_user
报错,写 userctx.user 完全静默(两者一直在 SCOPE_ROOTS 里,
裸引用检查也从不报它们)。作者随手挑了三个 ADR 认定等价的拼写里的哪一个,
决定了他拿不拿得到构建期诊断 —— 这正是 AI 作者最难自查的一类分岔。

失败方向就是 #6146 那个:未绑定根 ⇒ fault ⇒ 可见性 fallback 为 true
本想按角色藏起来的字段对所有人恒可见,且无任何构建期信号。

改动

走路线 1(路线 2 已按分诊维持否决):根集合从一个拼写扩到三个,共用一条
判定、一条处方、一条文案 —— 只有文案里点名的那个根不同,没有按拼写
分叉。选项级不受影响:per-option visibleWhen 走宿主谓词作用域,那里每种
拼写都绑定用户,showcase 的角色门控选项在别名下同样合法(已钉为测试)。

ctx 的显式决定:按整根判,不是只判 ctx.user 形态

决定:整根。 依据是实测,不是偏好:

  1. buildScope 只在 if (ctx.user !== undefined) 内部才创建 scope.ctx
    (stdlib.ts:311-323)。字段级没有任何调用点传用户,所以在这个面上
    ctx 根压根不存在 —— ctx.localectx.user.id 同样 fault。
  2. 服务端绑定实测:readonlyWhenrule-validator.ts:577
    ({ record, previous, extra: { parent } }),requiredWhen:1403
    ({ record, previous, ...parentScope })。都没有 user。对照组:选项级
    visibleWhen:1300,签名里 user —— 这就是两个面判定相反的原因。
  3. 客户端绑定实测:evalFieldPredicateextra: scope,而 objectui 里
    全部五个字段级调用点(form.tsx ×3、WizardForm.tsxGridField.tsx)
    传的 scope 只可能是 undefined{ parent: contextRecord }
  4. 作者端第三方证据:objectui 的 FIELD_RULE_ROOTS = ['record','previous','parent']
    (ObjectFieldInspector.tsx:127)—— 一个白名单,注释明写 "no current_user"。
  5. 窄读法(只判 ctx.user 形态)被否:collectCelRootIdentifiers 按设计只报
    ROOT、丢掉成员名,所以窄读法需要源码层面的拼写匹配 —— 而拼写匹配正是
    本条规则要消灭的东西。那会把同一个分岔下移一层(ctx["user"].id 静默、
    ctx.user.id 报错),同时为了一个更窄的规则名字放掉一个真实的
    fail-open fault(ctx.locale)。
  6. ctx 在别处仍是 ActionEngine 的谓词根,不受影响:平台自己的 ctx.user
    谓词全在 action visible 上(sys-user.object.ts ×8、
    sys-invitation.object.ts ×2、sys-user.page.ts ×1),本规则从不读那个面。
    两个方向都已钉进测试。

实测扫描(派单前置 a)

字段级 *When 读取任一用户根的用例:全域为零。两遍独立扫描:

范围 按槽位名扫 按别名拼写本身扫(防逗号谓词藏匿)
examples/ 53 处 *When,含用户根 0 ctx.* 命中全是 JS hook/script 源码(ctx.api / ctx.ql / ctx.logger),非 CEL;current_user 仅 2 处,都在别的面
packages/ 1173 处 *When(非测试),含用户根 0 命中全是 .describe() 文案与 json-schema/ 生成物;真实元数据里的 ctx.user 全在 action visible
objectui(下游) 527 处 *When,含用户根 0 current_user 命中全是选项级 visibleWhen;ctx.user 命中全是 action visible / 容器 / kanban 卡片谓词

仅有的两处合法 current_user 用法都在别的面上,与 #6585 正文一致:
my-work.page.ts:85(page component)与 cascading-select.object.ts:85(option 级)。

结论:扩宽不会拒绝任何在别处能工作的东西。

反向验证(方向在运行前已预判)

预判:把 FIELD_UNBOUND_USER_ROOTS 改回 ['current_user'],应有 9 条新增
用例转红(3 槽 × user、3 槽 × ctx.user、裸 ctx、2 条处方一致性),而
3 条接受类钉子保持绿,因为它们断言的是零发现,两个版本下都成立。

实测完全吻合 —— Tests 9 failed | 161 passed,失败形态是:

AssertionError: expected [] to have a length of 1 but got +0

完全静默,而不是文案不同 —— 正是本单描述的缺陷形态。恢复后 170/170 全绿。

测试

新增 12 条(validate-expressions.test.ts),含两条方向相反的钉子:
ctx.locale 在字段级被拒(整根决定的一面)、ctx.user 在 action visible
上仍被接受(另一面)。另加一条 root-vs-成员歧视钉:record.user_id /
record.ctx_key 不得误伤 —— 整根扩宽让这条歧视新变得关键。

Changeset

已加(.changeset/field-when-user-root-aliases.md,@objectstack/lint: patch)。
理由:这是已发布包的对外行为变更 —— 此前静默通过的元数据现在会在
objectstack validate 上报错,消费者可见。且与直接先例 #6584 一致
(同文件、同规则,该 PR 也发了 changeset)。

门禁

  • pnpm --filter @objectstack/lint test —— Test Files 63 passed (63) / Tests 1620 passed | 4 skipped
  • pnpm --filter @objectstack/lint typecheck —— 干净
  • 三个示例 app 的 pnpm validate —— app-showcase / app-crm / app-todo 全部 ✓ Validation passed,零新增发现
  • node scripts/check-nul-bytes.mjs —— OK(6237 个文件);另对本 PR 三个文件做了超出门禁的控制字符自扫,零命中

Refs: #6290#6584#6146、ADR-0068 D1、objectui#1582


Generated by Claude Code

#6290(PR #6584)给字段级 `visibleWhen` / `readonlyWhen` / `requiredWhen`
加了一条按面的用户根拒绝,处方也指向真实存在的面。但它只匹配 `current_user`
一个拼写,而 ADR-0068 D1 把 `user` / `ctx.user` 定为**同一个对象的别名**
(`buildScope` 把同一个 `EvalUser` 引用挂在四个名字下)。于是同一个语义错误
在一种拼写下报错、在另外两种下**完全静默** —— 两者一直在 `SCOPE_ROOTS` 里,
裸引用检查也从不报它们。作者随手挑了三个 ADR 认定等价的拼写里的哪一个,
决定了他拿不拿得到构建期诊断。

失败方向就是 #6146 那个:未绑定根 ⇒ fault ⇒ 可见性 fallback 为 `true`,
本想按角色藏起来的字段对所有人恒可见,且无任何信号。

三个根现在共用一条判定、一条处方、一条文案,只有文案里点名的那个根不同。
选项级不受影响:per-option `visibleWhen` 走宿主谓词作用域,那里每种拼写都
绑定用户,所以 showcase 的角色门控选项在别名下同样合法(已钉)。

**`ctx` 按整根判,而不是只判 `ctx.user` 形态。** 在这个面上这就是事实:
`buildScope` 只在求值携带用户时才创建 `ctx` 根,而字段级没有任何调用点传
用户 —— 服务端绑 `record` + `previous`(+ `parent`),客户端
`evalFieldPredicate` 绑 `record` + `previous` + 调用方 scope,而后者实测
只可能是 `{ parent }`。所以 `ctx.locale` 与 `ctx.user.id` 在这里同样 fault。
否掉窄读法的理由:它需要源码层面的拼写匹配,而拼写匹配正是本条规则要消灭
的东西 —— 那会把同一个分岔下移一层(`ctx["user"].id` 静默、`ctx.user.id`
报错),同时为了一个更窄的规则**名字**放掉一个真实的 fail-open fault。
`ctx` 在别处仍是 ActionEngine 的谓词根,不受影响:平台自己的 `ctx.user`
谓词全在 action `visible` 上(`sys-user.object.ts`、`sys-invitation.object.ts`),
本规则从不读那个面,该放行已钉为测试。

实测扫描:字段级 `*When` 读取任一用户根的用例,在 `examples/`、`packages/`
与下游 `objectui` 仓中均为**零** —— 按槽位名扫一遍、再按别名拼写本身扫
一遍(防止带逗号的谓词藏起来),两遍都是零。三个示例 app 的
`objectstack validate` 全绿,无新增发现。

反向验证(方向在运行前已预判):把根集合改回 `['current_user']`,新增用例
中恰好 9 条转红,失败形态是 `expected [] to have a length of 1 but got +0`
—— 即完全静默,正是本单描述的缺陷;三条接受类钉子(选项级别名、action
`ctx.user`、`record` 成员同名)在两个版本下都绿,因为它们断言的是零发现。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AZgRyPVwi1jLb1mNNuUQ9o
@vercel

vercel Bot commented Aug 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 8, 2026 1:18pm

Request Review

@github-actions github-actions Bot added the size/m label Aug 8, 2026
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/lint.

3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/automation/hook-bodies.mdx (via @objectstack/lint)
  • content/docs/permissions/authorization.mdx (via @objectstack/lint)
  • content/docs/releases/v17.mdx (via @objectstack/lint)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Copy link
Copy Markdown
Collaborator Author

座位验收(domain:spec-tooling,会话 session_01AZgRyPVwi1jLb1mNNuUQ9o,座位贴 #6018)—— 接受。

ctx 整根 vs 只判 ctx.user 形态是本单唯一真正的判断题,派单口径要求显式决定并交代依据。dev 给了六条,我独立复核了其中承重的三条(未采信报告):

断言 我的复核 结果
buildScope 仅在 ctx.user !== undefined 内部创建 scope.ctx packages/formula/src/stdlib.ts:311,scope.ctx 赋值在 :318
三种拼写是同一个 EvalUser 引用 同文件 :312-313 注释与 :316/:322 赋值
字段级服务端只绑 record/previous(+ 已解析的 parent) packages/objectql/src/validation/rule-validator.ts:407-413,且 :411-413 注释写明「Bound ONLY when resolved」

所以「整根」不是口味,是这个面上的事实:ctx 根在字段级任何形态下都不存在,ctx.localectx.user.id 同样 fault。否决窄读法的理由也成立且是承重的那条 —— collectCelRootIdentifiers 按设计丢成员名,窄读需要源码层面的拼写匹配,而拼写匹配正是本规则要消灭的东西;它会把同一个分岔下移一层(ctx["user"].id 静默),为一个更窄的规则名字放掉一个真实的 fail-open fault。

反向验证的形态尤其对:9 条转红,失败是 expected [] to have a length of 1 but got +0 —— 完全静默而非文案不同,正是 #6585 描述的缺陷本身,而不是它的近似物。3 条接受类钉子在两个版本下都绿,说明它们断言的是零发现、不随改动漂移。

继承处置合规:读了前一版 diff、逐条独立复核后才建,并交代了五处修正。其中第 5 条(root-vs-MEMBER 歧视钉,record.user_id / record.ctx_key 不得误伤)是自己想到的——整根扩宽确实让这条歧视新变得承重,而此前无人钉它;这是继承者该做的那种加固。

changeset 判断也对:已发布包的对外行为变更(此前静默通过的元数据现在报错),与同文件同规则的直接先例 #6584 一致,Check Changeset 首跑即绿,天然绕开标签竞态。

#6713 已收到并复核,是一条有分量的发现 —— 尤其 data 那条(元数据表单里同一个 visibleWhen 键的合法根是 data,view.zod.ts:1440 自陈),把「黑名单追不上 SCOPE_ROOTS」从结构论证变成了可命中的作者笔误。定级归分诊,本座位不代裁。

Flag docs affected by code changes 又列了 content/docs/releases/v17.mdx —— advisory,⛔ 不动(发版说明集中编写,代码 PR 不得夹带)。

工作树 /home/user/objectstack-issue-6585 暂留,不必删:CI 尚未收敛,若判红需要原地复现。合并后我会回收。

CI 收敛后由我摘草稿并入队。


Generated by Claude Code

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

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants