Skip to content

作者时表达式校验不认识平台注入列:record.owner_id == os.user.id 被判为 unknown field,最常见的归属谓词编译不过 #6113

Description

@baozhoutao

applySystemFields 给每个业务对象注入 owner_id / organization_id / created_by / updated_by / owning_business_unit_id,它们是真实存在的列(REST 读回来就在 payload 里),但不出现在 /meta/object/<name> 发布的 fields 里。作者时表达式校验(expression-invalid)拿发布的 fields 当字段全集,于是最常见的归属谓词直接编译失败:

defineAction({
  name: 'x', objectName: 'showcase_field_zoo', type: 'script',
  visible: 'record.owner_id == os.user.id',
});
✗ Author-time rules failed
• stack · action 'x' visible: unknown field `owner_id` on `showcase_field_zoo`
    source: `record.owner_id == os.user.id`
    rule: expression-invalid

为什么这条值得修,而不是绕过

record.owner_id == os.user.id 是「只有记录所有者才看得到这个按钮」的标准写法,也是绝大多数应用写的第一条 visible。现在的结果是:

  • 构建期直接红,作者只能改用一个自己声明的字段(showcase 里只好退到 f_user),或者放弃这条谓词;
  • 而运行时这条谓词是完全正常工作的 —— 列在、值在、CEL 求得出来。所以这是纯粹的校验器盲区,不是运行时限制;
  • 同一个盲区还波及消费方:objectui 的 $select 投影同样按「对象声明的字段」过滤谓词引用,owner_id 会被当成拼写错误丢掉(objectui#3501 里只能单独硬编码一份平台列名单来兜住,见 PLATFORM_RECORD_COLUMNS)。两边各自维护一份「平台注入了哪些列」的猜测,正是同一个事实没有权威出口的症状。

复现:examples/app-showcase 里任何一个 defineAction 写上 visible: 'record.owner_id == os.user.id',pnpm validate 即报上述错误(本单发现于 objectui#3501 的 showcase 覆盖工作)。

方案(三选一,倾向 1)

  1. 让注入列成为字段全集的一部分:applySystemFields 注入时给字段打 system: true 并照常进 fields,/meta/object 发布它们(前端已有 isSystemManagedField 按 system 标志把它们排除出默认列,所以「发布」不等于「显示」)。校验器与所有消费方从此读同一个权威来源。
  2. 校验器侧单独把注入列名单并进已知字段集合 —— 修得快,但把「平台注入了哪些列」这个事实又抄了一份。
  3. 明确宣布这些列不可在表达式中引用,并在错误信息里给出替代写法 —— 需要同时回答「那归属谓词该怎么写」,目前没有答案。

验收

visible: 'record.owner_id == os.user.id' 能通过 objectstack validate,且运行时行为与校验通过后的预期一致;消费方不再需要各自维护平台列名单。

Activity

  1. claude commented on Aug 7, 2026

    @claude
    Contributor

    Triage: finding + domain:engine-core — the reported failure is a stale premise, and the part that remains already has a dispatch entry.

    1. The author-time half is already fixed on origin/main (#5378, closed 2026-08-06T11:47:43Z — about 14.5 h before this issue was filed).

    Read against origin/main (d285586):

    • packages/lint/src/validate-expressions.ts:140 — buildFieldIndex now emits [...new Set([...names, ...injectedColumnsFor(obj)])];
    • packages/lint/src/system-fields.ts:69 — injectedColumnsFor delegates to resolveInjectedSystemColumns (@objectstack/spec/data), the same derivation applySystemFields consumes, so author-time and runtime cannot disagree;
    • packages/spec/src/data/injected-system-columns.ts — an omitted ownership is treated exactly like 'user' (pinned in injected-system-columns.test.ts), and showcase_field_zoo declares no ownership, is not sys_*, and has no managedBy ⇒ owner_id is in its resolved name set;
    • the action predicate path is the same index: validate-expressions.ts:683 — check(`… action '${name}' visible`, action.visible, obj, 'record').

    So the exact repro in the body — visible: 'record.owner_id == os.user.id' on showcase_field_zoo — should pass objectstack validate on current main. The run behind this report most likely predates the merge or used a worktree cut before it (SKILL Operational note 4: verify against origin/main, never a working tree). If it still reproduces on a fresh checkout of current main, say so here and this grading gets revisited — that would be a genuinely new defect, not this one.

    Note also what #5378 deliberately did not widen: buildFieldTypeIndex and the null-guard index still read declared fields only, with the reasons in the comments. Proposal 2 in the body ("merge an injected-column list into the validator") is the shape #5378 explicitly avoided — the second copy of the fact.

    2. What genuinely remains is #4513's, not a new entry. The residual — /meta/object not publishing the injected columns, so every consumer keeps its own guess (objectui's PLATFORM_RECORD_COLUMNS, objectui#3501) — is exactly what #4513 (pm:queue, domain:engine-core, target:v17) already asks for: "the /meta/objects projection should be the runtime registry (registry.getObject(), i.e. after applySystemFields / provisionPrimary)". Publishing from the registry makes the injected columns appear by construction. Converging rather than opening a second entry (one thing, one dispatch entry).

    3. Domain rationale: applySystemFields lives in packages/objectql/src/registry.ts and the publishing surface in packages/metadata-protocol/src/protocol.ts ⇒ domain:engine-core, the same lane #4513 is already routed to. Not domain:spec — the spec-side derivation this needs already exists and is unchanged.

    Cross-links: #5378 (the merged author-time fix), #4513 (the surviving dispatch entry), objectui#3501 (the consumer-side duplication).

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


    Generated by Claude Code

  2. baozhoutao commented on Aug 7, 2026

    @baozhoutao
    ContributorAuthor

    已被 #5378 修掉,本单是重复,关掉。

    合入 main(b3c1f3cd5 之后的树)重新实测:visible: 'record.owner_id == os.user.id' 现在 objectstack validate exit 0,不再报 unknown field owner_id。

    修法正是本单方案里倾向的路线 1 —— 让注入列成为一个两侧都能读的权威来源,而不是在校验器里另抄一份名单:packages/spec/src/data/injected-system-columns.ts。它的抬头把本单描述的症状写得比我更准:

    buildFieldIndex (validate-expressions.ts) 和 highlightFields 存在性检查根本没回答这个问题,于是引用了「仅注入」列的表达式被当成未知字段拒绝 —— 平台自己的 linter 告诉作者平台自己的列不存在(#5378)。

    下游动作:#6157(showcase 动作显隐矩阵)已把 record.owner_id == os.user.id 作为正式标本加回,同时是 #5378 的活证据。

    本单开单时我只跑了自己分支(落后 main 136 个提交),没先核对 main —— 是我的疏漏,不是新缺陷。

    🤖 Generated with Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions