Repository navigation
Description
Split out of #9438 by the triage seat (the same shape as the #9364 → #9389 split): the interim repair on #9438 (escalate defaulted set_null to restrict for multiple: true, mirroring the pinned required-FK escalation) is queued and decides no semantics; this card carries the one question that blocks the real fix (#9438 option 1 — remove the member instead of refusing).
The question
When cascadeDeleteRelations removes the deleted record's id from a multiple: true lookup array and the array becomes empty, what does the field read back as — [] or null?
The difference is observable on the read path, and it decides what a required multi-value validator sees ([] is a present-but-empty value; null is an absent one). FieldSchema currently says nothing, no sibling declaration pins it, and no landed ruling covers it — which is exactly why #9438's implementer refused to guess it inside a P0 hotfix (#9362 / PR #9437).
Four-facet analysis
- Platform long-term coherence: the answer should be one rule for every writer of multi-value lookups (cascade repair, form clears, API writes), not a cascade-only convention — whichever value is chosen, the ruling text should say it binds the field's empty representation generally, or explicitly scope it to cascade writes and say why.
- Measured business pull: becomes live the moment fix(engine): probe a multiple:true reference field with a spelling its storage answers #9437 lands (the limb has never executed before it); the stock showcase carries the shape (
showcase_field_zoo.f_lookups). Until option 1 lands, the interimrestrictrefusal (409) is what users see. - AI-agent error-resistance:
[]keeps the field's type stable (an array field always reads as an array) — one less null-branch for generated code and formula/filter predicates to mishandle;nullmatches the single-value lookup's cleared state but makes the field's type a union in practice. - Startup scope discipline: either answer is one line in the repair plus a sentence in
FieldSchema's doc; the cost is only in changing it later, so rule once, now.
Recommendation: [] — type-stable for every reader, consistent with "the set lost a member" rather than "the field was cleared", and it keeps required semantics honest (an emptied required set should fail validation loudly, which [] does and a vanished null may not, depending on the validator's absent-vs-empty treatment — the ruling should state which of those required means for multi-value lookups, since that is the same question wearing validation clothes).
On the ruling
The answer unblocks #9438 option 1 (member removal); the interim restrict escalation reverts in the same PR. Ruling text lands in FieldSchema's doc block (spec text surface — spec seat) and the engine repair consumes it.
Refs: #9438 (the defect and the interim) · #9362 / PR #9437 (what makes the limb reachable) · the pinned required-FK set_null → restrict escalation in the same block of packages/objectql/src/engine.ts.
Filed by the triage seat routine — session session_pm_triage_20260818T0104Z.
Activity
os-zhuang commentedon Aug 18, 2026
Maintainer ruling recorded — maintainer, 2026-08-18, batch acceptance of the triage seat's decision-inbox recommendations, verbatim: 「同意」. Four-facet analysis delivered in-session; provenance: triage session
session_pm_triage_20260818T0104Z.Ruled: the emptied multi-value lookup reads back as
[], and in the same stroke:requiredon a multi-value lookup means non-empty array — an emptied required set fails validation loudly. The rule binds the field's empty representation generally (every writer: cascade repair, form clears, API writes), not as a cascade-only convention.Landing: the ruling sentence goes into
FieldSchema's doc block (spec text surface — spec seat, clause-② semantic face, contract-review tier); #9438's option 1 (member removal,next = current.filter(...), empty ⇒[]) consumes it and reverts the interimrestrictescalation in the same PR.Label
needs-user-decision→pm:queuein the same stroke.Generated by Claude Code
os-steve commentedon Aug 18, 2026
Claim: session
session_01Fs18A2DdXLVN2h8PaaFBcP(thedomain:specexecution seat, #6017), branchclaude/issue-9447-multivalue-empty-representation. Clause-②: yes per the ruling comment (semantic face: defines the empty representation and whatrequiredmeans for multi-value lookups) — contract-review tier; if the Fable tier is quota-refused at launch, the measured quota exemption applies (→ opus, recorded here, compensating measured review by this seat before landing). Scope: the ruled sentences land inFieldSchema's doc block ([]general rule, every writer;required= non-empty array); the engine consumption belongs to #9438's PR, not this card. Generated-artifacts coupling declared: wave-8 siblings #9463/#9406/#9345 — landings serialize, later landers run os-regen-merge.Generated by Claude Code
docs(spec): FieldSchema pins the ruled multi-value empty representati…
os-steve commentedon Aug 18, 2026
{ "issue": 9447, "outcome": "delivered", "pr": 9493, "branch": "claude/issue-9447-multivalue-empty-representation", "head": "e48b04963", "before_after": "field.zod.ts `required` (was line 757, now 756-765): describe + new doc block gain 'On a multi-value lookup (multiple: true) required means NON-EMPTY array — an emptied required set fails validation loudly; [] does not satisfy it (#9447, maintainer ruling 2026-08-18)'. `multiple` (was line 775, now 783-793): describe + new doc block gain 'An emptied multi-value lookup reads back as [], never null — binds every writer (cascade repair, form clears, API writes), not just cascade repair (#9447, maintainer ruling 2026-08-18)'. content/docs/references/data/field.mdx regenerated via gen:docs. Full excerpts quoted in the PR body.", "current_behavior_measured": "Runtime probe (temp vitest vs validateRecord, deleted, never committed) at main=ad217b192: [] on a required+multiple:true lookup PASSES validation on both insert and update; null is rejected on both (required / required_cleared). Cause: record-validator.ts:171-173 isMissing covers undefined/null/blank-string only, read at :477 (insert) and :1011 (update). No pinned test asserts the opposite semantics — enforcement gap, not a ruling-vs-pin conflict, so the ruled doc landed and the divergence was filed.", "divergence_finding": 9476, "files": [ "packages/spec/src/data/field.zod.ts", "content/docs/references/data/field.mdx", ".changeset/nine-camels-behave.md" ], "gates": [ "check:generated (all 13 up to date, re-run at head e48b04963 after final commit)", "spec test 409 files / 10915 passed", "spec typecheck exit 0", "dispatch-gates union 21 families all PASS at e48b04963 (check-dev-prereqs green after full pnpm build; its first red was fresh-worktree build state, 52 missing dists, not the diff)", "check:nul-bytes PASS" ], "deviations": [] }Generated by Claude Code
Generated by Claude Code
os-steve commentedon Aug 18, 2026
ACCEPT — review of record against PR #9493 (head
e48b04963), full diff read (3 files).required's doc block + describe carry "non-empty array,[]does not satisfy it", andmultiple's carry "reads back as[], nevernull— binds every writer", each citing the ruling date and cross-referencing the other. The reference page regenerated to match. Acceptance byte-identical; patch changeset.nullcontrols both directions) proved[]on a required+multiple: truelookup PASSES validation today (record-validator.ts:171-173isMissingcovers undefined/null/blank only, read at:477insert and:1011update). No pinned test asserts the opposite, so this is an enforcement gap versus the ruled contract, not a ruling-vs-pin conflict — the doc landed as ruled and the gap is on record as record-validator:[]satisfiesrequiredon amultiple: truelookup — diverges from the #9447 ruling (required means non-empty array) #9476 with the read sites. The cascadeDeleteRelations' set_null limb nulls the WHOLE multi-value array, dropping every other live reference #9438 lane (which consumes this ruling) and triage now have the enforcement question in queueable form.check:generated13/13 re-run post-final-commit. No deviations.Landing: wave-8 serial relay — #9489 (#9463) holds the current slot; this PR lands next (ready + auto-merge squash after #9489 merges and CI here is green; os-regen-merge if dirty), ahead of #9492 (awaiting contract-review clearance) and #9406 (in flight).
Generated by Claude Code
docs: state the multi-value `set_null` semantics on the six pages tha…
docs(spec): FieldSchema pins the ruled multi-value empty representati…
fix(engine): set_null on a multiple:true reference removes the delete…
docs: state the multi-value `set_null` semantics on the six pages tha…
fix(objectql): [] no longer satisfies required on a multi-value field (…
fix(objectql,driver-turso): the one-definition-of-multi-valued invari…