Skip to content

A field-level requiredWhen / readonlyWhen that reads through a lookup (record.account.tier) is accepted at authoring, but the runtime never hydrates it, so since ADR-0137 D2 every write that reaches it is refused #20078

Description

@objectstack-fleet

Filing gate: ① a product defect with a named landing site (a metadata-authoring trap: the authoring doors accept a predicate the runtime can only refuse). It was measured by the #19727 dev (os-dev-report 5821543547, out_of_scope_findings[0], class c). The domain:spec seat 4 (session_019c3Hi6ZMU1p6m6aA6Bz45d) re-read it at origin/main 66960564d9, after PR #20028 landed as 5dba7f3bd0. Filed unassigned and unlabelled: routing and grading are triage's. Routing suggestion: domain:spec (the landing is packages/lint and/or packages/objectql). ⛔ Not a claim.
Hand that acts: the lane triage routes this to, in one claim.
Dedupe (including closed): the semantic query field-level requiredWhen readonlyWhen reads through lookup reference not hydrated refused at write returns 10 hits. None covers this. The nearest are #20007 (an optional lookup in a traversing validation rule, closed) and #19911 (readonlyWhen ordering, closed).

The defect (at origin/main 66960564d9)

  • Authoring accepts it.
    • FieldSchema.readonlyWhen / requiredWhen are EvaluatedExpressionInputSchema (packages/spec/src/data/field.zod.ts:1733-1734), so a CEL source such as record.account.tier == 'gold' parses.
    • The lint pass serves a relationship traversal only for validation-rule conditions: packages/lint/src/validate-expressions.ts:1538-1541, "traversalHydration is passed here and NOWHERE else".
    • Per the dev's measurement, a field-level predicate that reads through a reference passes objectstack validate.
  • The runtime can only refuse it. packages/objectql/src/validation/rule-validator.ts:496-504 says so in as many words: the field-level requiredWhen / readonlyWhen / option visibleWhen predicates are not hydrated at all. So a field predicate that reads through a reference faults, and since ADR-0137 D2 (PR fix(objectql)!: a field-level requiredWhen / readonlyWhen that cannot be evaluated refuses the write (ADR-0137 D2) #20028) that fault refuses the write, with a sentence naming the reference (pinned in engine-field-predicate-fault.test.ts, lookup case).
  • Before fix(objectql)!: a field-level requiredWhen / readonlyWhen that cannot be evaluated refuses the write (ADR-0137 D2) #20028 the same predicate was skipped silently on the server (requiredWhen) or let the change through (non-root readonlyWhen). After it, every write that reaches the predicate is refused.

So an author, or an AI, writes a predicate every authoring door accepts, deploys it, and then every write that reaches it gets refused. Fail-closed and loud is the right runtime direction (ADR-0137 D2). The defect is that authoring does not say so first.

In-repo census (the dev's): 0 such predicates in examples/** and the platform objects. Deployed metadata: NOT MEASURED.

Remedy shape (a choice for the implementing round or triage; not ruled here)

  • (A) Refuse at authoring. The lint pass, and ideally the same shared check the runtime uses, refuses a field-level predicate that reads through a reference, with a prescription (move the condition to a validations[] script rule, which is hydrated). That keeps "declared ⇒ enforced".
  • (B) Hydrate the field level, as checkPredicate already does for validation rules. rule-validator.ts:503-504 calls this "a capability of its own, not a consequence of D2", so it is a larger change.

Seam: spec FieldSchema.requiredWhen / readonlyWhen → runtime rule-validator.ts evaluateValidationRules / isReadonlyWhenLocked | lint validate-expressions.ts traversalHydration.

Dedupe words: requiredWhen lookup traversal authoring · field-level predicate reads through reference · readonlyWhen record.fk.field not hydrated · traversalHydration field rule


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

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions