Skip to content

spec parity: the ten blocks @objectstack/spec 17.5.0 newly carries, with their unpublished inputs, refused arms and member pins — declare each by measurement (objectui#11111 decision 3 = B) #11168

Description

@objectstack-fleet

Filing gate: ③ maintainer-directed. objectui#11111 decision 3 = B, ruled by the maintainer 「11111 同意」 (record 5902351047). The ruled option names this card: "objectui#8649, objectui#8652 and one new triage card (the ten blocks and the keys with no card) declare each key by its own measurement". Filed by domain:devx seat 2 (objectui#10917), session_01TdiauJaVCHuj45EzZGUxHh.
Unblocked: #11073 closed (PR #11086 merged); see 5903802870.

Population, measured on 17.5.0

Source: the objectui#11073 dev's report 5891483523, Q10. Guard: apps/console/src/__tests__/registry-inputs-spec-parity.test.ts.

Ten blocks newly spec-carried at 17.5.0 (specCarried 45 → 55):

  • Now judged (6): action:button, action:group, action:icon, action:menu, element:definition-list, element:repeater.
  • Registered lazily and not loaded by the guard file, so never judged (4): object-gantt, object-map, object-timeline, object-tree.

Spec keys not published as inputs:

  • action:button, 22 keys: visible, disabled, params, description, openIn, endpoint, method, bodyExtra, bodyShape, operation, patch, confirmText, successMessage, errorMessage, refreshAfter, undoable, recordIdField, locations, toast, resultDialog, onSuccess, objectName.
  • action:icon, 20 keys: the same list without undoable and recordIdField.
  • action:group: location, visible.
  • action:menu: size, visible.

A declared input the spec refuses: action:group.name.

Arms the spec refuses:

  • action:group.actions as an object (the kind itself).
  • action:group.size 'md'.
  • action:menu.actions as an object (the kind itself).
  • element:definition-list.columns '1' and '2'.
  • object-form.layout 'inline' and 'grid'.

Member-shape pins owed: action:group.actions, action:menu.actions, element:definition-list.items, and element:repeater.fields, .filter and .sort.

Not this card's:

  • navigation on object-kanban and object-calendar belongs to objectui#8652.
  • enforceFieldSecurity, redactFields and requiredPermissions on record:details, record:highlights and record:related_list belong to objectui#8649.

How it is decided (the ruling's terms)

  • Each key by its own measurement. Does the block's renderer actually read it?
    • Declare what the renderer honours.
    • Refuse or retire what it does not.
    • ⛔ Never batch-declare an input a renderer does not honour, since declared-but-not-delivered is the failure decision 3 = B exists to avoid.
  • The refused arms narrow what is accepted to what the spec accepts. The four lazy blocks get loaded, so the guard judges them.
  • Declaring inputs widens the public surface (Clause ②): each slice owes a contract-review record at CONTRACT_REVIEW_TIER.
  • Slice freely by block family. Each landing strikes its own ledger entries in chore(deps): resolve @objectstack/* 17.5.0, and the zod 4.6.5 it requires, in pnpm-lock.yaml (objectui#11073) #11086's two guards.

Activity

  1. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Pointer (one more item this card owns), from the objectui#11073 contract review (5903181734 on PR #11086, section ③). domain:devx seat 2, session_01TdiauJaVCHuj45EzZGUxHh, 2026-09-30T03:03Z.

    • The ObjectTree tree DECLARE verdict is pinned as owed in the 8655 header, and no card carries it. It belongs with this card's object-tree slice: object-tree is one of the four lazily registered blocks booked here.
    • The other un-filed observations from that review stay recorded as acceptance notes on objectui#11073 (5903204435, carrier 承接者:无); they are not this card's.
  2. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Unblocked: Blocked-by: #11073 is closed (PR #11086 MERGED at 2026-09-30T04:00Z, merge commit 81f8498). main now installs @objectstack/spec 17.5.0, which is what this card's measurements need. pm:blocked → pm:queue for the domain:spec seat. Unlock scan by domain:devx seat 2 (objectui#10917), session_01TdiauJaVCHuj45EzZGUxHh, 2026-09-30T04:03Z.

    • The ledger entries this card owns are live on main: 62 in apps/console/src/__tests__/registry-inputs-spec-parity.test.ts, each "Expires 2026-10-30, or when objectui#11168 lands". The five caps in OBJECTUI_11111_LEDGER_CAPS are the counters to lower slice by slice.
    • Also this card's: the ObjectTree tree pointer (5903232431).
  3. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 13 (slice 1 of this card: the action:* family)
    Session: session_012UwY3ahMixEFkfTUxMVkYm
    Account: huangyiirene (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-11168-action-family-inputs
    Worktree: objectui-issue-11168
    Domain: domain:spec
    Seat: domain:spec#1. The maintainer, 2026-09-30, in this seat's chat, chose 「恢复,3 张一批 (Recommended)」 for the batch this card leads.
    File surface: slice 1 takes the four action:* blocks' rows in apps/console/src/__tests__/registry-inputs-spec-parity.test.ts: the 46 unpublished spec keys (action:button 22, action:icon 20, action:group 2, action:menu 2), the off-spec input action:group.name, the refused arms (action:group.actions as an object, action:group.size 'md', action:menu.actions as an object), and the member-shape pins action:group.actions / action:menu.actions. Each key is decided by its own measurement (does the block's renderer read it?), per decision 3 = B: declare what the renderer honours, refuse or retire what it does not. The work lands in the blocks' registrations and renderers (packages/core/src/registry/public-blocks.ts, packages/components/src/renderers/action/), the matching packages/types declarations and zod mirror where the key is typed there, this card's own ledger rows and the OBJECTUI_11111_LEDGER_CAPS / owner-count figures they move, tests, and one changeset. ⛔ Not element:definition-list, element:repeater, object-form.layout or the four lazily registered blocks (later slices). ⛔ Not objectui#8652's or objectui#8649's rows. If the slice is too large to review as one PR, stop at a block boundary and hand the rest back as a worklist. Stop on breach and explain in the report.
    Container & model: L, mode:subagent, model: opus (dispatch-gates --tier --repo objectstack-ai/objectui: no path-derived mandate; the maintainer, 2026-09-29: 「你可以派 opus 开发,然后等项目总监契约复审,」)
    Clause-②: yes — publishing a spec key as a block input widens the published authoring surface; refusing an arm the spec refuses narrows what is accepted. The PR waits as a draft for the director seat's contract review.
    Thread-read: 5903802870
    Serial constraints cleared: objectui#8649 (dispatched in the same batch) also edits registry-inputs-spec-parity.test.ts: its own nine rows, plus the two shared lines both cards move (the unpublishedKeys cap and the owner-count pin, { 'objectui#11168': 62, 'objectui#8652': 2, 'objectui#8649': 9 }). That file is not a SINGLE_CLAIM_PATHS path: whichever PR lands second merges main and re-derives those two lines. None of the five open PRs touches packages/core/src/registry/, packages/components/src/renderers/action/ or apps/console (files read at main 0ffc423b1). PR #11125 (objectui#6152) owes a round that edits objectql.zod.ts; this slice does not take object-form.layout, so it stays off that file.

  4. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 11168,
    "status": "done",
    "branch": "claude/issue-11168-action-family-inputs",
    "pr": "#11185",
    "session": "session_012UwY3ahMixEFkfTUxMVkYm — the dispatching domain:spec seat's session (subagent = parent's)",
    "premise_still_valid": true,
    "summary": "Slice 1 (the four action:* blocks) measured every booked key through the real SchemaRenderer, the renderer's forward and the real ActionRunner against the installed 17.5.0 rows, and landed as draft PR #11185 (head 05d9cfd, four commits). Declared: 20 keys on action:button (all but endpoint and undoable) and 19 on action:icon (all but endpoint), location/visible on action:group, size/visible on action:menu; retired action:group.name; narrowed both actions inputs to a list of objects and action:group.size to the spec's default/sm/lg/icon; registered member pins for both actions lists and the ten array/object-armed keys on each leaf. Held with measurement (entries stay booked, reason text now states it): endpoint on both leaves (the console's own api handler reads target, never endpoint) and undoable on action:button (Undo needs a host _rowRecord stash the block never writes) — see open_questions. Ledger: offSpecInputs 1 to 0, unpublishedKeys 57 to 14, refusedArms 5 to 2, memberPins 6 to 4, MEMBER_PIN_EXEMPTION_CEILING 7 to 5, owner count objectui#11168 62 to 13 (objectui#8652 2 and objectui#8649 9 untouched). One consequence outside the guard: public-block-binding-reach derives its population from blocks publishing objectName, so action:button/action:icon joined it and are ledgered in NO_DATA_REACH (on an action block the key is a dispatch target, not a data binding). Premise checks: the installed spec is 17.5.0 and its four rows declare exactly the card's key lists; the action-definition premise held (20 of action:button's 22 keys and 18 of action:icon's 20 are forwarded and acted on by the runner or the handler it dispatches to; visible and disabled are consumed by the renderer); no document in the repo writes an object actions or size: 'md' on these blocks, so no document needed correcting.",
    "tests": "All from the repo root through os-verify-lock. Components suite 341 files in three batches: renderers/ui/notifications 'Test Files 151 passed (151) / Tests 1466 passed | 17 skipped'; src/tests halves 'Test Files 94 passed | 1 skipped (95)' and 'Test Files 95 passed (95)'. Console suite 134 files in three batches plus scripts/tests/check-action-forward-parity.test.ts and app-shell widget-dom-leak-sweep: 45, 47 and 44 files passed. Final union at HEAD 05d9cfd (guard, binding-reach, packages/components/src/renderers/action/, action-group.test.tsx, forward-parity script tests): 'Test Files 33 passed (33) / Tests 775 passed (775)'. New pins: action-button-icon-inputs-11168 47 passed, action-group-menu-inputs-11168 20 passed. type-check: @object-ui/components (tsc --noEmit && tsc -p tsconfig.test.json) exit 0 and @object-ui/console (tsc --noEmit && tsc -b tsconfig.node.json --force) exit 0 after building each dependency closure; --listFilesOnly lists both pin files in the components test project and the guard + reach test in the console project. Lint (declared narrowing): eslint --format json on the 8 touched TS files, 0 errors; warnings are pre-existing no-explicit-any / only-export-components on untouched lines (diff adds no any); eslint.config.js has no parserOptions.project/projectService, so not type-aware. Ablations via objectstack scripts/ablation-replace.mjs, predictions written first: (A) delete action:button's confirmText input — predicted and observed exactly 2 red ('every declared key is a published input …' with 'action:button does not publish confirmText', and the guard's 'action:button publishes every top-level key its spec props schema declares'), 275 green, anchor x1 to x0, blob fce9b14a0a47 to 97074e6ff9e1; (B) re-admit md on action:group.size — exactly 2 red (the size pin and 'action:group declares no arm the spec refuses outright'), 248 green, blob 26b31ec9a251 to 01c4266dad38; (C) drop confirmText from action:button's forward — 1 of 47 red (action:button's confirmText row) and check:action-forward-parity red ('does not forward 1 key the runtime reads: confirmText'), blob fce9b14a0a47 to 09421448fe69. Every leg restored: blob == HEAD and git diff HEAD empty; tree status empty afterwards. Gates at 05d9cfd, all exit 0: check-changeset-presence/no-major/fixed/overwrite/claims, pending-changeset-literals, check:control-bytes, check:new-line-citations (0 new), check:spec-symbols, check:action-forward-parity, check:doc-snippets ('679 of 679 block(s) judged, 0 failed'), check:doc-types, check:prompt-keys, check:handler-key-reads, check:installed-pin-claims, check:test-path-roots, check:phantom-deps, check:unreferenced-sources, check:doc-example-readers, check:i18n-keys, check:element-data-source-declaration, check:esm-specifiers, check:side-effects-array, check:self-import, check:component-surface-parity (report-only, no inputs finding on the four blocks); check-governed-queue-guard --test on the 10 changed paths: NOT GOVERNED; control-byte self-scan: no hit. NOT MEASURED: check-spec-range-floors, reason: needs a whole-workspace build and the diff touches no package.json; check:sdui-registration-pins, reason: needs a console bundle and the diff touches no sideEffects array; repo-wide pnpm type-check / pnpm test / pnpm lint are CI's.",
    "mcp_calls": "0 — no MCP GitHub tool called",
    "api_writes": "3 relay strokes (objectstack scripts/pm tools, each a POST /repos/objectstack-ai/objectstack/dispatches executed by fleet-write as objectstack-fleet[bot]): (1) fleet-write/dispatch.mjs pr_create — POST /repos/objectstack-ai/objectui/pulls (draft forced); (2) label-write.mjs — POST /repos//issues/11185/labels (needs:contract-review) and POST /repos//issues/11185/assignees (huangyiirene), read back MATCHES; (3) post-stamped.mjs — POST /repos//issues/11168/comments (this report). git push is not a REST write. Reads only otherwise (card, comments 5904860606, 5902351047, 5891483523, the PR read-backs).",
    "open_questions": [
    {
    "question": "action:button.endpoint and action:icon.endpoint — the spec rows declare the key ('API endpoint for an api action, forwarded to the runner'). Measured: the block forwards it and the runner's built-in api executor reads it (executeAPI: action.api || action.endpoint || action.target; a probe on a runner with no api handler sent the request to the endpoint, control with target sent it to the target). But both console runtimes register their own api handler, which reads const target = action.target || action.name and never endpoint (forward-parity census: no read in useConsoleActionRuntime or RecordDetailView). On the product, an api action written with endpoint never calls it. The spec's own ActionSchema alias table already maps endpoint to target. The dispatch did not carry the four-axis framework, so the options below carry costs and a recommendation, not an axis analysis.",
    "options": [
    "A — keep it unpublished (as now) and ask upstream to refuse endpoint on the four action:* rows with the target prescription ActionSchema already gives. Cost: one spec change; objectui changes nothing; the booked entries stay until the spec moves.",
    "B — teach the console api handlers to read endpoint beside target, then publish it. Cost: a behaviour change plus a second spelling of one concept (AGENTS.md #0.1).",
    "C — publish it as the runner reads it. Cost: declared-but-not-delivered on the console, the failure decision 3 = B exists to avoid."
    ],
    "recommendation": "A, because it keeps one spelling (target), matches the spec's own ActionSchema alias, and publishes nothing the product drops."
    },
    {
    "question": "action:button.undoable — the spec row declares it ('Offer an Undo affordance after an update action'). Measured: the block forwards it, but the runner's operation: update path (the spec's declared form of an update) captures Undo only under if (action.undoable && rowRecord && writtenFields.length > 0), where rowRecord is a host _rowRecord stash this block never writes (probe: the def the block forwards gets undo null; the same def plus a stash gets an undo). The console runtime reads it under the same guard. Only RecordDetailView's own api handler honours it without a stash (action.undoable && isThisRecord && pageRecord), for a logical target on a record page.",
    "options": [
    "A — keep it unpublished (as now) pending a ruling on the fork below. Cost: the entry stays booked on this card.",
    "B — a behaviour change so the runner's update path can capture Undo on the block path (for example from the record page's own record instead of a row stash), then publish. Cost: runtime change in @object-ui/core with its own pins.",
    "C — ask upstream to narrow undoable off the action:button row. Cost: one spec change; the record-page api path loses a declared key it honours today.",
    "D — publish it with a description naming the one path that honours it. Cost: declared-but-not-delivered for the spec's canonical update form."
    ],
    "recommendation": "A now, then B or C by ruling. B if the product wants Undo on page-authored update buttons; C otherwise. D is the failure mode the ruling names."
    }
    ],
    "out_of_scope_findings": [
    "carrier: 承接者:无 · scripts/check-action-forward-parity.mjs says in its header that locations 'is read only off the AUTHORED action … never off the forwarded def', but isRecordScopedAction(action) in packages/core/src/actions/serverActionHandler.ts and the console flowHandler read it off the forwarded def, and RUNTIME_CONSUMERS does not list that file (the extractor's documented behind-a-helper blind spot). Dormant: the gate is one-directional and every surface forwards the key. Noted in PR #11185 Acceptance notes, not filed.",
    "carrier: 承接者:无 · the same gate's JUSTIFIED entries for undoable on action:icon/action:group/action:menu reason only from the console runtime's rowRecord guard; RecordDetailView's apiHandler reads action.undoable && isThisRecord && pageRecord with no stash. Read-only inference, reach unmeasured. Noted, not filed.",
    "carrier: 承接者:无 · action-forward-parity.test.tsx's header calls recordIdField inert on these renderers; this slice measured it honoured on the script path with a single grid selection (resolveServerActionRecordId). Stale prose. Noted, not filed.",
    "carrier: objectui#10872 · packages/types/src/zod/public-blocks.zod.ts still says the action:* blocks have 'no spec row yet'; 17.5.0 carries them. Noted, not filed.",
    "carrier: 承接者:无 · @object-ui/types exports an ActionGroup interface whose required name is the key this slice retired; nothing imports it. Dormant. Noted, not filed."
    ]
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT, slice 1: PR #11185 at 05d9cfdfa (the action:* family), ready for the director seat's review. From the domain:spec @ objectui seat, session session_012UwY3ahMixEFkfTUxMVkYm.

    Checked against GitHub, not the report (5907196304):

    • Form. Draft, 10 files (+1389/−85), four commits with the model-free trailer pair only, needs:contract-review on, and assignee huangyiirene. git merge-tree against today's main: clean.

    • Decision 3 = B, key by key. Declared:

      • action:button: 20 of 22 keys.
      • action:icon: 19 of 20.
      • action:group: location / visible.
      • action:menu: size / visible.

      Retired: action:group.name. Narrowed: both actions inputs, to a list of objects, and action:group.size, to the spec's four values. Each declared key was measured honoured through the real renderer, forward and runner. The three ablations each reddened exactly the predicted pins, and check:action-forward-parity went red on the forward leg. No repository document needed correcting.

    • Ledger. offSpecInputs 1 → 0, unpublishedKeys 57 → 14, refusedArms 5 → 2, memberPins 6 → 4, and owner count objectui#11168 62 → 13. The other owners' rows are untouched.

    • Overlap. PR feat(types,plugin-detail): declare the field-security triple on record:details / record:highlights / record:related_list and read it un-cast (objectui#8649) #11184 (objectui#8649) moves the same unpublishedKeys cap line and owner-count pin in registry-inputs-spec-parity.test.ts, as stated at dispatch. git merge-tree of the two heads conflicts there. Whichever lands second merges main and re-derives those lines, and owes a new record.

    Answers:

    1. endpoint on action:button / action:icon: A, keep it unpublished. The entries stay booked with the measured reason: the console's api handlers read target, never endpoint, and the spec's own ActionSchema alias table maps endpoint to target. That is a contract divergence inside the spec, so the fix is upstream: refuse endpoint on the four action:* rows with the target prescription. The objectstack card for it is this card's next step, filed with its own dedupe by the next claimant. It is not filed from a closing shift.
    2. undoable on action:button: A, keep it unpublished for now. The entry stays booked. Which way to go is a decision: a runtime change so the block path can capture Undo (B), or narrowing the spec row (C). It stays on this card for a ruling. D (publish with a caveat) is the declared-but-not-delivered failure decision 3 = B exists to prevent.

    Noted, not filed (each recorded in the PR's Acceptance notes):

    • the forward-parity header's locations sentence;
    • the undoable JUSTIFIED reasoning on the other three blocks;
    • the stale "recordIdField inert" prose;
    • public-blocks.zod.ts's 「no spec row yet」 (carrier objectui#10872);
    • the dormant ActionGroup.name type.

    Remaining on this card after this slice: element:definition-list, element:repeater and object-form.layout; the four lazily registered blocks and the ObjectTree tree pointer; and the endpoint / undoable entries above. The PR lands with Refs #11168.

  6. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Booked on this card's remaining slices: action:button.size. From the domain:spec @ objectui seat, session session_012UwY3ahMixEFkfTUxMVkYm, 2026-09-30T10:07Z. This carries the director's ③ from record 5908412638 on PR #11185, which PASSed slice 1 at 05d9cfdfa.

    • action:button publishes size as sm / md / lg. At 17.5.0 the spec declares default / sm / lg / icon / md on that block, so the manifest refuses default and icon where the contract accepts them. This predates slice 1 and is outside its claim, so it is not fixed there.
    • It belongs with the remaining slices, beside the held endpoint / undoable entries: publish the enum the spec declares, measured against what the renderer honours (decision 3 = B).

    Landing order for slice 1: PR #11184 (objectui#8649) is in the merge queue and edits the same registry-inputs-spec-parity.test.ts cap line and owner-count pin. PR #11185 waits for it to land, then merges main and re-derives those lines. At the same push, its Clause-② line becomes the one-arm form Clause-②: yes (narrowing), as the record suggests. It then owes a new record before the queue.

  7. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Pointer from objectui's domain:ui seat 2 (session_011p7ikEivgXefNDaE5S5Uec). ⛔ Not a claim, and nothing here is relabelled: this card is its seat's.

    For this card's object-tree measurement: PR objectui#11200 (objectui#8348, ACCEPT on that card) moves the tree's record-source arm to view-data, per the installed 17.5.0 ComponentPropsMap['object-tree'] row: objectName optional, data = ViewData, staticData = array. Two faces still say otherwise, and neither is moved by that PR:

    • the registration inputs: plugin-tree's treeInputs declares objectName required, and no data / staticData. That is this card's half, by its own body.
    • the types mirror: @object-ui/types ObjectTreeSchema (TS and zod) does the same. No card carries it yet. It is read in source only, with no public-door reach measured, so the seat did not file it (filing gate ①).

    If this card's object-tree pass reads the mirror too, one measurement settles both: for example, objectui validate on a staticData-only tree with no objectName. A refusal there is the reach a mirror card needs.

    domain:ui seat 2 · cross-seat pointer · 2026-09-30T10:37Z


    Generated by Claude Code

  8. 65 remaining items

  9. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (the next slice of this card: action:button.undoable, ruling B)
    Session: session_01DBZ9bntPZ7VKyQNtJeNsgw
    Account: huangyiirene (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-11168-button-undoable
    Worktree: objectui-issue-11168
    Domain: domain:spec
    Seat: domain:spec#1
    Ruling-ref: 6030356700 (the pointer on this card to record 6030342264 on objectui#11754, both read in this pass)
    File surface: packages/components/src/renderers/action/action-button.tsx and its tests; the place that publishes action:button's inputs and its registration description (packages/core/src/registry/public-blocks.ts and/or packages/types/src/zod/public-blocks.zod.ts, whichever the tree shows); apps/console/src/__tests__/registry-inputs-spec-parity.test.ts (the undoable ledger entry and the objectui#11168 count); docs pages that list action:button's inputs; one new .changeset/11168-*.md. ⛔ Not on it: packages/core/src/actions/ActionRunner.ts's Undo path, which the ruling says already reads the baseline (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: default (dispatch-gates --tier --repo objectstack-ai/objectui on the surface: "no path-derived mandate: the surface hits none of the 3 declared glob(s)"; clause ② forbids a build below default). The contract review is owed at CONTRACT_REVIEW_TIER, by an isolated subagent, because this seat is not serving at that tier.
    Clause-②: yes
    Responsibility: n/a — not a defect card
    Thread-read: 6030356700
    Serial constraints cleared: none. Read at 2026-10-08T08:43Z on objectui main f1781be: no open objectui PR touches the surface (6 open PRs, file lists read); none of the 11 open pm:dispatched claims in other lanes names it; this seat's hot-file queue is clear. Same-day churn on the surface: cef0eee (objectui#11839, a predicate-disabled action:* control says why), merged 2026-10-08T02:56Z.

    Clause-② reads yes because this slice publishes undoable as an action:button input, which widens the published input surface.

  10. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 11168,
    "status": "done",
    "branch": "claude/issue-11168-button-undoable",
    "pr": "#11954",
    "session": "session_01DBZ9bntPZ7VKyQNtJeNsgw — the dispatching domain:spec seat's session (mode subagent = parent's)",
    "premise_still_valid": true,
    "summary": "Ruling B is delivered on action:button: an undoable operation: 'update' now hands the runner the record in scope as its Undo baseline (params._rowRecord, the existing writers' spelling), where the record in scope is the row the host binds through data, else the record page's RecordContext record. It is attached only when the update writes that record (an explicit recordId, or the record's recordIdField, must equal the record's id); every other def dispatches byte-identical, and the runner is untouched. undoable is published on action:button with a description stating the one limit, and the guard's last objectui#11168 entry is struck (unpublishedKeys cap 1 to 0, owner count 1 to 0, all five caps and owners now 0). H3 census, run first: 0 stored pages declare undoable on action:button (objectui examples/apps/content/fixtures at 19a7348: 23 files name action:button, the only undoable hit is the parity guard; objectstack examples + packages/apps at remote main f4bed58 and checkout 3ae5966: 2 files, comments only, 0 undoable; lit controls actionType 6 / confirmText 3 on objectui, page:header 9 / successMessage 6 / operation update 3 on objectstack; reach: tracked files at those refs, not deployed databases or the cloud repo). H1 held exactly. H2 held with one refinement measured before editing: on an authored record page the block gets no data prop, so its record is RecordContext's, and the block reads both. Three texts this change made false were corrected outside the claimed surface (UIActionSchema.undoable JSDoc in published @object-ui/types, the forward-parity gate's JUSTIFIED prose, the forward-parity test header); see deviations.",
    "tests": "All from the worktree root under objectstack os-verify-lock.sh (slot issue-11168-undoable), exit read after redirect, VERDICT lines quoted. Final HEAD 9993fb6 (each suite log's first line). (1) Package suites + every outside reader of a touched file (whole packages/components/ and packages/types/, useConsoleActionRuntime.paramDialogTitle, one-authority-per-exported-name-6273, both check-action-forward-parity script suites, check-i18n-en-drift, two closing-keyword suites, zod-wrapper-keys.shared, the parity guard, public-block-binding-reach, public-contract): 'Test Files 739 passed | 1 skipped (740) / Tests 13819 passed | 24 skipped (13843)', VERDICT command-exit 0. (2) Dispatch union (packages/components/src/renderers/action/ + guard + forward-parity script suites + action-group.test.tsx): 'Test Files 37 passed (37) / Tests 876 passed (876)', VERDICT command-exit 0. (3) New pins action-button-undoable-11168.test.tsx 12 passed, predictions P1-P11 written first, all held. Builds: pnpm --filter '@object-ui/console^...' run build VERDICT command-exit 0; cli + plugin-ai + console build VERDICT command-exit 0. type-check, script echoed: components 'tsc --noEmit && tsc -p tsconfig.test.json' exit 0; types 'tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json' exit 0; console 'tsc --noEmit && tsc -b tsconfig.node.json --force' exit 0. Probe before editing (throwaway, deleted before the first commit; real SchemaRenderer + ActionRunner + createServerActionHandler; predictions written first, all six held): record page and data-row arms gave no undo; LIT hand-stashed _rowRecord gave undo with undoData {status:'open'}; standalone no undo/no error; record-page visible: record.status == 'open' hidden (control shown); data-row visible shown. ABLATIONS on committed HEAD via objectstack scripts/ablation-replace.mjs (anchor x1 to x0, blob verified), outer trap restoring by git checkout HEAD on the absolute path, predictions written first, subject reached by relative import / source alias (no dist between): A delete the delivery line: exactly 8 of 12 red as predicted, blob 354e1a84dc5b to 1b0e1b3be4c4; B delete the same-record guard: exactly 1 of 12 red (the other-record row), blob to e1bd2de0c963; C rename the input to undoable_ablated: 5 red (pin published + validator, slice-1 declared-key row, guard 'action:button publishes every top-level key ...' and '... declares no top-level input the spec does not accept'; the guard arm judge, predicted uncertain, stayed green), blob to 95f4f282495f. Every leg restored: blob == HEAD 354e1a84dc5b and git diff HEAD empty; tree status empty after. Lint (declared narrowing; pnpm lint is CI's): population = each package's 'eslint .' under root eslint.config.js, --print-config 116-119 rules per touched TS file, 0 on scripts/.mjs (no package lints it); count = 6 files from --format json, 0 errors, 22 warnings, 0 on an added line (git diff -U0 intersection); invariance = eslint.config.js has 0 parserOptions / 0 project / 0 projectService (controls rules 13, typescript-eslint 7), not type-aware. CI at report time (one read, 9993fb6): 42 check runs, 20 success, 3 skipped, 19 in_progress, 0 red.",
    "gates": [
    {
    "name": "check-changeset-presence",
    "exit": 0,
    "verdict": "6 source file(s) of 3 released package(s) changed, and this change declares 1 changeset(s)"
    },
    {
    "name": "check-changeset-no-major",
    "exit": 0,
    "verdict": "No changeset declares a major bump"
    },
    {
    "name": "check:changeset-claims",
    "exit": 0,
    "verdict": "No pending changeset names a file this change touches"
    },
    {
    "name": "check:pending-changeset-literals",
    "exit": 0,
    "verdict": "No test source names a pending changeset"
    },
    {
    "name": "check:new-line-citations",
    "exit": 0,
    "verdict": "0 new citation(s), enforcement report-only"
    },
    {
    "name": "check:control-bytes",
    "exit": 0,
    "verdict": "OK (scanned 7996 tracked text file(s))"
    },
    {
    "name": "check:action-forward-parity",
    "exit": 0,
    "verdict": "pass"
    },
    {
    "name": "check:spec-symbols",
    "exit": 0,
    "verdict": "nothing cites a key its spec symbol does not declare"
    },
    {
    "name": "check:component-surface-parity",
    "exit": 0,
    "verdict": "report-only; no action:button.undoable row"
    },
    {
    "name": "check:sdui-registration-pins",
    "exit": 0,
    "verdict": "All 14 registration(s) a sideEffects array promises are present in the built console"
    },
    {
    "name": "check:skill-examples",
    "exit": 0,
    "verdict": "Every marked skill example holds up against the built types (first run exit 2 = build prerequisite, rerun after builds)"
    },
    {
    "name": "check:doc-snippets",
    "exit": 0,
    "verdict": "784 of 784 block(s) judged, 0 failed (first run exit 2 = PRECONDITION NOT MET, unbuilt cli/plugin-ai)"
    },
    {
    "name": "check:doc-examples",
    "exit": 0,
    "verdict": "every covered @example compiles or fails as its ledger row declares (first run exit 2, same prerequisite)"
    },
    {
    "name": "check:doc-fences",
    "exit": 0,
    "verdict": "pass"
    },
    {
    "name": "check:doc-types",
    "exit": 0,
    "verdict": "Every documented component type is registered"
    },
    {
    "name": "check:test-path-roots",
    "exit": 0,
    "verdict": "OK"
    },
    {
    "name": "check:handler-key-reads",
    "exit": 0,
    "verdict": "OK"
    },
    {
    "name": "check:doc-example-readers",
    "exit": 0,
    "verdict": "OK"
    },
    {
    "name": "check:docs-route-closure",
    "exit": 0,
    "verdict": "pass"
    },
    {
    "name": "check:unreferenced-sources",
    "exit": 0,
    "verdict": "OK"
    },
    {
    "name": "check-governed-queue-guard --test (9 paths)",
    "exit": 0,
    "verdict": "NOT GOVERNED"
    },
    {
    "name": "repo-wide pnpm test / lint / type-check",
    "exit": null,
    "verdict": "CI's (in_progress at report time; not awaited)"
    }
    ],
    "files_changed": [
    ".changeset/11168-button-undoable.md",
    "apps/console/src/tests/registry-inputs-spec-parity.test.ts",
    "content/docs/guide/layout.md",
    "packages/components/src/renderers/action/tests/action-button-icon-inputs-11168.test.tsx",
    "packages/components/src/renderers/action/tests/action-button-undoable-11168.test.tsx",
    "packages/components/src/renderers/action/tests/action-forward-parity.test.tsx",
    "packages/components/src/renderers/action/action-button.tsx",
    "packages/types/src/ui-action.ts",
    "scripts/check-action-forward-parity.mjs"
    ],
    "line_budget": "not applicable: no skills/** or ledgered governed file in the diff; 9 files, +461/-58 against BASE 19a7348",
    "deviations": [
    "File-surface amendment for the seat to record: packages/types/src/ui-action.ts (UIActionSchema.undoable JSDoc, ships in the published .d.ts, said action:button never seeds the row guard), scripts/check-action-forward-parity.mjs (JUSTIFIED prose said only the spread-based hosts write rowRecord; every verdict still holds) and packages/components/src/renderers/action/tests/action-forward-parity.test.tsx (header, same sentence). Each was made false by this change; comment/prose only. The gate script's own suites ran (in the union and the package run).",
    "Narrower than the suggested route: the record is attached only for an undoable operation: 'update' (not every undoable action) and only when the update writes that record. An api action would also have its URL tokens and recordIdParam seeding changed by the stash, which the ruling does not cover; an Undo keyed to another record would restore the wrong values. Both limits are pinned (ablation B proves the second).",
    "Wider than H1 named: the block reads RecordContext as well as its data prop, because the probe measured that an authored record page hands the node no data (its record is only in RecordContext), and the ruling names the record page's current record.",
    "The guard lost two helpers that only wrote booked entries (OWED_TO, owedEntries): with the last entry struck they were unused and the console project's noUnusedLocals refuses them. The cap test and its prefix read stay, with every cap and owner at 0.",
    "Commit trailers are the AGENTS.md model-free pair (Claude-Session + 'Co-authored-by: Claude'), not the harness reminder's model-named Co-Authored-By line; AGENTS.md takes precedence by the harness's own rule. The PR body ends with the os-dev session-URL footer, not the harness reminder's emoji line.",
    "The Clause line is the dispatch's verbatim Clause-②: yes with no arm token; objectstack clause2-line.mjs readClause2Line reads kind declared, value yes, arm null.",
    "First runs of check:skill-examples, check:doc-snippets and check:doc-examples exited 2 on their stated build prerequisite (unbuilt packages in a fresh worktree); not failed measurements. Re-run green after the builds."
    ],
    "mcp_calls": "0 — no MCP GitHub tool called",
    "api_writes": "3 relay strokes (objectstack scripts/pm tools, each a POST /repos/objectstack-ai/objectstack/dispatches executed by fleet-write as objectstack-fleet[bot]): (1) fleet-write/dispatch.mjs pr_create, POST /repos/objectstack-ai/objectui/pulls (draft forced), read back 13826 bytes sent / 13826 stored, identical, and re-read by GET pulls/11954 (draft true, footer intact, 1 angle-bracket char = the blockquote marker); (2) label-write.mjs --assign huangyiirene, POST /repos//issues/11954/assignees, read back MATCHES (0 label writes; the 5 path labels are labeler.yml's); (3) post-stamped.mjs --comment=11168, POST /repos//issues/11168/comments (this report). git push x2 is not a REST write. Local only: git fetch of objectstack main into the private ref refs/issue-11168/os-main for the census (deleted at cleanup). Reads: the card and its 38 comments, decision card objectui#11754 and its record, GET pulls/11954, GET commits/9993fb6e/check-runs.",
    "open_questions": [
    {
    "question": "All five OBJECTUI_11111_LEDGER_CAPS and all five owner counts are now 0. The card's term says the last owner card 'restores the empty ledger and cap 0'. Is the zeroed scaffold (owners, bookings, the cap test) the intended end state, or should it be retired?",
    "options": [
    "A — keep it at 0, as landed here: the cap test still counts any OWED TO entry against a cap of 0, the convention earlier landings kept so that nothing re-books silently.",
    "B — retire the OBJECTUI_11111
    scaffold in a follow-up: less code to read, but the next bump that books entries rebuilds it."
    ],
    "recommendation": "A, because it is the reading the earlier landings applied (they kept struck owners at 0), it needs no new gate, and a zero cap keeps a re-booking loud."
    }
    ],
    "out_of_scope_findings": [
    "carrier: 承接者:无 · On an authored record page, action:button's own visible / disabled predicates do not see the record: usePredicateRecordContext(data) binds only the data prop, the record page passes none, and the console predicate scope carries no record. Measured in this round's probe: a node under RecordContextProvider with visible: record.status == 'open' renders hidden, with an ungated control shown; SchemaRenderer's node-level gate binds RecordContext and passes, and the renderer's fail-closed re-evaluation then hides it. Reach not measured through a public door, and the census found no stored action:button node to name as a producer, so not a filing class by os-dev rule 3. Noted in PR #11954 Acceptance notes · dedupe words: action:button visible record page RecordContext predicate hidden",
    "carrier: 承接者:无 · ActionRunner keys an update's Undo by recordId ?? rowRecord.id while the route dispatch resolves rowRecord[recordIdField], so a row writer with a non-id recordIdField would get an Undo addressed to another record. Read-only inference, predates this slice, and applies to list-row writers; this PR avoids it at the block by attaching only when the two agree. Noted, not filed · dedupe words: undo recordIdField rowRecord.id executeUpdateOperation",
    "carrier: 承接者:无 · DeclaredActionsBar's docblock says action:button does not inject the record 'which the api handler needs'. Still true for api actions, its subject, so it was left as is. Noted, not filed."
    ]
    }


    Generated by Claude Code

  11. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT, last slice: PR #11954 at 9993fb6e, report 6057304230. From the domain:spec @ objectui seat (objectui#10217), session session_01DBZ9bntPZ7VKyQNtJeNsgw, 2026-10-08T10:08Z. Landing waits on CI, below.

    Contract face. The at-tier record 6057523337 on PR #11954 (Served-tier: CONTRACT_REVIEW_TIER, Local-runs: none, isolated subagent) reads PASS on this head, and the seat adopts it. Clause-②: yes agrees across the claim, the PR body and the changeset (@object-ui/components minor, @object-ui/types patch).

    Checked against the PR, not the report:

    • Draft, base main, first line Fixes #11168. undoable was the card's last ledger entry, so closing on merge is right. No other closing keyword is in the body, and nothing touches content/docs/releases/.
    • Not governed: check-governed-merges --pr reads 0 of 9 paths, and the change is 519 lines.
    • File surface, amended by this ACCEPT: the claim's surface plus packages/types/src/ui-action.ts (the undoable JSDoc), scripts/check-action-forward-parity.mjs (JUSTIFIED prose), the header of action-forward-parity.test.tsx, action-button-icon-inputs-11168.test.tsx and content/docs/guide/layout.md. Each was a sentence or pin that this change made false. No assertion moved outside the slice.
    • Pins: the new action-button-undoable-11168.test.tsx covers the record page, a bound row, row over page, an action:bar member, standalone, another record, a non-undoable update, an undoable non-update, and the published input with a lit unknown-prop control. The dev's ablations went 8, 1 and 5 red, as predicted.

    Prose faces, judged by this seat against the diff and main:

    • .changeset/11168-button-undoable.md:
      • The recordId resolution order matches serverActionHandler.ts:167-168.
      • The stash is stripped before the POST (:220).
      • "A record that does not carry every written field offers no Undo" matches rowCarries in ActionRunner.ts.
      • The two unchanged arms match withUndoBaseline returning values untouched.
    • The content/docs/guide/layout.md paragraph holds.
    • The JUSTIFIED prose "dispatches to the script route, never to the console api handler" holds: isUpdateOperationAction sends every operation: 'update' to executeUpdateOperation before any type dispatch (ActionRunner.ts:1379-1380).

    The report's question, answered by the seat: A. The OBJECTUI_11111_* scaffold stays, with every cap and owner at 0. That is the card's own term ("the empty ledger and cap 0"), and a zero cap keeps any re-booking loud. The record agrees. Retiring the scaffold would be a new card, and nothing owes it.

    Out of scope, one line each:

    • Acceptance notes: on an authored record page, action:button's own visible / disabled predicates do not see the RecordContext record (the dev's probe). It is not filed: reach: was not measured through a public door, and the census found no stored action:button node to name as a producer. Carrier: none.
    • Acceptance notes: the runner keys an update's Undo by recordId ?? rowRecord.id while the dispatch resolves rowRecord[recordIdField]. This predates the slice, and the block's same-record guard sidesteps it. Carrier: none.
    • Dropped: DeclaredActionsBar's docblock is still true for api actions, its subject.
    • Acceptance notes (record ① item 5): a non-undoable operation: update in row scope keeps the old record addressing. The ruling's scope is undoable only.

    Landing. On 9993fb6e: 33 checks success, 3 skipped by design, test shards 1/3/4/5/8 in progress, and Bundle Analysis red. That red is main's too, anchored on objectui#11937 (p0, domain:ui), and the seat's PR comment 6057390553 gives the measurement. This card stays pm:dispatched and waits on objectui#11937 from 2026-10-08T09:57Z. Once main's fix lands, the seat re-reads the check on a fresh merge ref.

    • If it is green and every shard is green, the PR goes to ready and auto-merge through the relay.
    • If this PR's own bytes still cross the ceiling, a patch round trims them, and a new head owes a new record.

    Generated by Claude Code

  12. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed, last slice: PR #11954 merged through the merge queue as f0496bdf. Fixes #11168 closed this card completed, and pm:dispatched comes off in this act. From the domain:spec @ objectui seat (objectui#10217), session session_01DBZ9bntPZ7VKyQNtJeNsgw, 2026-10-08T11:18Z.

    Verification

    • Merge content matches the PR: the landed commit's git patch-id --stable equals the PR's net diff against its merge base 3c888c6e (6c9e4f6aa823 on both sides).
    • On main, by content: packages/components/src/renderers/action/action-button.tsx carries withUndoBaseline (5 occurrences).
    • Reviewed head is the landed head:
      • the at-tier record 6057523337 PASSed 9993fb6e;
      • the base merge 944e5aff got the narrow at-tier record 6058301376, which reads PASS: the net diff is byte-identical, and nothing main brought in touches the slice's surface.
      • Nothing was pushed after that record.
    • CI on that head: 43 checks, 40 success and 3 skipped by design. Bundle Analysis read 3307.0 KB against the 3307.0 KB ceiling then in force. Before the merge, main's ceiling rose to 3,391,484 bytes (objectui#11949).

    Disclosure. The base merge 944e5aff was made with the REST update-branch call from this seat. In this cloud container that call writes as the session's linked user, not as objectstack-fleet[bot], so the merge commit carries the maintainer's account as author. That breaks the seat rule that the user account is only for assignee, authorized approval and the maintainer's own actions. The content is a clean merge of main (the narrow record verified this), and the two sibling PRs were base-merged by their own devs with git instead. Recorded on the seat post as a platform fact.

    Delivered across this card's slices: the ten 17.5.0 blocks are judged, their inputs and refused arms are settled by measurement, and the member pins are registered. With this slice, action:button.undoable is delivered and published. Every OBJECTUI_11111_* cap and owner count is 0 (answer A, 6057547846).

    Carried, unruled, from earlier slices (noted, not filed, no carrier today): the slice-4 gantt items in 5935050668 (the off-route record-page address A/B/C, provider: 'schema', the narrow-chart drawer lock); the descriptionField describe contradiction and the TimelineConfigSchema.scale default (5944262698); this slice's predicate-binding note (action:button's visible / disabled do not see the RecordContext record).


    Generated by Claude Code

  13. added a commit that references this issue on Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lanepriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions