You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
types: the SDUI form-option face (SelectOption) accepts spec 18's object-lifecycle kind and the object-only default, which the spec's form-view options leave out — accepted and silently ignored #12736
Filing gate ①: the open question of the os-dev report on objectui#12711 (PR #12735). The dev recommended B as its own card; the seat agrees. Filed by the domain:ui seat 1 (session_011K9g23W67dy2zwnyzaoB6o). ⛔ Not a claim; for triage to grade and route.
reach: none measured. No form-face reader of an option's kind exists; this is a declared-but-not-honoured gap on a published type.
Measured (objectui mainc7462e7c81, from the dev's report)
packages/types/src/form.tsSelectOption (the SDUI form-option face) and its zod twin SelectOptionSchema (packages/types/src/zod/form.zod.ts) derive the spec's DATA SelectOptionSchema keys by reference. So with spec 18 (objectstack#23846) they accept kind: 'open' | 'won' | 'lost', and they have long accepted default.
The spec's FORM-VIEW options (FormSelectOptionSchema) leave out both kind and default. objectui references FormSelectOptionSchema nowhere outside tests.
A form option carrying kind (or default) is accepted and silently ignored. Per the four axes, declaration is enforcement: a key the runtime does not honour should be refused, not tolerated.
The fork (for triage to scope)
A: keep as is. objectui#12711's pins state kind as a deliberate widening on both faces, and hold under either choice.
B: narrow the form face and its zod twin to the spec's form-view option boundary (drop kind and default). It is a published-type narrowing with zero measured producers, so it owes a CONTRACT_REVIEW_TIER review, and objectui#12711's set-difference pins would be edited deliberately in the same PR.
Dedupe: objectui issues read for SelectOption form face kind / default / FormSelectOptionSchema: none besides objectui#12711 (the pins) and the convergence history (objectui#7014).
Scope: B, narrowing SelectOption and its zod twin to the spec's form-view option boundary (FormSelectOptionSchema: no kind, no default). A key the form face does not honour is refused, not tolerated, and no producer is measured. The claimant re-greps for producers at the claim, and the changeset states the narrowing.
Why p3: no reach is measured. objectui#12711's pins hold under either choice.
Claim: PM loop round 3 (priority:p3 enhancement, a contract face on a published @object-ui/types type; filed by domain:ui seat 1 from objectui#12711's report, and scoped by the triage seat to B, 6220534234; serial after PR #12735, merged 2d9c81e)
Session: session_01SJn1GEkrbbBQkhGC26wEDw
Account: os-sales (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-12736-form-option-boundary
Worktree: objectui-issue-12736 (in the cloud session's own container)
Domain: domain:ui (packages/types is this repo's domain:spec stream, whose seat is vacant; triage routes it here, as objectui#12666, #12670 and #12711 were)
Seat: domain:ui#4
File surface:
packages/types/src/form.ts: the SDUI form face SelectOption narrows to the spec's form-view option boundary (FormSelectOptionSchema, @objectstack/specui/view.zod.ts). It drops kind and default, which that schema leaves out, and keeps its pinned divergences (value widened, visibleWhen, disabled, icon).
packages/types/src/zod/form.zod.ts: its zod twin SelectOptionSchema, the same narrowing on the runtime side. A form option carrying kind or default is refused, not silently ignored.
packages/types/src/__tests__/: objectui#12711's set-difference pins and the convergence pins edited deliberately, each with the reason written beside it, plus a pin that the form face's key set equals FormSelectOptionSchema's plus the named divergences.
One .changeset/12736-*.md for @object-ui/types, stating the narrowing.
⛔ SelectOptionBase (select-option.ts) and the field and object option faces keep kind and default: the spec takes kind on a select or radio field's options (18.0.0-next.3, objectui#12873). Only the form-view face narrows. No renderer or app-shell change.
Stop on breach; explain in the report.
Container & model: S, mode:cloud, model: opus (cloud on the maintainer's stance 「可以的话,ui 后续开发尽量用云 session,并且要求截图给我看」; lane scope on 「你也可以接ui车道的卡」; batch 3 on 「当前任务处理完,后续并发降到3」, this session's chat, 2026-10-16)
Clause-②: yes
Clause-② reason: a published @object-ui/types type and its zod twin narrow (SelectOption refuses kind and default). That is an accept-set change on a published face, so it owes a CONTRACT_REVIEW_TIER review.
Responsibility: n/a — declaration is enforcement: the form face accepts two keys no form reader honours, and silently ignores them
Thread-read: 6220534234
Serial constraints cleared:
No open objectui PR touches packages/types/src/form.ts, zod/form.zod.ts or select-option.ts, and no pm:dispatched objectui claim names them (read at dispatch, main26323c4).
Producers: the claimant re-greps at the claim, per triage. The dev re-measures for any objectui source or example that writes kind or default on a FORM option, and stops if one exists.
Release: not started · domain:ui seat 4 · session_01SJn1GEkrbbBQkhGC26wEDw · 2026-10-17T14:01Z
This releases claim 6234793729. Cause: the maintainer told this seat to finish its current work and stop after it merges (「当前任务处理完,合并后就下班」), and this card's dev session had not been started. No branch was pushed and no code was written.
Destination:pm:queue, assignee cleared. The claim's scoping stays on record for whoever claims it next:
route B, per triage 6220534234;
the form face reads FormSelectOptionSchema by reference;
SelectOptionBase and the field option faces keep kind and default;
Claim: PM loop round 3 (priority:p3 enhancement, scoped to B by triage 6220534234; re-claimed after seat 4's release 6234814619, whose scoping this claim keeps)
Session: session_01XVp19E1MYUTFM3dnkwz8gB
Account: baozhoutao (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-12736-form-option-boundary
Worktree: objectui-issue-12736
Domain: domain:ui (packages/types is this repo's domain:spec stream, whose seat is vacant; triage routes it here)
Seat: domain:ui#2
File surface:
packages/types/src/form.ts: the SDUI form face SelectOption narrows to the spec's form-view option boundary (FormSelectOptionSchema, @objectstack/specui/view.zod.ts), dropping kind and default and keeping its pinned divergences.
packages/types/src/zod/form.zod.ts: its zod twin SelectOptionSchema, the same narrowing on the runtime side; a form option carrying kind or default is refused.
packages/types/src/__tests__/: objectui#12711's set-difference and convergence pins edited deliberately, each with its reason, plus a pin that the form face's key set equals FormSelectOptionSchema's plus the named divergences.
One .changeset/12736-*.md for @object-ui/types, stating the narrowing.
⛔ SelectOptionBase (select-option.ts) and the field and object option faces keep kind and default. No renderer or app-shell change.
Stop on breach; explain in the report.
Container & model: S, mode:subagent, model: opus (default tier; dispatch-gates --tier --repo objectstack-ai/objectui reads no path-derived mandate); one CONTRACT_REVIEW_TIER review is owed before the queue
Clause-②: no
Clause-② reason: the card only NARROWS a published accept set (@object-ui/typesSelectOption and its zod twin refuse kind and default); a narrowing is Clause-②: no, stays in this lane, and owes one CONTRACT_REVIEW_TIER review before the queue. (Amended in place: first posted as yes.)
Responsibility: n/a — not a defect card (declaration is enforcement: the form face accepts two keys no form reader honours)
Thread-read: 6234814619
Serial constraints cleared: PR #12735 (objectui#12711) merged as 2d9c81e; PR #12887 (objectui#12873, next.3) merged as 26323c4. This seat's in-flight objectui#12111 (packages/fields) and objectui#12838 (packages/plugin-kanban, packages/i18n) are disjoint. The dev re-checks for an open PR on these three files and for any FORM-option producer of kind or default, and stops if one exists.
Claimed at: 2026-10-17T15:07Z
{
"issue": 12736,
"status": "done",
"branch": "claude/issue-12736-form-option-boundary",
"pr": "https://github.com/objectstack-ai/objectui/pull/12904",
"session": "session_01XVp19E1MYUTFM3dnkwz8gB (the harness-stamped Claude-Session id; subagent mode, so it is the parent's)",
"premise_still_valid": true,
"summary": "Implemented Route B. The SDUI form-option type face SelectOption is now Omit of SelectOptionBase over 'value' plus ObjectFieldOnlyOptionKey, which is Exclude of keyof SpecSelectOption, keyof SpecFormSelectOption (from @objectstack/spec/ui), so the keys are read by reference and never listed. The zod twin SelectOptionSchema now spreads stripImportedDefaults(SpecFormSelectOptionSchema).shape instead of the data SelectOptionSchema. It refuses each key in the same set difference (default, kind) with a named tombstone, invalid_type at the key's path, instead of stripping it; the twin is a strip-mode z.object, so leaving the keys out alone would have been silent. SelectOptionBase and SelectOptionMetadata are unchanged. Premise re-grep found zero form-face producers: no 'kind' option literal anywhere outside the edited pins, and every 'default: true' option literal is on the object-field face. A1 derivation before: both faces read @objectstack/spec/data SelectOption/SelectOptionSchema (SelectOptionBase's Omit; the twin's spread). After: both read FormSelectOption/FormSelectOptionSchema, cited by symbol, not by line, per objectui AGENTS.md #11. Changeset 12736-form-option-boundary.md bumps @object-ui/types as minor: this repo's version policy marks its own breaking changes minor (major is reserved for the @objectstack major sync, enforced by check-changeset-no-major), and the text states the narrowing.",
"tests": "At head 7298f167ef. pnpm --filter @object-ui/types type-check exit 0 (three legs, including tsconfig.test.json; --listFilesOnly confirms the new and edited test files are in that project). Consumers that import SelectOption: @object-ui/components (renderers/form/form.tsx). Consumers through FormField: @object-ui/plugin-form and @object-ui/fields. type-check exit 0 for all three, after building @object-ui/types and the plugin-form^... closure under the lock. @object-ui/app-shell also imports FormField; its type-check is declared to CI. pnpm exec vitest run --maxWorkers=1 packages/types/: Test Files 387 passed, Tests 10522 passed. The first full run went red on imported-defaults-8317 (new spec crossing unregistered; raw reads); fixed in 7298f167ef. Targeted run of 7 files (the new pin, spec-parity, tier1-convergence, spec-extension, zod-mirror-parity, disabled-twin-symmetry, schema-catalog component-fixture-declared-keys): 241 passed. Ablation via objectstack scripts/ablation-replace.mjs, with a trap restore. Re-adding 'kind: SpecSelectOptionSchema.shape.kind' after the refusals: anchor x1 to x0, blob 7127370930f3 to 7f3cd87dbd00, result '(a) refuses kind by name' x, 1 failed / 6 passed. Restore: blob 7127370930f3 equals the HEAD blob, git diff HEAD empty. vitest reads src, so no dist rebuild was needed. Gates exit 0: check:phantom-deps, self-import, unreferenced-sources, doc-example-readers, handler-key-reads, side-effects-array, element-data-source-declaration, spec-symbols, action-forward-parity, designer-field-key-parity, icon-record-names, i18n-keys, i18n-drift, i18n-designer-parity, control-bytes, new-line-citations; check-hand-rolled-comment-mask.mjs, check-changeset-fixed.mjs, check-changeset-presence.mjs, check-changeset-no-major.mjs. check:spec-floors exit 1 (no-artifact, workspace not fully built): recorded, not fixed. Lint: eslint --no-inline-config --format json over the 7 changed files read 7 files, 0 errors, 11 warnings. All warnings are pre-existing no-explicit-any on untouched lines. eslint.config.js has no parserOptions.project (not type-aware), so the diff cannot move the verdict on an untouched file. Repo-wide pnpm lint is declared to CI.",
"mcp_calls": "0",
"api_writes": "5. Three git pushes: the empty-branch probe and two commits. One fleet-write relay dispatch, pr_create, which is two REST writes: POST /repos/objectstack-ai/objectui/pulls (draft) and POST /repos/objectstack-ai/objectui/issues/12904/assignees. One relay dispatch for this os-dev-report comment: POST /repos/objectstack-ai/objectui/issues/12736/comments. No label writes: the order named no labels, and objectui path labels belong to labeler.yml.",
"open_questions": [],
"out_of_scope_findings": [],
"deviations": [
"The order asks for A1 evidence as file:line. The evidence is cited by symbol instead, because objectui AGENTS.md #11 bans line addresses in issue and PR bodies, and this report is posted to the card.",
"The ablation vitest run (one file, about 1s) ran outside os-verify-lock; every other build and test ran under it.",
"Extra files in the test surface: packages/types/src/__tests__/zod-mirror-parity.test.ts (ledger row) and imported-defaults-8317.test.ts (IMPORTED row). Both are inside the declared packages/types/src/__tests__/ surface. They were required by the new refusal tombstones and the new spec crossing.",
"PR body line 2 is 'Clause-②: no', following the coordinator's mid-task correction, which overrides the order's 'yes'."
],
"files_changed": [
"packages/types/src/form.ts",
"packages/types/src/zod/form.zod.ts",
"packages/types/src/__tests__/select-option-form-view-boundary-12736.test.ts",
"packages/types/src/__tests__/select-option-spec-parity.test.ts",
"packages/types/src/__tests__/select-option-tier1-convergence-7014.test.ts",
"packages/types/src/__tests__/zod-mirror-parity.test.ts",
"packages/types/src/__tests__/imported-defaults-8317.test.ts",
".changeset/12736-form-option-boundary.md"
],
"gates": "type-check types/components/plugin-form/fields 0; vitest packages/types 0; pre-tsc battery 0 except check:spec-floors 1 (no-artifact, recorded); check-changeset-fixed 0; lint narrowed 0 errors"
}
Reviewed on GitHub at head 7298f16, against the card's fork, triage's scoping B (6220534234), seat 4's released scoping (6234814619) and the claim (6235492982, amended in place: Clause-②: no, because a narrowing does not widen an accept set; one contract-tier review is owed before the queue).
Shape: the body says Fixes #12736; the second line is Clause-②: no. The changeset is @object-ui/types: minor, marked Breaking (types). This repo levels its own breaking changes at minor (major is reserved for the @objectstack major sync).
The change (packages/types):
Type face.SelectOption is now Omit<SelectOptionBase, 'value' | ObjectFieldOnlyOptionKey>, where ObjectFieldOnlyOptionKey is the set difference of the spec's data SelectOption and ui FormSelectOption keys. It is derived, never listed, and is default | kind on spec 18.
Zod twin.SelectOptionSchema spreads FormSelectOptionSchema instead of the data option. It refuses each key in the same derived set by name (a retirementTombstone, invalid_type at the key's path) rather than leaving it out, because a strip-mode z.object would drop it silently.
Untouched.SelectOptionBase and SelectOptionMetadata keep kind and default, as ruled. No renderer or app-shell file changes.
Producers. The dev's re-grep found no FORM-option producer of kind or default. Type-check is green for @object-ui/components, @object-ui/plugin-form and @object-ui/fields, which consume the face; @object-ui/app-shell is declared to CI.
Pins:select-option-form-view-boundary-12736:
(a) the twin refuses kind and default by name, behind an accepting control;
(b) the key set equals FormSelectOptionSchema's plus disabled and icon, on the type and runtime sides, and the derived difference is default | kind;
(c) @ts-expect-error on both keys in a typed literal;
(d) CONTROL: the base, the metadata face and the spec's object-field option still take both.
objectui#12711's pins in select-option-tier1-convergence-7014 and select-option-spec-parity are edited deliberately, each with its reason beside it. A new assertionFormFaceLostOnlyDefault keeps the excusal from hiding a second loss. zod-mirror-parity gains one MirroredUndeclared row (two refusal keys), and imported-defaults-8317 registers the new FormSelectOptionSchema crossing.
Ablation: re-adding kind to the twin turns (a) red; the restore was proven by blob hash. packages/types (387 files, 10522 tests) is green; type-check exits 0.
Trailers: both commits carry only the model-free pair.
Contract review: PASS at this head, CONTRACT_REVIEW_TIER, by an isolated reviewer and adopted by this seat (PR comment 6235882816). No needs:contract-review label is owed: a same-shape PASS is on record at the current head.
CI at head: 33 success, 3 skipped, 0 failures (filter=latest, 36 runs), including Bundle Analysis (performance budget PASS). Not governed (path audit); 484 changed lines.
PR #12904 merged through the merge queue as c999d4c, which is on main; Fixes #12736 closed this card. Verified by content: all 8 of the PR's files on main are blob-identical to the accepted head 7298f16. The merge commit's message names no model.
What landed:
The SDUI form-option face SelectOption and its zod twin SelectOptionSchema stop at the spec's form-view option (FormSelectOptionSchema), read by reference. A form option carrying kind or default is now refused: an excess property on a typed literal, and a named invalid_type refusal at the zod twin.
SelectOptionBase and the object-metadata face keep both keys, unchanged.
The contract review (CONTRACT_REVIEW_TIER) PASSed at the landed head (PR comment 6235882816).
Released as @object-ui/types: minor (Breaking (types), per this repo's own-breaking-is-minor policy).
Activity
objectstack-fleet commentedon Oct 16, 2026
Triage: first grade,
enhancement·priority:p3·domain:ui·pm:queue· scoped to B · triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-16T16:59ZTriage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015). ⛔ Not a claim, ⛔ not a dispatch.
SelectOptionand its zod twin to the spec's form-view option boundary (FormSelectOptionSchema: nokind, nodefault). A key the form face does not honour is refused, not tolerated, and no producer is measured. The claimant re-greps for producers at the claim, and the changeset states the narrowing.packages/typesis this repo'sdomain:specstream, whose seat ([PM seat] domain:spec @ objectui — ⏳ vacant #10217) is vacant, so it goes todomain:ui, as types:layout.zod.tsimportscheckPageComponentViewData, which objectstackmaindeleted (objectstack#23784), so objectstack's console pin no longer builds — drop the import #12666, types: thelist-viewnode face still judges the retiredobjectName/viewTypespelling, soobjectui validaterefuses a ruled page list-view node (follows objectui#12099, ruling C) #12670 and types: spec 18 adds the optional select-optionkind(objectstack#23846), so the tier-1 convergence pins inselect-option-tier1-convergence-7014.test.tsno longer hold — statekindas a deliberate optional widening #12711 did. Land after PR test(types): state spec 18's optional select-optionkindas a deliberate widening in the tier-1 convergence pins #12735 (types: spec 18 adds the optional select-optionkind(objectstack#23846), so the tier-1 convergence pins inselect-option-tier1-convergence-7014.test.tsno longer hold — statekindas a deliberate optional widening #12711), which edits the same faces.objectstack-fleet commentedon Oct 17, 2026
Claim: PM loop round 3 (
priority:p3enhancement, a contract face on a published@object-ui/typestype; filed bydomain:uiseat 1 from objectui#12711's report, and scoped by the triage seat to B,6220534234; serial after PR #12735, merged2d9c81e)Session:
session_01SJn1GEkrbbBQkhGC26wEDwAccount:
os-sales(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-12736-form-option-boundaryWorktree:
objectui-issue-12736(in the cloud session's own container)Domain:
domain:ui(packages/typesis this repo'sdomain:specstream, whose seat is vacant; triage routes it here, as objectui#12666, #12670 and #12711 were)Seat:
domain:ui#4File surface:
packages/types/src/form.ts: the SDUI form faceSelectOptionnarrows to the spec's form-view option boundary (FormSelectOptionSchema,@objectstack/specui/view.zod.ts). It dropskindanddefault, which that schema leaves out, and keeps its pinned divergences (valuewidened,visibleWhen,disabled,icon).packages/types/src/zod/form.zod.ts: its zod twinSelectOptionSchema, the same narrowing on the runtime side. A form option carryingkindordefaultis refused, not silently ignored.packages/types/src/__tests__/: objectui#12711's set-difference pins and the convergence pins edited deliberately, each with the reason written beside it, plus a pin that the form face's key set equalsFormSelectOptionSchema's plus the named divergences..changeset/12736-*.mdfor@object-ui/types, stating the narrowing.SelectOptionBase(select-option.ts) and the field and object option faces keepkindanddefault: the spec takeskindon a select or radio field's options (18.0.0-next.3, objectui#12873). Only the form-view face narrows. No renderer or app-shell change.Stop on breach; explain in the report.
Container & model:
S,mode:cloud,model: opus(cloud on the maintainer's stance 「可以的话,ui 后续开发尽量用云 session,并且要求截图给我看」; lane scope on 「你也可以接ui车道的卡」; batch 3 on 「当前任务处理完,后续并发降到3」, this session's chat, 2026-10-16)Clause-②: yes
Clause-② reason: a published
@object-ui/typestype and its zod twin narrow (SelectOptionrefuseskindanddefault). That is an accept-set change on a published face, so it owes aCONTRACT_REVIEW_TIERreview.Responsibility: n/a — declaration is enforcement: the form face accepts two keys no form reader honours, and silently ignores them
Thread-read: 6220534234
Serial constraints cleared:
kindas a deliberate widening in the tier-1 convergence pins #12735 (objectui#12711), which edits the same faces, merged as2d9c81e.next.3) merged as26323c4. It touchedselect-option.ts's comment and theselect-option-spec-extension-7014pin; this branch is cut after it.packages/types/src/form.ts,zod/form.zod.tsorselect-option.ts, and nopm:dispatchedobjectui claim names them (read at dispatch,main26323c4).kindordefaulton a FORM option, and stops if one exists.objectstack-fleet commentedon Oct 17, 2026
Release: not started ·
domain:uiseat 4 ·session_01SJn1GEkrbbBQkhGC26wEDw· 2026-10-17T14:01ZThis releases claim
6234793729. Cause: the maintainer told this seat to finish its current work and stop after it merges (「当前任务处理完,合并后就下班」), and this card's dev session had not been started. No branch was pushed and no code was written.Destination:
pm:queue, assignee cleared. The claim's scoping stays on record for whoever claims it next:6220534234;FormSelectOptionSchemaby reference;SelectOptionBaseand the field option faces keepkindanddefault;Generated by Claude Code
objectstack-fleet commentedon Oct 17, 2026
Claim: PM loop round 3 (
priority:p3enhancement, scoped to B by triage6220534234; re-claimed after seat 4's release6234814619, whose scoping this claim keeps)Session:
session_01XVp19E1MYUTFM3dnkwz8gBAccount:
baozhoutao(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-12736-form-option-boundaryWorktree:
objectui-issue-12736Domain:
domain:ui(packages/typesis this repo'sdomain:specstream, whose seat is vacant; triage routes it here)Seat:
domain:ui#2File surface:
packages/types/src/form.ts: the SDUI form faceSelectOptionnarrows to the spec's form-view option boundary (FormSelectOptionSchema,@objectstack/specui/view.zod.ts), droppingkindanddefaultand keeping its pinned divergences.packages/types/src/zod/form.zod.ts: its zod twinSelectOptionSchema, the same narrowing on the runtime side; a form option carryingkindordefaultis refused.packages/types/src/__tests__/: objectui#12711's set-difference and convergence pins edited deliberately, each with its reason, plus a pin that the form face's key set equalsFormSelectOptionSchema's plus the named divergences..changeset/12736-*.mdfor@object-ui/types, stating the narrowing.SelectOptionBase(select-option.ts) and the field and object option faces keepkindanddefault. No renderer or app-shell change.Stop on breach; explain in the report.
Container & model:
S,mode:subagent,model: opus(default tier;dispatch-gates --tier --repo objectstack-ai/objectuireads no path-derived mandate); oneCONTRACT_REVIEW_TIERreview is owed before the queueClause-②: no
Clause-② reason: the card only NARROWS a published accept set (
@object-ui/typesSelectOptionand its zod twin refusekindanddefault); a narrowing isClause-②: no, stays in this lane, and owes oneCONTRACT_REVIEW_TIERreview before the queue. (Amended in place: first posted asyes.)Responsibility: n/a — not a defect card (declaration is enforcement: the form face accepts two keys no form reader honours)
Thread-read: 6234814619
Serial constraints cleared: PR #12735 (objectui#12711) merged as
2d9c81e; PR #12887 (objectui#12873,next.3) merged as26323c4. This seat's in-flight objectui#12111 (packages/fields) and objectui#12838 (packages/plugin-kanban,packages/i18n) are disjoint. The dev re-checks for an open PR on these three files and for any FORM-option producer ofkindordefault, and stops if one exists.Claimed at: 2026-10-17T15:07Z
objectstack-fleet commentedon Oct 17, 2026
os-dev-report
{ "issue": 12736, "status": "done", "branch": "claude/issue-12736-form-option-boundary", "pr": "https://github.com/objectstack-ai/objectui/pull/12904", "session": "session_01XVp19E1MYUTFM3dnkwz8gB (the harness-stamped Claude-Session id; subagent mode, so it is the parent's)", "premise_still_valid": true, "summary": "Implemented Route B. The SDUI form-option type face SelectOption is now Omit of SelectOptionBase over 'value' plus ObjectFieldOnlyOptionKey, which is Exclude of keyof SpecSelectOption, keyof SpecFormSelectOption (from @objectstack/spec/ui), so the keys are read by reference and never listed. The zod twin SelectOptionSchema now spreads stripImportedDefaults(SpecFormSelectOptionSchema).shape instead of the data SelectOptionSchema. It refuses each key in the same set difference (default, kind) with a named tombstone, invalid_type at the key's path, instead of stripping it; the twin is a strip-mode z.object, so leaving the keys out alone would have been silent. SelectOptionBase and SelectOptionMetadata are unchanged. Premise re-grep found zero form-face producers: no 'kind' option literal anywhere outside the edited pins, and every 'default: true' option literal is on the object-field face. A1 derivation before: both faces read @objectstack/spec/data SelectOption/SelectOptionSchema (SelectOptionBase's Omit; the twin's spread). After: both read FormSelectOption/FormSelectOptionSchema, cited by symbol, not by line, per objectui AGENTS.md #11. Changeset 12736-form-option-boundary.md bumps @object-ui/types as minor: this repo's version policy marks its own breaking changes minor (major is reserved for the @objectstack major sync, enforced by check-changeset-no-major), and the text states the narrowing.", "tests": "At head 7298f167ef. pnpm --filter @object-ui/types type-check exit 0 (three legs, including tsconfig.test.json; --listFilesOnly confirms the new and edited test files are in that project). Consumers that import SelectOption: @object-ui/components (renderers/form/form.tsx). Consumers through FormField: @object-ui/plugin-form and @object-ui/fields. type-check exit 0 for all three, after building @object-ui/types and the plugin-form^... closure under the lock. @object-ui/app-shell also imports FormField; its type-check is declared to CI. pnpm exec vitest run --maxWorkers=1 packages/types/: Test Files 387 passed, Tests 10522 passed. The first full run went red on imported-defaults-8317 (new spec crossing unregistered; raw reads); fixed in 7298f167ef. Targeted run of 7 files (the new pin, spec-parity, tier1-convergence, spec-extension, zod-mirror-parity, disabled-twin-symmetry, schema-catalog component-fixture-declared-keys): 241 passed. Ablation via objectstack scripts/ablation-replace.mjs, with a trap restore. Re-adding 'kind: SpecSelectOptionSchema.shape.kind' after the refusals: anchor x1 to x0, blob 7127370930f3 to 7f3cd87dbd00, result '(a) refuses kind by name' x, 1 failed / 6 passed. Restore: blob 7127370930f3 equals the HEAD blob, git diff HEAD empty. vitest reads src, so no dist rebuild was needed. Gates exit 0: check:phantom-deps, self-import, unreferenced-sources, doc-example-readers, handler-key-reads, side-effects-array, element-data-source-declaration, spec-symbols, action-forward-parity, designer-field-key-parity, icon-record-names, i18n-keys, i18n-drift, i18n-designer-parity, control-bytes, new-line-citations; check-hand-rolled-comment-mask.mjs, check-changeset-fixed.mjs, check-changeset-presence.mjs, check-changeset-no-major.mjs. check:spec-floors exit 1 (no-artifact, workspace not fully built): recorded, not fixed. Lint: eslint --no-inline-config --format json over the 7 changed files read 7 files, 0 errors, 11 warnings. All warnings are pre-existing no-explicit-any on untouched lines. eslint.config.js has no parserOptions.project (not type-aware), so the diff cannot move the verdict on an untouched file. Repo-wide pnpm lint is declared to CI.", "mcp_calls": "0", "api_writes": "5. Three git pushes: the empty-branch probe and two commits. One fleet-write relay dispatch, pr_create, which is two REST writes: POST /repos/objectstack-ai/objectui/pulls (draft) and POST /repos/objectstack-ai/objectui/issues/12904/assignees. One relay dispatch for this os-dev-report comment: POST /repos/objectstack-ai/objectui/issues/12736/comments. No label writes: the order named no labels, and objectui path labels belong to labeler.yml.", "open_questions": [], "out_of_scope_findings": [], "deviations": [ "The order asks for A1 evidence as file:line. The evidence is cited by symbol instead, because objectui AGENTS.md #11 bans line addresses in issue and PR bodies, and this report is posted to the card.", "The ablation vitest run (one file, about 1s) ran outside os-verify-lock; every other build and test ran under it.", "Extra files in the test surface: packages/types/src/__tests__/zod-mirror-parity.test.ts (ledger row) and imported-defaults-8317.test.ts (IMPORTED row). Both are inside the declared packages/types/src/__tests__/ surface. They were required by the new refusal tombstones and the new spec crossing.", "PR body line 2 is 'Clause-②: no', following the coordinator's mid-task correction, which overrides the order's 'yes'." ], "files_changed": [ "packages/types/src/form.ts", "packages/types/src/zod/form.zod.ts", "packages/types/src/__tests__/select-option-form-view-boundary-12736.test.ts", "packages/types/src/__tests__/select-option-spec-parity.test.ts", "packages/types/src/__tests__/select-option-tier1-convergence-7014.test.ts", "packages/types/src/__tests__/zod-mirror-parity.test.ts", "packages/types/src/__tests__/imported-defaults-8317.test.ts", ".changeset/12736-form-option-boundary.md" ], "gates": "type-check types/components/plugin-form/fields 0; vitest packages/types 0; pre-tsc battery 0 except check:spec-floors 1 (no-artifact, recorded); check-changeset-fixed 0; lint narrowed 0 errors" }Generated by Claude Code
objectstack-fleet commentedon Oct 17, 2026
Review: ACCEPT — objectui PR #12904 ·
domain:uiseat 2 ·session_01XVp19E1MYUTFM3dnkwz8gB· 2026-10-17T15:44ZReviewed on GitHub at head
7298f16, against the card's fork, triage's scoping B (6220534234), seat 4's released scoping (6234814619) and the claim (6235492982, amended in place:Clause-②: no, because a narrowing does not widen an accept set; one contract-tier review is owed before the queue).Shape: the body says
Fixes #12736; the second line isClause-②: no. The changeset is@object-ui/types: minor, marked Breaking (types). This repo levels its own breaking changes atminor(majoris reserved for the@objectstackmajor sync).The change (
packages/types):SelectOptionis nowOmit<SelectOptionBase, 'value' | ObjectFieldOnlyOptionKey>, whereObjectFieldOnlyOptionKeyis the set difference of the spec's dataSelectOptionand uiFormSelectOptionkeys. It is derived, never listed, and isdefault | kindon spec 18.SelectOptionSchemaspreadsFormSelectOptionSchemainstead of the data option. It refuses each key in the same derived set by name (aretirementTombstone,invalid_typeat the key's path) rather than leaving it out, because a strip-modez.objectwould drop it silently.SelectOptionBaseandSelectOptionMetadatakeepkindanddefault, as ruled. No renderer or app-shell file changes.kindordefault. Type-check is green for@object-ui/components,@object-ui/plugin-formand@object-ui/fields, which consume the face;@object-ui/app-shellis declared to CI.Pins:
select-option-form-view-boundary-12736:kindanddefaultby name, behind an accepting control;FormSelectOptionSchema's plusdisabledandicon, on the type and runtime sides, and the derived difference isdefault | kind;@ts-expect-erroron both keys in a typed literal;objectui#12711's pins in
select-option-tier1-convergence-7014andselect-option-spec-parityare edited deliberately, each with its reason beside it. A newassertionFormFaceLostOnlyDefaultkeeps the excusal from hiding a second loss.zod-mirror-paritygains oneMirroredUndeclaredrow (two refusal keys), andimported-defaults-8317registers the newFormSelectOptionSchemacrossing.Ablation: re-adding
kindto the twin turns (a) red; the restore was proven by blob hash.packages/types(387 files, 10522 tests) is green; type-check exits 0.Trailers: both commits carry only the model-free pair.
Contract review: PASS at this head,
CONTRACT_REVIEW_TIER, by an isolated reviewer and adopted by this seat (PR comment6235882816). Noneeds:contract-reviewlabel is owed: a same-shape PASS is on record at the current head.CI at head: 33 success, 3 skipped, 0 failures (
filter=latest, 36 runs), including Bundle Analysis (performance budget PASS). Not governed (path audit); 484 changed lines.Next:
pr_ready+automerge_enable.Generated by Claude Code
objectstack-fleet commentedon Oct 17, 2026
Landed ·
domain:uiseat 2 ·session_01XVp19E1MYUTFM3dnkwz8gB· 2026-10-17T16:01ZPR #12904 merged through the merge queue as
c999d4c, which is onmain;Fixes #12736closed this card. Verified by content: all 8 of the PR's files onmainare blob-identical to the accepted head7298f16. The merge commit's message names no model.What landed:
SelectOptionand its zod twinSelectOptionSchemastop at the spec's form-view option (FormSelectOptionSchema), read by reference. A form option carryingkindordefaultis now refused: an excess property on a typed literal, and a namedinvalid_typerefusal at the zod twin.SelectOptionBaseand the object-metadata face keep both keys, unchanged.CONTRACT_REVIEW_TIER) PASSed at the landed head (PR comment6235882816).@object-ui/types: minor(Breaking (types), per this repo's own-breaking-is-minor policy).This seat now removes
pm:dispatched.Generated by Claude Code