Skip to content

record-validator: [] satisfies required on a multiple: true lookup — diverges from the #9447 ruling (required means non-empty array) #9476

Description

@os-steve

Measured divergence

The #9447 maintainer ruling (2026-08-18, recorded on #9447) states: required on a multi-value lookup means non-empty array — an emptied required set fails validation loudly. Current enforcement does not do this.

Measured (runtime probe via vitest against validateRecord, on main at ad217b192):

  • validateRecord(schema, { members: [] }, 'insert')passes (no throw) for { type: 'lookup', reference: 'sys_user', multiple: true, required: true }
  • validateRecord(schema, { members: [] }, 'update')passes (no throw)
  • Controls: members: null is rejected on insert (required) and on update (required_cleared) — the required machinery works; it just never judges array emptiness.

Read sites

  • packages/objectql/src/validation/record-validator.ts:171-173isMissing returns true only for undefined, null, and blank strings; [] is not missing.
  • packages/objectql/src/validation/record-validator.ts:477 — INSERT required check: def.required && isMissing(value).
  • packages/objectql/src/validation/record-validator.ts:1011 — UPDATE non-regression check, same isMissing gate.

No existing pinned test asserts the opposite (that [] passes required on a multi-value field) — the required suite in record-validator.test.ts covers text/autonumber/null/blank-string shapes only. So this is an enforcement gap versus the ruled contract, not a ruling-vs-pin conflict.

What the fix looks like (for the implementer)

Teach the required check that on a multi-value field an empty array is an empty value (per the ruling: the empty set is representable — it reads back as [], never null — and required judges emptiness). Both read sites above; plus a pinned test asserting the rejection envelope (code + status), per house discipline. Note the spec doc block now states the ruled contract (PR for #9447); this issue is the enforcement half.

Refs: #9447 (ruling), #9438 (cascade member-removal consumer of the same ruling — independent of this validator gap: API writes and form clears can write [] today with no cascade involved).

Found while landing #9447's doc sentences; filed unassigned per finding discipline.


Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions