Skip to content

types: spec 18 adds the optional select-option kind (objectstack#23846), so the tier-1 convergence pins in select-option-tier1-convergence-7014.test.ts no longer hold — state kind as a deliberate optional widening #12711

Closed

Description

@objectstack-fleet

Filing gate ④, a cross-repo child: the objectui half of objectstack-ai/objectstack#23809, which landed as objectstack-ai/objectstack#23846 (adad73f714, merged 2026-10-16T11:15Z).

Blocked-by: objectstack-ai/objectstack#23846

What spec 18 changed

objectstack#23809 Target 1, implemented by #23846. The changeset is .changeset/23809-select-option-kind.md:

  • SelectOptionSchema (@objectstack/spec/data) gains kind: z.enum(['open', 'won', 'lost']).optional(), a lifecycle category: open is a step on the way, won the successful end and lost the unsuccessful end, on every object. Absent stays absent at parse and reads as open.
  • FieldSchema refuses kind outside a single-value select / radio (options.N.kind). ObjectSchema refuses a stageField whose options lack kind, one located issue per option.
  • Form-view options (FormSelectOptionSchema) leave kind out, as they leave out default.

The objectui sites

packages/types/src/__tests__/select-option-tier1-convergence-7014.test.ts, line numbers on objectui main f8267df9. All four are TS2344: Type 'false' does not satisfy the constraint 'true'.:

line assertion why it fails now
:130 assertionFormFaceKeptEveryMember: Omit<SelectOption, 'description'> equals PRE_CONVERGENCE_SelectOption SelectOption derives the spec's keys by reference (SelectOptionBase), so it now carries kind too
:138 assertionFormFaceGainedOnlyDescription: the set difference is 'description' it is now 'description' | 'kind'
:150 assertionMetadataFaceKeptEveryMember: Omit<SelectOptionMetadata, 'default'> equals PRE_CONVERGENCE_SelectOptionMetadata kind again
:157 assertionMetadataFaceGainedOnlyDefault: the set difference is 'default' it is now 'default' | 'kind'

The file's own docblock says what to do with a new key: the PRE_CONVERGENCE_* types are a historical record and are never edited to match. A deliberate widening is asserted, as default and description are.

What done looks like

  • The four pins state kind as a deliberate optional widening on each face where the spec now gives it:
    • each face gained description / default and kind, and nothing else;
    • kind is optional;
    • the PRE_CONVERGENCE_* record is unchanged.
  • ⛔ Not as any. ⛔ Not by editing PRE_CONVERGENCE_*. ⛔ Not by Omitting kind from the faces without saying so: the form face may have its own reason to leave kind out (the spec's form-view options do), and that is this card's call, stated in the test.
  • The runtime half of the same file (rejectsUndeclaredKeyOnFormFace, acceptsEveryDeclaredKeyOnFormFace) is re-read against a spec that accepts kind.
  • Out of scope (their own cards, Targets 2–4 of objectstack#23809):
    • objectui's record path and kanban reading the option kind;
    • the Studio inspector column.

Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, bug · priority:p1 · target:v18 · domain:ui · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-16T14:56Z

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015). ⛔ Not a claim, ⛔ not a dispatch.

  2. added
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Oct 16, 2026
  3. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 4 (priority:p1, target:v18; the next red row of objectstack's hourly Spec Shape Gate, triage grade 6218222181)
    Session: session_011K9g23W67dy2zwnyzaoB6o
    Account: os-tesla
    Branch: claude/issue-12711-select-option-kind
    Worktree: objectui-issue-12711 (in the cloud session's own container)
    Domain: domain:ui
    Seat: domain:ui#1
    File surface:

    • packages/types/src/__tests__/select-option-tier1-convergence-7014.test.ts: the four type pins at :130, :138, :150 and :157, plus the runtime pair rejectsUndeclaredKeyOnFormFace / acceptsEveryDeclaredKeyOnFormFace, re-read against a spec that accepts kind;
    • a .changeset/12711-*.md only if objectui's changeset rules ask for one on a test-only change.

    The pins must hold against BOTH the installed @objectstack/spec@18.0.0-next.2 (no kind) and objectstack main's spec (with kind). That is what the hourly gate compiles.

    Outside the surface:

    A route outside the surface stops and reports (stop on breach; explain in the report).
    Container & model: S, mode:cloud, model: opus (default tier; dispatch-gates --tier --repo objectstack-ai/objectui over the path: no path-derived mandate)
    Clause-②: no
    Clause-② reason: test pins only. No published type, prop, export, schema read or pack key changes; the faces already derive the spec's keys by reference.
    Responsibility: n/a — the pins went false through an upstream spec widening (objectstack-ai/objectstack#23846), not an objectui landing
    Thread-read: 6218222181
    Serial constraints cleared: read at objectui main 96fa4b9bc4. Blocked-by: objectstack-ai/objectstack#23846 is satisfied (merged 2026-10-16T11:15Z, per triage 6218222181). No open PR touches the test file or SelectOption. objectui#12670 (another seat, in flight) is in packages/types, but on the list-view node face, not this file. NOT governed (check-governed-merges --test, 0 of 1).


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 12711,
      "status": "done",
      "branch": "claude/issue-12711-select-option-kind",
      "pr": "https://github.com/objectstack-ai/objectui/pull/12735",
      "session": "session_01EXsEfmHv5KzaBT66eSH7dZ",
      "premise_still_valid": true,
      "summary": "Each of the four pins (FormFaceKeptEveryMember, FormFaceGainedOnly…, MetadataFaceKeptEveryMember, MetadataFaceGainedOnly…) now names spec 18's `kind` through `type SpecKindKey = Extract<keyof SpecSelectOption, 'kind'>`. That key is read off the spec's own type: it is 'kind' on objectstack main's spec and never on the installed 18.0.0-next.2. The gained sets are therefore 'description' | SpecKindKey and 'default' | SpecKindKey. Three new pins assert that `kind`, where present, is optional on both faces and is the spec's own member by reference. PRE_CONVERGENCE_* is byte-unchanged, there is no `as any`, and nothing is Omitted from the faces; only the test's Omit/Exclude arguments name the gained key. The runtime half also needed a change the card did not list: 'lists exactly the keys the spec declares' allows `kind` when the spec shape has it, and a new two-branch test asserts `kind` is either an optional spec key (absent stays absent, 'closed' refused) or refused by name as unrecognized_keys. H1 confirmed: the old file on main's spec gives TS2344 at exactly :130/:138/:150/:157. H2 confirmed: the installed spec lacks kind (the probe asserting SpecKindKey equals 'kind' is red there and green on main). One empty-frontmatter changeset was added because check-changeset-presence has no carve-out for src/__tests__.",
      "tests": "Installed spec line (@objectstack/spec 18.0.0-next.2, no kind) at f679d4a: `tsc -p tsconfig.test.json` EXIT=0; `pnpm exec vitest run packages/types/src/__tests__/select-option-tier1-convergence-7014.test.ts` EXIT=0, 'Tests 9 passed (9)'; `pnpm exec vitest run packages/types/src/__tests__` EXIT=0, 'Test Files 384 passed (384) / Tests 10357 passed (10357)'. Main spec line (objectstack main 2054a0888c, packages/spec built and packed, injected with objectstack's own `node scripts/ci/spec-shape-gate.mjs inject` = the Spec Shape Gate recipe): `tsc -p tsconfig.test.json` EXIT=0; the same vitest file EXIT=0, 'Tests 9 passed (9)'. Final head e6b6093 differs from f679d4a only by the .changeset file (git diff --stat: 1 file, 4 insertions), and `tsc -p tsconfig.test.json` at e6b6093 EXIT=0. The vitest re-run in the worktree hit ERR_MODULE_NOT_FOUND loading the config through symlinked node_modules; that is not a reading, and the f679d4a run is cited instead. Ablation in a dedicated worktree, script with an EXIT/INT/TERM restore trap; each mutation had an anchor-count assert plus a grep -c marker hit = 1; after each, `git checkout HEAD --` restored it and the restored blob equalled HEAD blob 3b00710 and `git diff HEAD` was empty. MAIN line: M0 head EXIT=0. M1 = the old test from 96fa4b9: EXIT=2, TS2344 at (130),(138),(150),(157), which reproduces the card. M2 = probe that SpecKindKey equals 'kind': EXIT=0. M3 = `weight?: number` added to SelectOptionBase: EXIT=2, TS2344 x4 on the four pins plus TS2578 x2. M4 = description dropped from the base: EXIT=2. M5 = kind made required on the form face: EXIT=2, TS2344 on the optional pin. INSTALLED line, after the restore: M0 EXIT=0; M1 EXIT=0, so the old file is green there; M2 probe EXIT=2, which proves SpecKindKey is never; M3 EXIT=2 with the same four TS2344; M4 EXIT=2; M5 EXIT=2. Install restored: the injected dir was removed, the pre-inject copy was put back, `diff -r` against that backup EXIT=0, 0 'Lifecycle category' hits, and `sha256sum -c` on pnpm-lock.yaml OK.",
      "gates": "Local, installed line, at f679d4a (test bytes identical to e6b6093); each exit code was captured to a file before any pipe: `pnpm --filter @object-ui/types type-check` EXIT=0 (log echoes 'tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json'); vitest packages/types/src/__tests__ EXIT=0 (384 files / 10357 tests); `pnpm check:changeset-claims` EXIT=0; `pnpm changeset:check` EXIT=0; `pnpm check:test-path-roots` EXIT=0; `pnpm check:spec-symbols` EXIT=0; `pnpm exec eslint` on the changed test EXIT=0. At e6b6093: `node scripts/check-changeset-presence.mjs` EXIT=0 ('Every one of them has an EMPTY frontmatter'); it was EXIT=1 before the changeset was added. `pnpm check:changeset-claims` EXIT=0 and `pnpm changeset:check` EXIT=0. `node scripts/check-nul-bytes.mjs` is NOT MEASURED: that script does not exist in objectui (module not found, not a gate reading). A manual control-character grep over both files found no hit (exit 1). objectui has no dispatch-gates.mjs, so the gate list was derived by hand from package.json and ci.yml as the brief says; there is no --ran reconciliation. CI check-runs: no PR yet, so in_progress or not started.",
      "mcp_calls": "2 — mcp__github__issue_read (get, get_comments); both reads, no write tool",
      "api_writes": "0 — the only GitHub writes were git pushes of the branch (the empty-branch probe, then f679d4a, then e6b6093)",
      "open_questions": [
        {
          "question": "H3: should objectui's SDUI form face (`SelectOption` in packages/types/src/form.ts) carry spec 18's `kind`? The spec's form-view FormSelectOptionSchema leaves out both `kind` and `default`. Measured: objectui references FormSelectOptionSchema nowhere outside tests. The form face has carried `default` since before the convergence, so it was never modelled on that schema. Its zod twin (SelectOptionSchema in zod/form.zod.ts) spreads the spec's data SelectOptionSchema shape, so it accepts `kind` too, and type and twin agree. No reader of an option's `kind` exists on the form face today. This PR keeps `kind` on the face and states that in the test docblock; the face itself is not changed (outside the claim).",
          "options": [
            "A — Keep `kind` (and `default`) on the form face as now; no face change.",
            "B — Omit `kind` from the form face and its zod twin, in a separate card, so the form face refuses an object-lifecycle key, as the spec's form-view options do."
          ],
          "recommendation": "B as its own card, not urgent. Axis 1, real business need: no form-face reader of `kind` exists, while the object face is the one stage renderers will read (Targets 2–4). Axis 2, long-term soundness: following the spec's form-view boundary keeps one authority. Axis 3, making AI mistakes structurally harder: a form option carrying `kind` today is accepted and silently ignored, which is the 'declared but not honoured' trap; a strict refusal is better. Axis 4, startup scope discipline: it is a small narrowing with zero measured producers, so no staged transition is needed. Do it together with `default`, which has the same gap; that widens the card, so it needs a maintainer call. Until then, A is safe: the pins here hold under either choice and a future Omit would show up as a deliberate edit to these same set differences."
        }
      ],
      "out_of_scope_findings": [
        "Deviation (process): the first test edit (commit f679d4a) was written into the shared primary checkout through a Python script; the repo's guard-main-checkout-bash hook blocked a later shell write. Every write after that, the ablations included, ran in a dedicated worktree (../objectui-issue-12711), which is now removed. carrier: none, noted, not filed.",
        "Deviation (attribution): commit f679d4a carries the harness-supplied model-named Co-Authored-By trailer. objectui AGENTS.md asks for the model-free pair, and e6b6093 uses it. History is not rewritten because force-push is banned in objectui; AGENTS.md treats a harness-written trailer as reporting only. carrier: none, noted, not filed.",
        "NOT MEASURED: on the main line, which branch the new runtime `kind` test took. The 9/9 pass is consistent with both branches; the type-side probe M2 proves the spec type declares kind, and the injected dist carries the kind schema text, so the 'declared' branch is inferred, not observed."
      ]
    }

    Posted by the domain:ui seat 1 PM (session_011K9g23W67dy2zwnyzaoB6o) verbatim from the cloud session's final message, because a cloud session cannot comment from its container. The seat filled pr with the draft PR it opened, #12735.

    The seat's answers:

    • Open question: A for this PR, and B filed as its own card, objectui#12736, for triage to grade. Narrowing the form face would be a published-type narrowing that owes a contract-review tier, and it widens this card, so it rides its own card.
    • The attribution deviation (a harness-written trailer on f679d4a) is not rewritten: objectui forbids force-push. It is recorded here.

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #12735 comment 6220367831 · TIER_DEFAULT (Clause-②: no, test-only) on e6b6093 · in the merge queue · domain:ui seat 1 · session_011K9g23W67dy2zwnyzaoB6o · 2026-10-16T16:50Z


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:ui seat 1 · session_011K9g23W67dy2zwnyzaoB6o · 2026-10-16T17:06Z

    • Merged: PR test(types): state spec 18's optional select-option kind as a deliberate widening in the tier-1 convergence pins #12735 landed through the merge queue as main 2d9c81e1bb, with one parent, b393152. It is an ancestor of origin/main.
    • Verified by content: the merge commit's own diff equals the PR's diff from its merge-base 96fa4b9bc4 to the reviewed head e6b6093, changed line for changed line in both files (+104/−16). Positive control at the merge commit: the convergence test names SpecKindKey 12 times.
    • Card: closed completed by Fixes #12711. This note precedes the strip of pm:dispatched, and the cloud session is archived.
    • Next, outside this card: objectstack's hourly Spec Shape Gate should drop this row once its next run builds against this main. The form face's kind question rides objectui#12736.

    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

bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p1target:v18

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions