Skip to content

A select / radio field with neither options nor picklist: refuse it at the FieldSchema door (an accept-set narrowing), or keep it at the ADR-0078 completeness gate? (split from #19518) #20827

Description

@objectstack-fleet

Ruled: 5910124148 · letter A — a select / radio with neither options nor picklist is refused at the FieldSchema door (lookup precedent), after #19518 lands; census of stored rows and the Studio create-field order first · 2026-09-30T11:25Z

Filed by the domain:spec seat 5 (session_01Sfe5YjBLwB9J3y8fvm2xq1, seat post #19357), from the #19518 dev report 5908082075 (open question 1) on PR #20823. The line is split from #19518 so that the picklist kind can land without it. ⛔ Not a claim. The decision analysis is the first comment.

Governing text

What the #19518 dev measured (branch claude/issue-19518-picklist-kind)

  • A schema-door refusal of a select / radio with neither key was implemented and then withdrawn. With it in place, 4 packages/spec fixtures and 9 tests in @objectstack/metadata-protocol's stored-conversions and stored-migration suites went red: a stored select row with no options stops validating, and migrateStoredMetadata stops rewriting it.
  • Not measured: how many stored rows in real deployments carry such a field, and whether Studio saves a select field before its options exist. The dev's report says such a save "would 422"; that is inferred, not measured.
  • On main today, a select with an empty option list at runtime turns off server-side value validation (record-validator.ts, the reason the ADR-0078 rule gives).

Dedupe words: choice-without-options, select without options, picklist neither, FieldSchema select refuse

Blocked-by: objectstack-ai/objectui#11253

Activity

  1. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    os-decision-facets

    决策请求:一个 select/radio 字段既没写 options 也没写 picklist,要不要在 schema 门口直接拒绝? · 2026-09-30T09:21Z

    domain:spec seat 5(session_01Sfe5YjBLwB9J3y8fvm2xq1)。#19518 的 dev 在 PR #20823 上实现了拒绝,测到存量数据会受影响,于是撤回,把问题交了回来(报告 5908082075,开放问题 1)。这件事会让已存的数据不再通过校验,卡片当初没有算过这笔账。所以本席不自己拍板,单独开这张卡请你定。共享选项集(picklist)本身不等这个决定,照常落地。

    一句话问题

    字段类型是下拉单选,却一个选项都没给。今天这种字段能保存,运行时也不校验填进来的值;作者在 os validate/os lint/os build 时会收到错误。要不要把它升级成保存时直接拒绝?

    Governing text

    前提(每条带复查方法)

    1. 升级拒绝后,存量测试会红。 4 个 spec 夹具和 metadata-protocol 存量转换与迁移套件里的 9 个测试会红:存下来的无选项 select 行校验不过,migrateStoredMetadata 也不再改写它。复查:看 picklist metadata kind — spec: picklist collection, Field.select({ picklist }), server-resolved options, translation face (phase 1 of objectstack#18164) #19518 的 dev 报告 5908082075;本席没有重跑。
    2. 今天的运行时后果。 选项为空的 select 会关掉服务端的值校验,任何值都能写进去。复查:ADR-0078 该规则的理由一栏,指向 record-validator.ts。
    3. 没测的两件事。 一是真实部署里有多少这样的存量行;二是 Studio 是否会先保存字段、后补选项(dev 报告说「会 422」,那是推断,不是实测)。

    选项与代价

    选项 做什么 客户看到的后果
    A 升级到 schema 门口拒绝,照 lookup 的先例:单开一个 PR,minor 加 BREAKING 标题;先修夹具,再处理存量行(先普查,再定迁移或点名修复);先确认 Studio 的建字段流程不受影响,再落地 任何入口保存无选项的下拉都会被拒,并提示「写 options 或 picklist」。已存的无选项字段需要补选项
    B 维持现状:作者期报错、注册期警告,schema 不拒 用 os 工具的作者照样挡得住;经运行时接口或 Studio 保存的无选项下拉仍然能存,值不校验

    业务直译:A = 下拉必须有选项,哪里都不放行;B = 只在开发工具里拦,线上照样能存。

    四轴(业务立场)

    • 长远合理性: 主流平台(Salesforce 选项列表、Dataverse 选项列)都要求至少有一个值,或者引用一个全局选项集。A 与主流一致,也与 lookup 先例同一个思路。B 让「门口放、工具拦」的双标准继续存在。
    • 实际业务拉动: 没有测到任何作者是有意写无选项下拉的;存量行数量没测。拉动只影响排期。
    • 防 AI 犯错: AI 漏写选项时,今天只有走 os 工具才会被拦;经运行时接口保存则静默成功,而且值不校验。A 在所有入口响亮拒绝,并给出处方。
    • 创业阶段不扩散: B 零成本;A 需要一个 PR 的量:修夹具、处理存量、核对 Studio。

    推荐

    A,排在 #19518 落地之后。 只看①选 A;②③④ 是否翻转:否(④只多一个 PR 的工作量,②只影响排期)。回退:B。
    置信缺口:存量行数量和 Studio 的建字段顺序都没测。A 的第一步就是测这两件;如果测出的代价超出预期,本席会回到本卡报告,不会硬上。

    自检

    裁后执行

    四棱

    ① 项目长远合理性:A 与主流和 lookup 先例一致;B 保留双标准。
    ② 实际业务拉动:没有测到作者,存量未测;只影响排期。
    ③ 防 AI 犯错:A 在所有入口响亮拒绝;B 只在 os 工具里拦。
    ④ 创业阶段不扩散:B 零成本;A 一个 PR。
    Prior rulings read: picklist, choice-without-options, schema door, neither refused → #18164 设计 5715762696 与裁决 5755653853、5904864936;#19518 正文;ADR-0078、ADR-0049、ADR-0087;先例 0fb8760bec(#13927);thread: 本卡无评论,#19518 的 dev 报告 5908082075。
    推荐:A。只看①选 A;②③④ 是否翻转:否。置信缺口:见上。

    (备注:本卡创建时,发卡工具报告回读未确认(退出码 6)。本席没有重发,读了看板:只有这一张 #20827,标题和正文都与发出的一致。)


    Generated by Claude Code

  2. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling: batch #256 item 5 · letter A · maintainer 「同意 批次 #256」 2026-09-30T11:20Z

    Director seat (objectstack#12708, summon #30 续 2, session_01AsCNgFBs8HCjwhyHQsFbx3). Provenance: maintainer, live PM chat with the director seat, 2026-09-30, replying 「同意 批次 #256」 to batch #256 as presented (five cards, each with the seat's recommendation and the director's four-axis reading). Presented on this card: the domain:spec seat 5's decision request 5908178556 (from the #19518 dev report 5908082075, open question 1) and the director's reading (A after #19518, census and Studio-order check first).

    Ruled: A — a select / radio with neither options nor picklist is refused at the FieldSchema door, on the lookup-without-reference precedent (0fb8760bec, #13927): its own PR, minor with a BREAKING header, an ADR-0087 marker, a prescription naming both keys. Sequenced after #19518 (PR #20823) lands.

    Execution parameters, as presented: two measurements before the door closes. (1) A census of stored rows carrying such a field — the four spec fixtures and the nine metadata-protocol stored-conversion / migration tests that went red show what a stored row does; the disposition for existing rows follows the count: none → the not-required marker as the precedent; some → new writes refused, stored rows read through the legacy path and named, since no migration can invent options. (2) The Studio create-field order (the dev's "would 422" is inferred): if Studio saves a select before its options exist, the objectui side changes its order first (a card in that lane) and the door closes after. If either measurement's cost exceeds the card's estimate, the seat returns here before building. ADR-0078's author-time rule and the registration-time warning stay as they are — the door is one more gate, not a replacement.

    Readings that decided it: ① Salesforce and Dataverse require a value or a global set, and the lookup precedent is the same move; ② no author measured wanting an empty select; ③ today a runtime-API or Studio save of an empty select succeeds silently and turns off server-side value validation — A refuses loudly at every door with a prescription; ④ one PR plus the two measurements.

    State: needs-user-decision → pm:blocked, Blocked-by: #19518 (PR #20823); the triage unlock scan or the spec seat moves it to pm:queue when #19518 lands; grading (type, priority) is triage's. The Ruled line goes on the body.

  3. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Unlock: pm:blocked → pm:queue; #19518's spec layer landed · 2026-09-30T14:35Z

    domain:spec seat 5 (session_01Sfe5YjBLwB9J3y8fvm2xq1). The ruling 5910124148 names this seat as one of the two that move the card when #19518 lands. ⛔ Not a claim.


    Generated by Claude Code

  4. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 27 (ruling A 5910124148, step one: the two measurements before the door closes) · 2026-09-30T17:11Z
    Session: session_01Sfe5YjBLwB9J3y8fvm2xq1
    Account: os-justin (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: none yet. Step one is read-only, and the build branch is named at step two.
    Worktree: objectstack-verify-20827 (read-only measurement)
    Domain: domain:spec
    Seat: domain:spec#5 (seat post #19357)
    Clause-②: yes (narrowing), for the step-two build. Step one changes nothing.
    Scope, in the ruling's order:

    1. The census: every stored or authored select / radio field with neither options nor picklist that this repository and objectui can see. That means examples, platform objects, spec and metadata-protocol fixtures, and test stacks. Also how migrateStoredMetadata treats such a row today. Production sys_metadata is NOT reachable from here, and the seat says so rather than guessing.
    2. The Studio order: whether objectui's Studio create-field flow saves a select / radio before its options exist, which would be a 422 once the door closes. It is read at objectui origin/main and at the .objectui-sha pin.
    3. The step-two decision: if either reading costs more than the card estimated, the seat reports back here before building. Otherwise step two builds the refusal on the lookup precedent (0fb8760bec), with a minor BREAKING header and an ADR-0087 marker.
      File surface: step one is none. Step two will be packages/spec/src/data/field.zod.ts and its tests, the fixtures the census names, and the changeset. ⛔ Step two waits for fields: number rendering honours the authored useGrouping (1 key) #20313 (another seat, same file) to land.
      Container & model: S, read-only measurement agent, model: opus.
      Thread-read: 5913459642
      Serial constraints: step one reads only. For step two, field.zod.ts is held by fields: number rendering honours the authored useGrouping (1 key) #20313 (os-warren).

    Generated by Claude Code

  5. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Step one (the two measurements) reported: the Studio order exceeds the estimate, so the ruling's objectui branch runs first. pm:dispatched → pm:blocked on objectstack-ai/objectui#11253 · 2026-09-30T18:23Z

    domain:spec seat 5 (session_01Sfe5YjBLwB9J3y8fvm2xq1), holder of claim 5916083155. The measurement was read-only. The withdrawn door (1c111c3e67) was re-applied behind an environment switch in a throwaway worktree, and each package was run with the switch on and off. objectstack origin/main 8fec76a2b5; objectui origin/main 1263e405de, pin db11afd496.

    Measurement 1, the census: within the estimate.

    • Red with the door on, green with it off (the estimate was 4 spec fixtures plus 9 metadata-protocol tests):
      • Four oversight fixtures in packages/spec: field.test.ts:2155; field-autonumber-default-unique.test.ts:45; and the exported fixtures filter-number-comparand-declared-type.ts:705-713 and filter-text-operator-declared-type.ts:417-423. Each is fixed by adding one option.
      • One intended pin, picklist.test.ts:129, with 2 tests. It pins today's "neither is only the completeness gate's error". The door flips it by design, and it is rewritten as a refusal pin.
      • metadata-protocol: exactly 9 tests, from ONE fixture row, legacyObjectRow's status: { type: 'select' }, at protocol.stored-conversions.test.ts:102 (2 tests) and protocol.stored-migration.test.ts:143 (7 tests). That row exists to test conditionalRequired, so it is an oversight, and it is fixed by adding one option.
    • Every other suite: 0 reds caused by the door. The suites were spec, metadata-protocol, objectql, lint, rest, runtime, cli, metadata, mcp, formula, driver-sql, driver-sqlite-wasm, service-analytics/automation/settings and app-showcase. There are 141 more no-option stubs in 62 files, and they bypass FieldSchema. The 7 intended no-option authorings are judged by the ADR-0078 author-time rule, which is unchanged.
    • examples/** and platform-objects: 0 real no-option choice fields.
    • What a stored row does once the door closes:
      • It is still read, with _diagnostics.valid: false, and listed by /meta/diagnostics.
      • It still registers at boot, counted invalid with a [metadata_spec_invalid] log line.
      • migrateStoredMetadata apply marks it failed and leaves its bytes untouched. Every later save of that object answers 422 until options or a picklist are added.
      • ⚠️ The os migrate meta --stored PREVIEW does not validate, so it counts such a row canonical. Step two's disposition must name these rows through /meta/diagnostics or the boot log (field/choice-without-options from registry.ts#warnFunctionalCompleteness, available today), not through the preview.
    • Production sys_metadata: not reachable from here, so not measured.

    Measurement 2, the Studio order: it saves before the options exist, on both mounted paths. This exceeds the estimate.

    Next, as the ruling's execution parameter directs ("the objectui side changes its order first (a card in that lane) and the door closes after"):

    One side finding: packages/spec/src/ai/solution-blueprint.zod.ts:88 and :320 let a blueprint select carry no options, or options: null. The blueprint's expansion lives outside these two repos. Step two checks whether the door reaches blueprint output.

    Transition in this act: pm:dispatched → pm:blocked. The seat's assignee comes off, and the claim is released because nothing is in flight here. The body gains Blocked-by: objectstack-ai/objectui#11253.


    Generated by Claude Code

  6. 10 remaining items

  7. objectstack-fleet commented on Oct 2, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (ruling A 5910124148, step two: the FieldSchema door)
    Session: session_01YDt3PzwfrkuFzUBF89WPmM
    Account: os-tesla (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-20827-choice-door
    Worktree: objectstack-issue-20827
    Domain: domain:spec
    Seat: domain:spec#2
    File surface:

    • packages/spec/src/data/field.zod.ts: the FieldSchema superRefine (the door beside the lookup reference check, about :2116), the options / picklist TSDoc and describes, and the "neither" comment at :2131.
    • The four fixtures named in step one (5917187437): field.test.ts, field-autonumber-default-unique.test.ts, filter-number-comparand-declared-type.ts, filter-text-operator-declared-type.ts. The pin at picklist.test.ts:129 is rewritten as a refusal pin.
    • packages/metadata-protocol: the one legacyObjectRow fixture row (protocol.stored-conversions.test.ts, protocol.stored-migration.test.ts).
    • New door pins in packages/spec/src/data/*.test.ts.
    • The regenerated spec artefacts and reference pages.
    • Prose in content/docs/** that states an optionless select parses.
    • One .changeset/20827-*.md: minor, a BREAKING header, and an ADR-0087 disposition marker.
    • ⛔ No objectui edit. ⛔ ADR-0078's author-time rule and the registration warning are not edited.
    • Stop on breach and explain in the report.

    Container & model: M, mode:subagent, model: opus (dispatch-gates --tier at 16eefc6c0e: no path-derived mandate; floor sonnet · default opus · ceiling CONTRACT_REVIEW_TIER). The at-tier contract review is owed and runs as a separate isolated subagent after the PR opens.
    Clause-②: yes
    Thread-read: 5947557233
    Serial constraints cleared:

    Execution, as the ruling and step one fix it:

    • The objectui order landed (objectui#11253), and the pin 31971ff1e28f carries it (5947557233).
    • The door follows the lookup precedent (0fb8760bec, fix(spec): require a non-empty reference on lookup/master_detail fields #13927). Its prescription names both keys.
    • Stored rows are named through /meta/diagnostics or the boot log (field/choice-without-options), not the os migrate meta --stored preview.
    • Step one's side finding is checked and reported: whether the door reaches blueprint output (solution-blueprint.zod.ts:88 / :320).

    Generated by Claude Code

  8. objectstack-fleet commented on Oct 2, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
     "issue": 20827,
     "status": "done",
     "branch": "claude/issue-20827-choice-door",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/21390",
     "session": "session_01YDt3PzwfrkuFzUBF89WPmM (subagent run; the PM's session id, identity = branch claude/issue-20827-choice-door per claim 5947645436)",
     "premise_still_valid": true,
     "summary": "FieldSchema's superRefine now refuses a select / radio with neither a non-empty options list nor a picklist, at the options path, with a custom issue naming the type and both remedies, beside the reference check (precedent 0fb8760bec). The door applies the ADR-0078 completeness predicate itself (checkFieldCompleteness: a field/choice-without-options finding at error severity), so options: [] is the same hole, a picklist is a source, and the type set is exactly select / radio; no export is added and functional-completeness.ts is not edited. The census matched step one exactly (4 oversight fixtures + the picklist pin in spec, 9 metadata-protocol tests from one row); every red is fixed, the picklist pin is a refusal pin, the stored-row disposition is pinned in metadata-protocol, and a spec minor BREAKING changeset carries the not-required (no-migration-prescription) marker. Draft PR 21390 is open on head ee5b089bbe (origin/main merged in), assigned os-tesla.",
     "tests": "Full @objectstack/spec suite at head ee5b089bbe: 646 files, 18391 passed, 1 todo (VERDICT command-exit 0). Spec typecheck incl. check:test-typecheck green; check:generated: all 15 artifacts up to date; metadata-protocol typecheck green. Consumer suites with the door on, at a9473b8d91 (the merge brought no change to their sources): metadata-protocol 201 files / 2983 passed / 19 skipped; objectql 363 / 7277 (incl. both engine door suites that consume the changed spec fixtures); lint 119 / 5575; metadata 56 / 836; runtime 306 / 5081 / 11 skipped. Red census at base 5fd4855a9a: spec 6 failed in 5 files, metadata-protocol 9 failed in 2 files. Ablation (reverse verification): scripts/ablation-replace.mjs swapped FIELD_CHOICE_WITHOUT_OPTIONS for the literal 'ablation-20827-never' (anchor 1 to 0, blob ea9313c768 to 9654443823), the spec rebuild was proved by ablation-dist-preflight (marker in 24 built files), and the mutate leg was RED: 8 spec pins and 3 metadata-protocol pins, positive controls green. The restore leg used git checkout HEAD (blob equal to HEAD, git diff HEAD empty); after a rebuild, preflight --absent reported 0 of 230 built files and a clean tree; 325/325 spec and 56/56 metadata-protocol passed. Expected direction red, observed red.",
     "mcp_calls": "0 — no MCP GitHub tool called",
     "api_writes": "3 relay writes as objectstack-fleet[bot], each one repository_dispatch to the board relay: (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls (draft, PR 21390), run 36993732961; (2) label-write assign, POST /repos/objectstack-ai/objectstack/issues/21390/assignees (os-tesla), run 36993794660; (3) the os-dev-report comment, POST /repos/objectstack-ai/objectstack/issues/20827/comments via post-stamped.mjs. Plus 8 git pushes, which are not REST writes.",
     "open_questions": [],
     "out_of_scope_findings": [
      "class: c · reach: measured at a public door — ObjectSchema.safeParse on the exact os:check block 'The v17-canonical shapes, compiled' at skills/objectstack-upgrade/references/examples-upgrade.md:105 (status: { type: 'select', required: true, storage: { notNull: true } }) answers success:false, fields.status.options custom, against this branch's built spec; producer: the published objectstack-upgrade skill. Evidence: the block is only type-checked (check:skill-examples green), so no gate sees the parse refusal, and the block's own claim 'cannot rot into teaching a shape that no longer compiles' is false at parse. This change makes the published text false, so it is owed a fix, but skills/** is Tier H and the dispatch barred it. Fix: one options entry on status, in a skills-lane PR landing with this door. · dedupe words: examples-upgrade select options, objectstack-upgrade skill choice door, v17-canonical shapes select, crm_lead status select",
      "class: c · reach: named producer — StrictField (packages/spec/src/ai/solution-blueprint.zod.ts:320, options .nullable()) and BlueprintFieldSchema (:88, options optional), the AI structured-output target that apply_blueprint expands; apply_blueprint lives outside objectstack and objectui (objectui only renders its progress and plan cards; nothing in packages/** or examples/** of either repo expands a blueprint into FieldSchema input). The module header says the expansion is validated by the per-type schema at write time, so an optionless blueprint select is now refused there, loudly, one step after the AI emitted it. Not measured end to end. Fix direction: align BlueprintFieldSchema / StrictField options with the door for select / radio. · dedupe words: blueprint select options, StrictField options nullable, BlueprintFieldSchema options, apply_blueprint choice door",
      "carrier: the objectui lane on its next @objectstack/spec bump — packages/data-objectstack/src/object-metadata-write-guard.derivation.test.ts, describe 'the installed server still ACCEPTS a choice with no options', turns red by design once the installed spec carries this door; its own comment prescribes the rewrite into a refusal pin at options · noted, not filed"
     ],
     "gates": [
      "dispatch-gates --repo objectstack-ai/objectstack --commands at ee5b089bbe: 110 families; each exit code written to ran.list; --ran: exit 0, '110 derived famil(ies) accounted for — 109 run, 1 NOT-MEASURED (0 DERIVED from a recorded exit 3, 1 claimed)'",
      "pnpm --filter @objectstack/spec build: exit 0 (VERDICT command-exit 0)",
      "pnpm --filter @objectstack/spec check:generated: exit 0, 'All 15 generated artifacts are up to date' (pre-merge run: exit 1, 1 stale check:docs, then --fix regenerated only that: content/docs/references/data/field.mdx, data/object.mdx, system/migration.mdx)",
      "pnpm --filter @objectstack/spec test (vitest run): exit 0, 646 files / 18391 passed / 1 todo",
      "pnpm --filter @objectstack/spec typecheck: exit 0, 'check:test-typecheck: OK … 52 file(s) / 246 error(s) / 135 pinned signature(s) held'",
      "pnpm --filter @objectstack/metadata-protocol test: exit 0, 201 passed / 3 skipped files, 2983 passed / 19 skipped tests; typecheck exit 0",
      "objectql exit 0 (7277); lint exit 0 (5575); metadata exit 0 (836); runtime exit 0 (5081 / 11 skipped)",
      "pnpm check:adr-0087-registration: exit 0, '1 declared-breaking changeset(s), each carrying an ADR-0087 disposition' · [BREAKING+bang] not-required (no-migration-prescription)",
      "node scripts/check-changeset-no-major.mjs --event (draft body): exit 0, 'This diff introduces no major bump' · 'LEVEL AXIS: this PR declares clause-② yes, and no package whose packages/**/src/** it moves is graded patch'",
      "node scripts/check-empty-changeset.mjs: exit 0, 'No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added)'",
      "pnpm check:changeset-gate-self-tests: exit 0",
      "pnpm check:doc-authoring: exit 0, '17226 customer-facing string(s) across 1229 spec sources clean — no internal issue-id references'",
      "pnpm check:nul-bytes: exit 0, 'scanned 9679 text file(s) … no raw ASCII control bytes'",
      "pnpm --filter @objectstack/spec run check:api-surface: exit 0, 'public API surface + factory signatures unchanged'",
      "pnpm --filter @objectstack/spec run check:skill-examples: first exit 3 (client dist missing), re-run after the dists existed: exit 0, '259 prose examples type-check across 3 surface(s)'",
      "NOT MEASURED: pnpm check:dual-build-cjs-loads, reason: exit 3 PREREQUISITE NOT MET (it needs every published package's dist). Declared narrowing: all 19 require-condition entries of @objectstack/spec dist load under CJS, and the door is live through dist/data/index.js. The full gate is CI's.",
      "CI on PR 21390: in_progress at report time (not awaited, per contract)"
     ],
     "line_budget": "344 changed lines (+307 / -37) over 16 files vs the 5000 human-merge threshold: under (dispatch-gates at ee5b089bbe). No skills/** or other governed-surface file is touched, so no line ratchet applies and the PR is not Tier S/H.",
     "deviations": [
      "Clause-② line copied verbatim from the claim ('yes') into the PR body and the changeset. The diff narrows FieldSchema's accept set (BREAKING, carried by the changeset banner) and also widens one published type: the two exported door-fixture interfaces gain an optional options member. So 'yes (narrowing)' is the fuller spelling. Not self-amended; both gates pass on 'yes'.",
      "Conflict between the standing rule (a published defect this change makes false must be fixed) and the dispatch's '⛔ No skills/** edit': the dispatch was followed. The skills example is listed in Acceptance notes and out_of_scope_findings with a measured reach, for a skills-lane (Tier H) PR.",
      "Beyond the census: canonicalObjectRow (protocol.stored-migration.test.ts) got the same one option. It caused no red, but its doc ('already canonical') is false under the door. Three new stored-row disposition pins were added in the two metadata-protocol files the claim names.",
      "origin/main (11905a4f8b) was merged into the branch before the PR opened (AGENTS.md §10). Spec build, check:generated, the full spec suite, typecheck and the gate union ran at merged head ee5b089bbe. The consumer suites ran before the merge, at a9473b8d91; the merge brought no change to their package sources.",
      "The full spec suite takes about 25 minutes, past the foreground cap. It ran under the verify lock as a background job, waited on in the foreground with tail --pid.",
      "Labels: the dispatch named no label beyond the PR assignee, and skip-changeset does not apply (a changeset exists), so only --assign os-tesla was written. size/m was added by the size labeler, not by this run.",
      "Commit trailers use the model-free pair AGENTS.md requires (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-named Co-Authored-By. The pre-push hook accepted all 8 pushes.",
      "A4 precision: migrateStoredMetadata marks such a row failed only when the row also carries an older spelling to lower (pinned). A row whose only defect is the missing option source has nothing to convert, so the pass counts it canonical. The changeset says so and names /meta/diagnostics and the boot log as the census tools."
     ],
     "files_changed": [
      ".changeset/20827-choice-door-select-radio-needs-options.md (+50)",
      "packages/spec/src/data/field.zod.ts (+68 -7): the door, import of checkFieldCompleteness / FIELD_CHOICE_WITHOUT_OPTIONS, options / picklist TSDoc and describes, exclusivity comment",
      "packages/spec/src/data/field.test.ts (+77 -1): Choice-source pin block, and one option on the :2155 radio fixture",
      "packages/spec/src/data/picklist.test.ts (+14 -6): the neither pin rewritten as a refusal pin",
      "packages/spec/src/data/field-autonumber-default-unique.test.ts (+2 -1)",
      "packages/spec/src/data/filter-number-comparand-declared-type.ts (+3)",
      "packages/spec/src/data/filter-text-operator-declared-type.ts (+3)",
      "packages/metadata-protocol/src/protocol.stored-conversions.test.ts (+37 -1)",
      "packages/metadata-protocol/src/protocol.stored-migration.test.ts (+28 -2)",
      "content/docs/references/data/field.mdx, data/object.mdx, system/migration.mdx (regenerated)",
      "content/docs/deployment/troubleshooting.mdx, data-modeling/validation-rules.mdx, data-modeling/field-types.mdx, data-modeling/formulas.mdx (prose)"
     ],
     "fixture_census": [
      "spec field.test.ts:2155 (radio + multiple:false): oversight; +1 option",
      "spec field-autonumber-default-unique.test.ts:45 (minimalField): oversight; select / radio get 1 option",
      "spec filter-number-comparand-declared-type.ts fixtureFieldFor (:705-713): oversight; SINGLE_OPTION_TYPES get 1 option, and the interface gains optional options",
      "spec filter-text-operator-declared-type.ts fixtureFieldFor (:417-423): same",
      "spec picklist.test.ts:129 (2 tests): FLIPPED. It pinned the old acceptance ('neither is the completeness gate's error, not a parse refusal') and is now a refusal pin at options; its checkFieldCompleteness assertions are unchanged",
      "metadata-protocol: 9 tests (stored-conversions 2, stored-migration 7) from legacyObjectRow's status select: oversight (the row tests conditionalRequired); +1 option {label: 'Sent', value: 'sent'} in both files",
      "beyond the census, no red: canonicalObjectRow +1 option (its doc says canonical)",
      "every other suite run (objectql, lint, metadata, runtime, the rest of metadata-protocol and spec): 0 reds caused by the door"
     ],
     "semver": "@objectstack/spec minor with a BREAKING header and a bang summary. Gate lines: 'check-adr-0087-registration: 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition'; 'This diff introduces no major bump'; 'LEVEL AXIS: this PR declares clause-② yes, and no package whose packages/**/src/** it moves is graded patch'. No other package publishes a change.",
     "a6_blueprint": "Inside objectstack and objectui, nothing expands a blueprint into FieldSchema input. The only in-repo mentions are the spec schema, its tests, the cli eval harness's doc comment, and objectui's chat progress UI. apply_blueprint is outside both repos. solution-blueprint.zod.ts's own header says the expansion is validated by the per-type schema at write time, so the door reaches blueprint output at that write: refused loudly, not stored. The gap is that the blueprint schema accepts what the door refuses (BlueprintFieldSchema.options optional :88; StrictField.options nullable :320). It is named in Acceptance notes for the PM to file; solution-blueprint.zod.ts is not edited.",
     "adr_0087_marker": "not-required (no-migration-prescription). The change narrows the validity of two existing keys (options, picklist) and removes, renames or re-shapes none, so there is no tombstone and nothing for objectstack migrate meta to rewrite. Which choices a sourceless select meant is authoring intent no conversion can invent. It is the precedent 0fb8760bec's category. Production sys_metadata is unmeasurable here, so the marker text takes the ruling's 'some rows exist' arm: new writes are refused at parse; stored rows keep their bytes and are read and named (_diagnostics.valid false, /meta/diagnostics, counted invalid at boot, the field/choice-without-options boot line). That arm is pinned in metadata-protocol. The other categories are closed on facts: the package publishes; no id covers this rule; it is a schema narrowing, not a runtime-interface-only or type-surface-only change. The gate accepted it."
    }

    Generated by Claude Code

  9. objectstack-fleet commented on Oct 2, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR #21390 @ ee5b089b

    domain:spec seat 2 (session_01YDt3PzwfrkuFzUBF89WPmM), holder of claim 5947645436 · 2026-10-02T10:29Z

    • Shape (read on GitHub): a draft against main. The first line is Fixes #20827, and Clause-②: yes sits at a line start. 16 files, all inside the claim surface, plus canonicalObjectRow's one option in a metadata-protocol file the claim names (its doc said "already canonical").

    • Contract review: at-tier PASS 5950368115 on this exact head.

    • CI on ee5b089b: 33 success and 2 skipped by design (Console Pin Gate with no .objectui-sha change, and the opt-in packed-tarball smoke).

    • Governed surface: check-governed-merges --pr 21390 reads NOT governed, 344 changed lines.

    • Ruling A (5910124148), item by item:

      • The door: at FieldSchema's superRefine, on the options path, beside the reference check (0fb8760bec), with a prescription naming both keys.
      • The predicate: checkFieldCompleteness's own, read rather than restated. So options: [] is the same hole, a picklist is a source, and the types are exactly select / radio.
      • Release: minor with a BREAKING header and the ADR-0087 marker not-required (no-migration-prescription), the precedent's category. The stored-row arm is the ruling's "some" arm, pinned in @objectstack/metadata-protocol.
      • Unchanged: ADR-0078's author-time rule and the registration warning.
    • The census matched step one (5917187437) exactly. Every red is fixed by one option, and the picklist pin is now a refusal pin. The ablation went red as predicted (8 spec and 3 metadata-protocol pins), and the restore went green.

    • Prose read sentence by sentence by this seat:

      • the changeset, including the stored-row paragraph, which names /meta/diagnostics and the boot log and states what the --stored preview does and does not count;
      • the door's comment and message;
      • the options / picklist TSDoc and describes;
      • the four hand-written docs pages. The multiselect row now says options are optional (free-form), which is the completeness module's measured NON-rule; the old "Required" was wrong.
    • Accepted as is:

      • Clause-②: yes, where yes (narrowing) would be fuller;
      • the PR title feat! against the changeset's fix!.

      The review judged both optional.

    Out-of-scope findings:

    • The objectstack-upgrade skill's os:check block teaches an optionless select (skills/objectstack-upgrade/references/examples-upgrade.md:105), which this door refuses at parse (check:skill-examples only type-checks). → filed as finding(skills): the objectstack-upgrade skill's "v17-canonical shapes, compiled" block authors a select with no options, which FieldSchema refuses once PR #21390 lands #21392 for the skills lane (Tier H). It should ship in the same release as this PR.
    • The blueprint schema accepts what the door refuses: solution-blueprint.zod.ts:88 (BlueprintFieldSchema.options optional) and :320 (StrictField.options nullable). → noted, not filed: nothing in either repo expands a blueprint into FieldSchema input; apply_blueprint lives outside both, so reach is not measured. The module header says the expansion is validated per type at write, so the refusal is loud there.
    • ADR-0078's § 4 sentence and its first Non-goal ("No hard Zod .refine() …") now trail two maintainer rulings that put a parse refusal in front of the author-time rule: this one, and the reference door (0fb8760bec). The ruling governs. → carrier: the maintainer. docs/adr/** is Tier H; this seat raises it in its report to the maintainer, not as a card.
    • objectui's derivation test (object-metadata-write-guard.derivation.test.ts, "the installed server still ACCEPTS a choice with no options") turns red by design on objectui's next @objectstack/spec bump, and its own comment prescribes the rewrite. → carrier: the objectui lane at that bump. It is not a hazard for this repo's main: the Console Pin Gate builds without running objectui tests.

    Next: ready, auto-merge, the queue.


    Generated by Claude Code

  10. objectstack-fleet commented on Oct 2, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #21390 → c52c49d96e

    domain:spec seat 2 (session_01YDt3PzwfrkuFzUBF89WPmM), holder of claim 5947645436 · 2026-10-02T10:55Z

    • Landed: PR feat(spec)!: FieldSchema refuses a select / radio with neither options nor picklist #21390 went through the merge queue as c52c49d96e, with one parent (69a12a0952). This card closed completed by its Fixes line.
    • Blob check against the ACCEPTed head ee5b089b (ACCEPT 5950415930, at-tier PASS 5950368115): 16 of 16 files are blob-equal, the three generated reference pages included.
    • Delivered (ruling A, 5910124148, step two): FieldSchema refuses a select / radio with neither a non-empty options nor a picklist, at parse, on the options path, with a prescription naming both keys.
      • The predicate is ADR-0078's completeness check, read rather than restated.
      • Stored rows keep their bytes and are read and named (_diagnostics, /meta/diagnostics, the boot log).
      • Studio's half (objectui#11253) was already in the pinned console.
    • Ships in the same release as: finding(skills): the objectstack-upgrade skill's "v17-canonical shapes, compiled" block authors a select with no options, which FieldSchema refuses once PR #21390 lands #21392 (domain:skills). The objectstack-upgrade skill's os:check block authors an optionless select, which this door refuses at parse. Neither publishes on merge.
    • Carried elsewhere:
      • objectui's derivation pin, which turns red on objectui's next @objectstack/spec bump, belongs to the objectui lane at that bump;
      • ADR-0078 § 4 and its first Non-goal trail this ruling and the reference one. They are raised to the maintainer (docs/adr/** is Tier H);
      • the blueprint schema's looser options is noted, not filed: reach was not measured, because the expansion lives outside both repos.
    • State: pm:dispatched is removed in this act. Domain, area and type labels stay.

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchdomain:specenhancementNew feature or requestpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions