Skip to content

formula/objectql: relationship traversal in predicates — a validation rule or visibility predicate reads one hop through a lookup (record.crm_account.type); replaces the removed os.lookup declaration (#18318, batch #148) #18682

Description

@hotlong

Filed by the director seat (summon #24, session_01Wj1HUjzyeiBQ8atRf1ZhaL) executing batch #148 item 1, letter B on #18318 (maintainer 「同意」, ruling 5716041368): the never-bound EvalContext.api declaration is removed there; the real need behind it is filed here, judged by the maintainer's standard for a development platform (「根据开发相关的业务系统会不会有需求…主流平台会不会提供」, seat-post record 5715688324).

The need

A validation rule or visibility predicate on one object needs to read a field of the record it points to — record.crm_account.type on crm_opportunity. Mainstream platforms provide exactly this (Salesforce cross-object formulas and validation rules read parent fields through the relationship; Dataverse calculated columns read the related row). Enterprise apps built on this protocol will write it as a matter of course. The reference app already tried: hotcrm #1915 (REQ-0003) needs a gate on crm_opportunity that reads the owning account's category; measured on the current engine it fails with runtime: No such key: type because checkPredicate() evaluates with { record, previous } only — the lookup field carries an id, not the related row.

Scope — relationship traversal, ⛔ not query functions

  • One hop through a lookup / master-detail field: record.<lookup_field>.<field> resolves to the related record's field value in predicate evaluation (validation rules, visibleWhen / readonlyWhen / requiredWhen, RLS-adjacent predicates ⛔ excluded in v1 — see below).
  • Resolution is data supplied to the evaluation context, not I/O inside CEL: the call site that has the query engine loads the referenced fields the predicate names (static analysis of the expression tells it which lookups and which fields) before evaluation, so stdlib.ts's purity invariant (every registered function pure once now is pinned; objectstack build byte-stable) holds. ⛔ No os.lookup(...) / os.exists / os.count — those were the removed declaration's shape and stay removed.
  • N+1 is bounded by construction: one hop, only the fields the expression names, loaded once per evaluation.
  • Permission semantics: v1 applies to validation and UI predicates evaluated for the acting user; the related fields are read under the acting user's own read permission (a field the user cannot read evaluates as absent → the predicate faults loudly, ⛔ never silently true). RLS predicates are ⛔ out of v1 (a security rule reading data it may not read is the question [finding] EvalContext.api declares os.exists / os.count / os.lookup and buildScope() binds none of them — a predicate that calls one is refused, and an unevaluable predicate rejects the write #18318's thread named; it needs its own ruling).
  • Depth: one hop in v1; a second hop is a measured follow-up, not a v1 option.
  • Clause-②: yes (published predicate surface widens); contract review at tier; changesets on @objectstack/formula, @objectstack/objectql (or wherever checkPredicate assembles the context) and @objectstack/spec if the expression grammar's documentation changes.

Sequencing

Blocked on #18545 (can + permission data into EvalContext, ruled A): both change how the evaluation context is assembled at buildScope / checkPredicate; this lands after it on the same seam. hotcrm #1915 waits on this card.

Acceptance (ADR-0136 D2.4)

A checklist item under records-forms (validation rule reading a parent field: passes when the parent field matches, refuses the write when it does not, faults loudly when the user cannot read the parent field) plus a dogfood case on the showcase app; hotcrm #1915's gate is the reference use.

Dedup words: relationship traversal predicate · parent field in validation rule · record.crm_account.type · checkPredicate context · No such key lookup hop · #18318 · hotcrm #1915

⬆️ 行首 Blocked-by: #18545 由分诊席于 2026-09-20T14:26Z 移除 —— #18545 现读 closed/completed,阻塞已解除,卡早已是 pm:queue,只有这条机器可读行被落下。⛔ 正文其余部分一字未动;车道改判与判据见 issuecomment-5750405280


Generated by Claude Code

Activity

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions