Repository navigation
Description
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:140
default: 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) => … (o as AnyRec).default === true)) {
return false; // 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
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. - Retire it (ADR-0049 enforce-or-remove;
spec-property-retirementplaybook) — drop the key, and with it theisNullableFieldbranch 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
Activity
yinlianghui commentedon Aug 10, 2026
Delegated ruling (objectui seat PM, session
session_017Qqyix2QcnpUC9XeYVDzx3): Retire — with execution explicitly deferred past the v17 release window.Authorization: maintainer directive (2026-08-10, this seat's session) — decision-inbox items delegated to this seat, to be ruled from the product-long-term-stability angle. Veto window: open until anyone starts executing; a maintainer word reverses this at zero cost.
Ruling and basis:
SelectOption.defaultunder ADR-0049 enforce-or-remove (route via thespec-property-retirementplaybook), removing with it theisNullableFieldbranch that trusts the key. The decisive question the card poses — does a real business scenario pull on option-leveldefaultgivendefaultValuealready covers it — has, on the evidence in the card itself, the answer "no": the engine has never honored it, so there is no working usage anywhere to preserve; the one real-world sighting (the driver-memory / driver-mongodb:Field.datetime无单一存储形态,跨类型比较恒 false —— #3912 的非 SQL 版 #4047 reporter writing both spellings on one field) is evidence of the confusion the double vocabulary causes, not of demand. Two spellings for one concept is a permanent ambiguity tax on every future consumer (forms, DDL, import, and especially AI authors who read the schema as the contract); enforcing would buy that tax plus a precedence rule plus DDL review to make real a capabilitydefaultValuealready provides. The third option (narrow the lint, keep the key declared) keeps a key that looks meaningful and does nothing — the exact ADR-0078 shape this repo eradicates.defaultValue), per the playbook's ledger discipline.Until the retirement lands, objectui's stance stays as PR #4068 shipped it:
defaultValueis the only honored spelling — which this ruling makes permanent rather than provisional.Generated by Claude Code
os-zhuang commentedon Aug 10, 2026
Maintainer ruling (2026-08-10, directed in session
session_01BPWqbmEFU8gJepBJTHESXd): enforce.applyFieldDefaultsfalls back to the option markeddefault: truewhen the field declares nodefaultValue;defaultValuewins when both are declared (the more specific declaration). This makes the lint'sisNullableFieldheuristic honest and the select idiom real — the ui#4047 reporter writing both spellings on one field is evidence of genuine author expectation. Include the driver-DDL look the card asks for, and a precedence pin test. objectui note: ui#4068'sdefaultValue-only seeding stays correct — after this lands the server honors option-defaults on omitted fields, so the console needs no second default contract.needs-user-decision→pm:queue.Generated by Claude Code
os-zhuang commentedon Aug 10, 2026
CLAIM — spec-lane PM seat (#6017), session
session_01PiRUoQkTSBBmpyXBY3cVn2. Branch:claude/issue-7246-enforce-select-option-default. Dispatching a local dev agent (model: Opus — the ruling fully specifies direction; mechanics are engine fallback + precedence + DDL review).Executing the maintainer's 2026-08-10 ruling (comment 5237213604): enforce.
applyFieldDefaultsfalls back to the option markeddefault: truewhen the field declares nodefaultValue;defaultValuewins when both are declared; include the driver-DDL look the card asks for; add a precedence pin test. Note the ruling's objectui rider: ui#4068'sdefaultValue-only seeding stays correct — no console change needed.Sequencing note on record: the maintainer ruling (07:34Z) post-dates and supersedes the objectui seat's delegated retire-ruling (06:50Z) — that delegation declared its own veto condition ("a maintainer word reverses this at zero cost"), and the maintainer's word arrived. Enforce is the operative direction.
Premise-first guards: re-verify on current
mainthatapplyFieldDefaults(packages/objectql/src/engine.ts) still resolvesf.defaultValueonly and never readsoptions; thatisNullableField(packages/lint/src/validate-expressions.ts) still trustsdefault: true. Design points the dev must decide explicitly in the PR (with reasoning, not silently): (a) multi-select fields — multiple options carryingdefault: true(array assembly vs refuse-in-lint); (b) whether the SQL DDL emits a physical column DEFAULT for the option-default (thedefaultValueliteral path does — decide symmetry and state why; the #4560 discipline on engine-resolved tokens does not apply here since an option value is a plain literal); (c)''presence semantics must match the engine'sdv == nulltest, not the action-param one. Behavior change is additive (fields that stored null now store the marked option) — changeset level judged from precedent, blast radius measured over the shipped corpus (any object declaring option-default: truewithoutdefaultValuechanges insert behavior — census it and list them in the PR).Generated by Claude Code
fix(test): re-pin the two suites this change legitimately moves (#7246)
feat(objectql): enforce the select idiom — the option marked `default…
docs(spec): nine migration-step rationale passages and two registry c…