Repository navigation
[finding] SelectOption.default is an authorable key that nothing on the insert path ever reads — the select idiom is inert #7246
Description
Activity
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:
- Disposition: retire
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. - Not before v17 ships. Removing an authorable spec key mid-rc is the wrong moment: the release window should not absorb a metadata-validation break for zero v17 benefit (the key changes no runtime behavior today, and the lint branch's unsoundness — treating a de-facto nullable column as always-valued — is severity-bounded and pre-existing). Execute in the v18 window as a normal retirement card.
- One rider worth doing at retirement time: the removal card should include the migration note for the two showcase-adjacent spellings observed in the wild (drop the flag, keep/add
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
- Disposition: retire
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
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
- added a commit that references this issue
on Aug 10, 2026 - added a commit that references this issue
on Aug 17, 2026 - added a commit that references this issue
on Oct 7, 2026
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