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-173 — isMissing 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
Measured divergence
The #9447 maintainer ruling (2026-08-18, recorded on #9447) states:
requiredon 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, onmainatad217b192):validateRecord(schema, { members: [] }, 'insert')— passes (no throw) for{ type: 'lookup', reference: 'sys_user', multiple: true, required: true }validateRecord(schema, { members: [] }, 'update')— passes (no throw)members: nullis 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-173—isMissingreturns true only forundefined,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, sameisMissinggate.No existing pinned test asserts the opposite (that
[]passes required on a multi-value field) — the required suite inrecord-validator.test.tscovers 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
[], nevernull— andrequiredjudges 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