Found while implementing objectstack-ai/objectui#4047 (objectstack-ai/objectui#4068), which had to decide which of two default spellings a create form honours. Filed unassigned; observation-class, severity for triage.
The finding
SelectOptionSchema declares an authorable default:
// packages/spec/src/data/field.zod.ts:140default: z.boolean().optional().describe('Is default option'),so this is spec-valid and reads like a declaration of the initial value:
status: Field.select({label: 'Status',options: [{label: 'Draft',value: 'draft',default: true},{label: 'Approved',value: 'approved'},],}),Nothing applies it. The insert path — ObjectQL.applyFieldDefaults, packages/objectql/src/engine.ts — resolves f.defaultValue and nothing else: Expression envelopes, then the DEFAULT_VALUE_TOKENS (current_user, NOW()), then static literals. It never looks at options. So a create that omits the field stores null, not draft.
The only consumer of the key found anywhere in the repo is a nullability analysis:
// packages/lint/src/validate-expressions.ts — isNullableField()if(Array.isArray(options)&&options.some((o)=>…(oasAnyRec).default===true)){returnfalse;// treated as always-valued}That comment calls it "the select idiom", and the verdict it feeds is build-breaking. So the one place that reads the key concludes the column is always valued — on the strength of a declaration the engine does not honour. A field defaulted only that way is in fact nullable, and a predicate reading it can be silenced by this heuristic.
Why it surfaced now
objectstack-ai/objectui#4047's reporter wrote both spellings on the same field — defaultValue: 'draft'and the option's default: true — which is what an author does when it is not clear which one is load-bearing. objectstack-ai/objectui#4068 deliberately honours defaultValue only, on the grounds that it is the key the server enforces: a console that also seeded from option-level default would preselect values the server would never have applied, i.e. a UI-only second default contract. That reasoning holds only as long as this stays true, so it is worth recording either way.
Dispositions, for triage
- Enforce it — have
applyFieldDefaults fall back to the option marked default: true when the field declares no defaultValue. Makes the lint heuristic honest and the idiom real. Needs a precedence rule when both are declared and disagree (defaultValue should win — it is the more specific declaration), and the driver DDL side wants a look. - Retire it (ADR-0049 enforce-or-remove;
spec-property-retirement playbook) — drop the key, and with it the isNullableField branch that trusts it. - Leave it declared and narrow the lint — the least appealing: the key keeps looking meaningful to authors while doing nothing.
No claim is made here about which; the startup-focus question ("does a real business scenario pull on option-level default, given defaultValue already covers it?") is the one that decides it, and that is a maintainer call.
Generated by Claude Code
Found while implementing objectstack-ai/objectui#4047 (objectstack-ai/objectui#4068), which had to decide which of two default spellings a create form honours. Filed unassigned; observation-class, severity for triage.
The finding
SelectOptionSchemadeclares an authorabledefault:so this is spec-valid and reads like a declaration of the initial value:
Nothing applies it. The insert path —
ObjectQL.applyFieldDefaults,packages/objectql/src/engine.ts— resolvesf.defaultValueand nothing else: Expression envelopes, then theDEFAULT_VALUE_TOKENS(current_user,NOW()), then static literals. It never looks atoptions. So a create that omits the field stores null, notdraft.The only consumer of the key found anywhere in the repo is a nullability analysis:
That comment calls it "the select idiom", and the verdict it feeds is build-breaking. So the one place that reads the key concludes the column is always valued — on the strength of a declaration the engine does not honour. A field defaulted only that way is in fact nullable, and a predicate reading it can be silenced by this heuristic.
Why it surfaced now
objectstack-ai/objectui#4047's reporter wrote both spellings on the same field —
defaultValue: 'draft'and the option'sdefault: true— which is what an author does when it is not clear which one is load-bearing. objectstack-ai/objectui#4068 deliberately honoursdefaultValueonly, on the grounds that it is the key the server enforces: a console that also seeded from option-leveldefaultwould preselect values the server would never have applied, i.e. a UI-only second default contract. That reasoning holds only as long as this stays true, so it is worth recording either way.Dispositions, for triage
applyFieldDefaultsfall back to the option markeddefault: truewhen the field declares nodefaultValue. Makes the lint heuristic honest and the idiom real. Needs a precedence rule when both are declared and disagree (defaultValueshould win — it is the more specific declaration), and the driver DDL side wants a look.spec-property-retirementplaybook) — drop the key, and with it theisNullableFieldbranch that trusts it.No claim is made here about which; the startup-focus question ("does a real business scenario pull on option-level
default, givendefaultValuealready covers it?") is the one that decides it, and that is a maintainer call.Generated by Claude Code