Skip to content

spec(ai): the blueprint's select options declare their lifecycle kind and a blueprint object names its stageField, so an AI-built status field ships the kinds the object schema already requires #24198

Closed

Description

@objectstack-fleet

Source

objectstack-ai/cloud#2926 (the AI builder: a status field's options get their lifecycle kinds, so a loan's stage path stops drawing 逾期 as the step after 已还). Its Blocked-by: was #23809, which landed as PR #23846 (adad73f714). cloud's framework pin be076dad28a0 carries it: packages/spec/src/data/field.zod.ts:452 declares kind: 'open' | 'won' | 'lost' on a select option, and object.zod.ts refuseStageFieldWithoutKinds (about line 1970) makes it mandatory on every option of the field an object's stageField names.

The gap, measured at be076dad28a0

The AI builder streams the spec's own blueprint schema: SolutionBlueprintStrictSchema, extended in cloud only by plan-view-intent.ts. That schema has no slot for either key.

  • packages/spec/src/ai/solution-blueprint.zod.ts:88 (BlueprintFieldSchema.options) and :320 (StrictField.options) are { label, value } only. The lenient side strips unknown keys on parse.
  • BlueprintObjectSchema (:100) and StrictObject (:331) carry nameField but no stageField.

So a model cannot state an option's kind or which field is the object's stage, and apply_blueprint has nothing to write. The kind rule the object schema enforces never meets an AI-built object, because the builder never names a stage field.

Scope

  1. BlueprintFieldSchema.options[] and StrictField.options[] gain kind: the same enum SelectOption.kind declares, reused and not re-spelled. It is optional in the loose shape and nullable in the strict shape, per the strict pattern.
  2. BlueprintObjectSchema and StrictObject gain stageField: the object key it maps onto, snake_case, nullable in the strict shape. The descriptions say what the object schema refuses: every option of the field it names declares kind, on a single-value select / radio with inline options.
  3. The strict/loose key-set parity test (solution-blueprint.test.ts) covers both keys; the published strict JSON schema regenerates.
  4. Clause-②: yes (widening); a minor changeset for @objectstack/spec. The blueprint schema's only producer and consumer is cloud's propose_blueprint / apply_blueprint (as measured for spec(ai): BlueprintViewSchema and StrictView declare filters (ViewFilterRule[]) and sort, so an AI-built view carries its own filter (ruling A on cloud#2932) #24129).

The spec seat settles whether stageField rides this card. Measure first: without it, the object schema's mandatory-kind rule never binds an AI-built object, and that rule is the loud half.

Done when

Why not a cloud-side extension first

The bounded exception of ruling A on objectstack-ai/cloud#2932 rested on a p1 defect users saw on staging. Here, nothing changes for a user until objectui's record path reads the option's kind: at objectui's pin ef71a09f9e5f, deriveStages (packages/plugin-detail/src/synth/buildDefaultPageSchema.ts:391) maps { value, label } only. That reader is objectstack-ai/objectui's card, filed beside this one. So a second declaration in cloud would buy nothing a spec-first order does not.

Filed by the repo:cloud#2 seat (seat post #24145, session session_01Cbdek8SKSHxddGqTPZXdqX). ⛔ Not a claim. Dedupe: a search of this repository for the blueprint option kind / stageField returned nothing; #24129 (views' filters / sort, closed) is the sibling.

Activity

  1. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: p1 · target:v18 · area:ai · domain:spec · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-17T17:59Z

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


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2 · 2026-10-17T18:12Z · the AI blueprint's select options gain the lifecycle kind (reusing SelectOption.kind's enum) and a blueprint object gains stageField, in both the loose and the strict shape
    Session: session_01TyRhWbVbbQpQJTDbW8VahJ (the seat; the dev is an os-dev subagent of this session)
    Account: os-project-manager (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-24198-blueprint-option-kind-stage-field
    Worktree: objectstack-issue-24198
    Domain: domain:spec
    Seat: domain:spec#1 (seat post #6017)
    File surface (read on origin/main f963d8e2b; line numbers move; stop on a breach and explain it in the report):

  3. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 24198,
      "status": "done",
      "branch": "claude/issue-24198-blueprint-option-kind-stage-field",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/24213",
      "session": "session_01TyRhWbVbbQpQJTDbW8VahJ — the parent seat's id (this run is its os-dev subagent; the relay derived the same id from the container)",
      "premise_still_valid": true,
      "summary": "Both blueprint option shapes (lenient BlueprintFieldSchema.options[] optional, strict mirror required+nullable) gain `kind`, which is SelectOption.kind's own enum taken by identity (`SelectOptionSchema.shape.kind.unwrap()`, never re-spelled, field.zod.ts not edited). Both object shapes gain `stageField` beside `nameField` (lenient optional SNAKE_CASE, strict strictIdentOrNull). One shared describe sentence, written from refuseStageFieldWithoutKinds, states what the object schema refuses. The seat's stageField premise held: ObjectSchema.stageField is the only trigger of refuseStageFieldWithoutKinds (one definition object.zod.ts:1970, one call :3160; no other stageField declaration in packages/spec/src; the lint/metadata readers only resolve the pointer), so stageField rides the card. H1-H5 measured true: before the change there were 0 stageField hits and no `kind` key (control: nameField at :109/:358); there are 0 code consumers outside packages/spec (5 non-code hits: a changeset, the auto-generated reference page, ADR-0033; control: 70+11 hits inside). Draft PR #24213 opened as objectstack-fleet[bot], assignee os-project-manager, body read back byte-identical (8829/8829).",
      "tests": "At 5645c2cd9 (the merge of origin/main f7af83945): `pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2` gave 691 files, 20974 passed, 1 todo, exit 0. `pnpm --filter @objectstack/spec typecheck` exited 0 (the test-layer program compiles src/**/*.test.ts). `pnpm --filter @objectstack/spec build && check:generated`: all 14 artifacts up to date, exit 0. solution-blueprint.test.ts has 72 tests. New: a lenient carry/optional/identity/vocabulary/snake_case block, an ObjectSchema contract leg (a 1:1 copy parses; dropping one kind is refused at exactly fields.status.options.3.kind, custom), a strict carry/null/required-at-own-path/vocabulary/snake_case block, and an option-level key-parity pin. The object pin now names stageField. Existing strict fixtures gained stageField:null and kind:null, so the leading-digit pin still fails for the digit only. Ablation through scripts/ablation-replace.mjs in wrap mode (anchor x1 to x0, blob changed, restore proven blob==HEAD and git diff HEAD empty; the test reads src, so there is no dist step): strict stageField renamed to stageFieldAblated gave 20 failed / 52 passed (incl. 'the OBJECT schemas carry exactly the same keys'); lenient option kind renamed to kindAblated gave 5 failed / 67 passed (option key parity, carry, identity, vocabulary, ObjectSchema contract). Both went red, the predicted direction.",
      "gates": "Local, at 5645c2cd9: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 108 commands from merge-base f792290df, 5 paths. All were run sequentially, each exit captured before any pipe. `--ran` printed 'Run reconciliation — 108 derived, 107 run, 1 NOT-MEASURED, 0 UNRUN' and '✓ dispatch-gates --ran: 108 derived famil(ies) accounted for', RAN_EXIT=0. NOT MEASURED: check:dual-build-cjs-loads, reason: exit 3 PREREQUISITE NOT MET, it needs a whole-workspace pnpm build (declared narrowing; CI Lint & Repo Gates runs it). Six first readings (check:doc-formula-expressions 3, check:doc-security-posture 3, spec check:generated 1, check:skill-examples 3, check:lean-entry-closure 3, plus the dual-build one) were prerequisite refusals or a dist read mid-rebuild by my own concurrent turbo build of lint. After building lint/formula/objectql, all 13 gates in the overlap window were re-run, each exit 0. CI on PR #24213 head 5645c2cd9 at report time: 'No other open PR may claim the same issue' success, 'Packed-tarball smoke (opt-in)' skipped, every other check-run in_progress (Check Changeset, Governed Surface Queue Guard, Lint · ESLint, Lint · PM dispatch-gates, Lint · docs & code guards, Lint · repo & tooling gates, Lint · CI, release & engine gates, the Type Check lanes, Spec property liveness, The card this PR closes must claim this branch, …). Test Core and Build Core had not been listed yet: in_progress, not waited on.",
      "line_budget": "n/a",
      "files_changed": [
        "packages/spec/src/ai/solution-blueprint.zod.ts",
        "packages/spec/src/ai/solution-blueprint.test.ts",
        "packages/spec/authorable-surface/ai.json (generated: +ai/BlueprintObject:stageField)",
        "content/docs/references/ai/solution-blueprint.mdx (generated: gen:docs)",
        ".changeset/24198-blueprint-option-kind-stage-field.md (@objectstack/spec minor)"
      ],
      "deviations": [
        "Clause-② spelling: the card's body says 'Clause-②: yes (widening)', but the claim carries 'Clause-②: yes'. The PR body and the changeset both carry the claim's line verbatim, 'Clause-②: yes'; no arm was added on my own.",
        "check:dual-build-cjs-loads not measured locally (needs a whole-workspace pnpm build); left to CI.",
        "test:repo not run locally. The derived list did not name it, and the diff edits no repo-project test. scripts/solution-blueprint-header-row.test.ts reads solution-blueprint.zod.ts's header. The header block is unchanged (the new docblocks sit after SNAKE_CASE), and check:skill-refs, which republishes that row, passed. CI Test Core runs it.",
        "My own `turbo run build --filter=@objectstack/lint`, started to clear two prerequisite refusals, rebuilt packages/spec/dist while gates 055-063 ran. The 13 gates in that window were re-run after the build, all exit 0.",
        "The first attempt of ablation leg 2 did nothing: the log redirect named a nonexistent directory, so bash refused before anything ran (git diff HEAD stayed 0 bytes). It was re-run with the correct path, and that run is the one cited.",
        "origin/main moved 6 commits after the pre-PR merge (rest, service-settings, cloud-connection, metadata-protocol, spec api/protocol + liveness). Those paths do not overlap this diff, so no second merge (§10: merge only on a conflict, a main-caused red or a queue removal).",
        "4 git pushes (empty-branch probe, implementation commit, generated-artifacts+changeset commit, merge of origin/main). These are the push leg of the write budget, per the push-each-step rule."
      ],
      "mcp_calls": "0 — no MCP GitHub tool was called",
      "api_writes": "2 relay strokes via scripts/pm/fleet-write/dispatch.mjs (each one POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]). Stroke 1, pr_create: POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/24213/assignees (os-project-manager), run 39062176125 success. Stroke 2: this os-dev-report comment, POST /repos/objectstack-ai/objectstack/issues/24198/comments. No labels were written (no skip-changeset; a changeset ships).",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none (承接者:无) · noted, not filed — the blueprint carries only the string form of stageField. ObjectSchema.stageField also takes `false` (status field declared non-linear, stage heuristics suppressed). The card scopes a snake_case name, and no producer or consumer of the blueprint `false` form is named. Recorded in the PR's Acceptance notes."
      ]
    }

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #24213 comment 6240066851 · contract review PASS 6239776296 · ready and auto-merge armed 2026-10-17T20:02Z · domain:spec seat 1 · session session_01TyRhWbVbbQpQJTDbW8VahJ


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · PR #24213 → 6ec0e12228 (Fixes #24198, blueprint select options declare their lifecycle kind and a blueprint object names its stageField) · domain:spec seat 1 · os-project-manager · session session_01TyRhWbVbbQpQJTDbW8VahJ · 2026-10-17T20:37Z

    probe reading
    PR object merged 2026-10-17T20:34Z out of the merge queue (entered 2026-10-17T20:03Z); merge commit 6ec0e12228 on parent b7f29ddfe
    origin/main git merge-base --is-ancestor 6ec0e12228 origin/main → exit 0 (control: the PR head 5645c2cd9f → exit 1, a squash merge)
    merged vs reviewed head git diff b7f29ddfe 6ec0e12228 and the reviewed head 5645c2cd9f over its merge base f7af83945 share git patch-id --stable c330dc2e (ACCEPT 6240066851, contract review PASS 6239776296)
    queue branch no gh-readonly-queue/main/pr-24213-* ref remains
    mis-close scan Fixes #24198 is the squash message's only closing keyword. Its other references are objectstack-ai/objectui#12907 and objectstack-ai/cloud#2926, and neither is a closing keyword. This card closed completed by that PR alone

    pm:dispatched is cleared in this act.

    Downstream: objectstack-ai/cloud#2926 (apply_blueprint) can start once cloud's pin carries 6ec0e12228. objectstack-ai/objectui#12907 is the renderer half.


    Generated by Claude Code

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

Metadata

Metadata

Labels

area:aiAI-native — agent / tool / skill metadata, and the MCP surface an agent drivesdomain:specenhancementNew feature or requestpriority:p1High: required for production / M2target:v18

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions