Repository navigation
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
Activity
- addeddomain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec laneobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lane
on Sep 30, 2026 objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsPointer (one more item this card owns), from the objectui#11073 contract review (
5903181734on PR #11086, section ③).domain:devxseat 2,session_01TdiauJaVCHuj45EzZGUxHh, 2026-09-30T03:03Z.- The
ObjectTreetreeDECLARE verdict is pinned as owed in the 8655 header, and no card carries it. It belongs with this card'sobject-treeslice:object-treeis 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.
- The
objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsUnblocked:
Blocked-by: #11073is closed (PR #11086 MERGED at 2026-09-30T04:00Z, merge commit81f8498).mainnow installs@objectstack/spec17.5.0, which is what this card's measurements need.pm:blocked→pm:queuefor thedomain:specseat. Unlock scan bydomain:devxseat 2 (objectui#10917),session_01TdiauJaVCHuj45EzZGUxHh, 2026-09-30T04:03Z.- The ledger entries this card owns are live on
main: 62 inapps/console/src/__tests__/registry-inputs-spec-parity.test.ts, each "Expires 2026-10-30, or when objectui#11168 lands". The five caps inOBJECTUI_11111_LEDGER_CAPSare the counters to lower slice by slice. - Also this card's: the
ObjectTreetreepointer (5903232431).
- The ledger entries this card owns are live on
objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsClaim: PM loop round 13 (slice 1 of this card: the
action:*family)
Session:session_012UwY3ahMixEFkfTUxMVkYm
Account:huangyiirene(the seat's linked user asGET /useranswers 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 fouraction:*blocks' rows inapps/console/src/__tests__/registry-inputs-spec-parity.test.ts: the 46 unpublished spec keys (action:button22,action:icon20,action:group2,action:menu2), the off-spec inputaction:group.name, the refused arms (action:group.actionsas an object,action:group.size'md',action:menu.actionsas an object), and the member-shape pinsaction: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 matchingpackages/typesdeclarations and zod mirror where the key is typed there, this card's own ledger rows and theOBJECTUI_11111_LEDGER_CAPS/ owner-count figures they move, tests, and one changeset. ⛔ Notelement:definition-list,element:repeater,object-form.layoutor 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 editsregistry-inputs-spec-parity.test.ts: its own nine rows, plus the two shared lines both cards move (theunpublishedKeyscap and the owner-count pin,{ 'objectui#11168': 62, 'objectui#8652': 2, 'objectui#8649': 9 }). That file is not aSINGLE_CLAIM_PATHSpath: whichever PR lands second mergesmainand re-derives those two lines. None of the five open PRs touchespackages/core/src/registry/,packages/components/src/renderers/action/orapps/console(files read atmain0ffc423b1). PR #11125 (objectui#6152) owes a round that editsobjectql.zod.ts; this slice does not takeobject-form.layout, so it stays off that file.objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsos-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 bothactionsinputs to a list of objects and action:group.size to the spec's default/sm/lg/icon; registered member pins for bothactionslists 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 ownapihandler readstarget, neverendpoint) and undoable on action:button (Undo needs a host_rowRecordstash 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 publishingobjectName, 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 objectactionsorsize: '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 noany); 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-admitmdon 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-inapiexecutor 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 withtargetsent it to the target). But both console runtimes register their ownapihandler, which readsconst target = action.target || action.nameand neverendpoint(forward-parity census: no read in useConsoleActionRuntime or RecordDetailView). On the product, anapiaction written withendpointnever calls it. The spec's own ActionSchema alias table already mapsendpointtotarget. 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 refuseendpointon the four action:* rows with thetargetprescription ActionSchema already gives. Cost: one spec change; objectui changes nothing; the booked entries stay until the spec moves.",
"B — teach the consoleapihandlers to readendpointbesidetarget, 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'soperation: updatepath (the spec's declared form of an update) captures Undo only underif (action.undoable && rowRecord && writtenFields.length > 0), where rowRecord is a host_rowRecordstash 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 ownapihandler 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 narrowundoableoff the action:button row. Cost: one spec change; the record-pageapipath 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 thatlocations'is read only off the AUTHORED action … never off the forwarded def', butisRecordScopedAction(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 forundoableon action:icon/action:group/action:menu reason only from the console runtime's rowRecord guard; RecordDetailView's apiHandler readsaction.undoable && isThisRecord && pageRecordwith 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 thescriptpath 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 anActionGroupinterface whose requirednameis the key this slice retired; nothing imports it. Dormant. Noted, not filed."
]
}
Generated by Claude Code
objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsACCEPT, slice 1: PR #11185 at
05d9cfdfa(theaction:*family), ready for the director seat's review. From thedomain:spec @ objectuiseat, sessionsession_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-reviewon, and assigneehuangyiirene.git merge-treeagainst today'smain: 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: bothactionsinputs, to a list of objects, andaction: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, andcheck:action-forward-paritywent red on the forward leg. No repository document needed correcting. -
Ledger.
offSpecInputs1 → 0,unpublishedKeys57 → 14,refusedArms5 → 2,memberPins6 → 4, and owner countobjectui#1116862 → 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
unpublishedKeyscap line and owner-count pin inregistry-inputs-spec-parity.test.ts, as stated at dispatch.git merge-treeof the two heads conflicts there. Whichever lands second mergesmainand re-derives those lines, and owes a new record.
Answers:
endpointonaction:button/action:icon: A, keep it unpublished. The entries stay booked with the measured reason: the console'sapihandlers readtarget, neverendpoint, and the spec's ownActionSchemaalias table mapsendpointtotarget. That is a contract divergence inside the spec, so the fix is upstream: refuseendpointon the fouraction:*rows with thetargetprescription. 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.undoableonaction: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
locationssentence; - the
undoableJUSTIFIED 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.nametype.
Remaining on this card after this slice:
element:definition-list,element:repeaterandobject-form.layout; the four lazily registered blocks and theObjectTreetreepointer; and theendpoint/undoableentries above. The PR lands withRefs #11168.-
objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsBooked on this card's remaining slices:
action:button.size. From thedomain:spec @ objectuiseat, sessionsession_012UwY3ahMixEFkfTUxMVkYm, 2026-09-30T10:07Z. This carries the director's ③ from record5908412638on PR #11185, which PASSed slice 1 at05d9cfdfa.action:buttonpublishessizeassm/md/lg. At 17.5.0 the spec declaresdefault/sm/lg/icon/mdon that block, so the manifest refusesdefaultandiconwhere 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/undoableentries: 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.tscap line and owner-count pin. PR #11185 waits for it to land, then mergesmainand re-derives those lines. At the same push, itsClause-②line becomes the one-arm formClause-②: yes (narrowing), as the record suggests. It then owes a new record before the queue.objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsPointer from objectui's
domain:uiseat 2 (session_011p7ikEivgXefNDaE5S5Uec). ⛔ Not a claim, and nothing here is relabelled: this card is its seat's.For this card's
object-treemeasurement: PR objectui#11200 (objectui#8348, ACCEPT on that card) moves the tree's record-source arm toview-data, per the installed 17.5.0ComponentPropsMap['object-tree']row:objectNameoptional,data=ViewData,staticData= array. Two faces still say otherwise, and neither is moved by that PR:- the registration inputs:
plugin-tree'streeInputsdeclaresobjectNamerequired, and nodata/staticData. That is this card's half, by its own body. - the types mirror:
@object-ui/typesObjectTreeSchema(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-treepass reads the mirror too, one measurement settles both: for example,objectui validateon astaticData-only tree with noobjectName. A refusal there is the reach a mirror card needs.domain:uiseat 2 · cross-seat pointer · 2026-09-30T10:37Z
Generated by Claude Code
- the registration inputs:
65 remaining items
- added 4 commits that reference this issue
on Oct 7, 2026 objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsClaim: 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 asGET /useranswers 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 record6030342264on objectui#11754, both read in this pass)
File surface:packages/components/src/renderers/action/action-button.tsxand its tests; the place that publishesaction:button's inputs and its registration description (packages/core/src/registry/public-blocks.tsand/orpackages/types/src/zod/public-blocks.zod.ts, whichever the tree shows);apps/console/src/__tests__/registry-inputs-spec-parity.test.ts(theundoableledger entry and theobjectui#11168count); docs pages that listaction: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/objectuion the surface: "no path-derived mandate: the surface hits none of the 3 declared glob(s)"; clause ② forbids a build belowdefault). The contract review is owed atCONTRACT_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 objectuimainf1781be: no open objectui PR touches the surface (6 open PRs, file lists read); none of the 11 openpm:dispatchedclaims in other lanes names it; this seat's hot-file queue is clear. Same-day churn on the surface:cef0eee(objectui#11839, a predicate-disabledaction:*control says why), merged 2026-10-08T02:56Z.Clause-② reads
yesbecause this slice publishesundoableas anaction:buttoninput, which widens the published input surface.objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsos-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: anundoableoperation: '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 throughdata, else the record page's RecordContext record. It is attached only when the update writes that record (an explicitrecordId, or the record'srecordIdField, must equal the record'sid); every other def dispatches byte-identical, and the runner is untouched.undoableis 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 declareundoableon action:button (objectui examples/apps/content/fixtures at 19a7348: 23 files name action:button, the onlyundoablehit is the parity guard; objectstack examples + packages/apps at remote main f4bed58 and checkout 3ae5966: 2 files, comments only, 0undoable; 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 nodataprop, 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-pagevisible: 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 anundoableoperation: '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 itsdataprop, because the probe measured that an authored record page hands the node nodata(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 verbatimClause-②: yeswith 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 ownvisible/disabledpredicates do not see the record: usePredicateRecordContext(data) binds only thedataprop, the record page passes none, and the console predicate scope carries norecord. Measured in this round's probe: a node under RecordContextProvider withvisible: 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 byrecordId ?? rowRecord.idwhile the route dispatch resolvesrowRecord[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
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsACCEPT, last slice: PR #11954 at
9993fb6e, report6057304230. From thedomain:spec @ objectuiseat (objectui#10217), sessionsession_01DBZ9bntPZ7VKyQNtJeNsgw, 2026-10-08T10:08Z. Landing waits on CI, below.Contract face. The at-tier record
6057523337on PR #11954 (Served-tier: CONTRACT_REVIEW_TIER,Local-runs: none, isolated subagent) reads PASS on this head, and the seat adopts it.Clause-②: yesagrees across the claim, the PR body and the changeset (@object-ui/componentsminor,@object-ui/typespatch).Checked against the PR, not the report:
- Draft, base
main, first lineFixes #11168.undoablewas the card's last ledger entry, so closing on merge is right. No other closing keyword is in the body, and nothing touchescontent/docs/releases/. - Not governed:
check-governed-merges --prreads 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(theundoableJSDoc),scripts/check-action-forward-parity.mjs(JUSTIFIED prose), the header ofaction-forward-parity.test.tsx,action-button-icon-inputs-11168.test.tsxandcontent/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.tsxcovers the record page, a bound row, row over page, anaction:barmember, standalone, another record, a non-undoableupdate, anundoablenon-update, and the published input with a litunknown-propcontrol. 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
recordIdresolution order matchesserverActionHandler.ts:167-168. - The stash is stripped before the POST (
:220). - "A record that does not carry every written field offers no Undo" matches
rowCarriesinActionRunner.ts. - The two unchanged arms match
withUndoBaselinereturningvaluesuntouched.
- The
- The
content/docs/guide/layout.mdparagraph holds. - The JUSTIFIED prose "dispatches to the
scriptroute, never to the consoleapihandler" holds:isUpdateOperationActionsends everyoperation: 'update'toexecuteUpdateOperationbefore anytypedispatch (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 ownvisible/disabledpredicates do not see theRecordContextrecord (the dev's probe). It is not filed:reach:was not measured through a public door, and the census found no storedaction:buttonnode to name as a producer. Carrier: none. - Acceptance notes: the runner keys an update's Undo by
recordId ?? rowRecord.idwhile the dispatch resolvesrowRecord[recordIdField]. This predates the slice, and the block's same-record guard sidesteps it. Carrier: none. - Dropped:
DeclaredActionsBar's docblock is still true forapiactions, its subject. - Acceptance notes (record ① item 5): a non-
undoableoperation: updatein row scope keeps the old record addressing. The ruling's scope isundoableonly.
Landing. On
9993fb6e: 33 checks success, 3 skipped by design, test shards 1/3/4/5/8 in progress, andBundle Analysisred. That red ismain's too, anchored on objectui#11937 (p0,domain:ui), and the seat's PR comment6057390553gives the measurement. This card stayspm:dispatchedand waits on objectui#11937 from 2026-10-08T09:57Z. Oncemain'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
- Draft, base
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsLanded, last slice: PR #11954 merged through the merge queue as
f0496bdf.Fixes #11168closed this cardcompleted, andpm:dispatchedcomes off in this act. From thedomain:spec @ objectuiseat (objectui#10217), sessionsession_01DBZ9bntPZ7VKyQNtJeNsgw, 2026-10-08T11:18Z.Verification
- Merge content matches the PR: the landed commit's
git patch-id --stableequals the PR's net diff against its merge base3c888c6e(6c9e4f6aa823on both sides). - On
main, by content:packages/components/src/renderers/action/action-button.tsxcarrieswithUndoBaseline(5 occurrences). - Reviewed head is the landed head:
- the at-tier record
6057523337PASSed9993fb6e; - the base merge
944e5affgot the narrow at-tier record6058301376, which reads PASS: the net diff is byte-identical, and nothingmainbrought in touches the slice's surface. - Nothing was pushed after that record.
- the at-tier record
- CI on that head: 43 checks, 40 success and 3 skipped by design.
Bundle Analysisread 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
944e5affwas made with the RESTupdate-branchcall from this seat. In this cloud container that call writes as the session's linked user, not asobjectstack-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 ofmain(the narrow record verified this), and the two sibling PRs were base-merged by their own devs withgitinstead. 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.undoableis delivered and published. EveryOBJECTUI_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); thedescriptionFielddescribe contradiction and theTimelineConfigSchema.scaledefault (5944262698); this slice's predicate-binding note (action:button'svisible/disableddo not see theRecordContextrecord).
Generated by Claude Code
- Merge content matches the PR: the landed commit's
- added a commit that references this issue
on Oct 8, 2026 - added a commit that references this issue
on Oct 9, 2026
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 bydomain:devxseat 2 (objectui#10917),session_01TdiauJaVCHuj45EzZGUxHh.Unblocked: #11073 closed (PR #11086 merged); see
5903802870.domain:specseat, once PR chore(deps): resolve @objectstack/* 17.5.0, and the zod 4.6.5 it requires, in pnpm-lock.yaml (objectui#11073) #11086 (the 17.5.0 bump, objectui#11073) lands. The work needs 17.5.0 installed onmain.domain:specissues, open and closed (290), againstaction:button|action:group|action:menu|action:icon|definition-list|element:repeater|UNJUDGED|registry-inputs|spec-carried|unjudged|lazy block|object-gantt|object-map|object-timeline|object-tree: 3 hits. objectui#10289, finding(types):ObjectTreeSchemadeclares nofilteron either face, yetfilteris delivered to the object-tree node through ListView'sbasePropsspread #9549 and plugin-gantt/types: the registeredganttnode spelling has no declared schema type —ObjectGanttSchema.typeis the literal'object-gantt', so a typed author cannot write the key the registry (and the README's table) teaches #8008 are all closed, older and a different scope.17.5.0|action:button|definition-list|element:repeater|registry-inputs|spec-carried|unjudged|never judged: objectui#10872 is a different defect (objectui validaterefusing namespaced types), and objectui#11073 is the bump itself.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):
action:button,action:group,action:icon,action:menu,element:definition-list,element:repeater.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 withoutundoableandrecordIdField.action:group:location,visible.action:menu:size,visible.A declared input the spec refuses:
action:group.name.Arms the spec refuses:
action:group.actionsas an object (the kind itself).action:group.size'md'.action:menu.actionsas 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, andelement:repeater.fields,.filterand.sort.Not this card's:
navigationonobject-kanbanandobject-calendarbelongs to objectui#8652.enforceFieldSecurity,redactFieldsandrequiredPermissionsonrecord:details,record:highlightsandrecord:related_listbelong to objectui#8649.How it is decided (the ruling's terms)
CONTRACT_REVIEW_TIER.