Repository navigation
spec(ui): an action:group / action:menu member refuses an object params on a non-api type at the gate, with the member prescription — the rows' value ratchet for container members (from objectstack-ai/objectui#11638) #21855
Description
Activity
- addedbugSomething isn't workingSomething isn't workingarea:studioChanging a running app without code — authoring, publish, docs and the portalChanging a running app without code — authoring, publish, docs and the portaland removed
on Oct 5, 2026 objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 (the card's step, as triage filed it) · 2026-10-05T09:18Z
Session:session_01T9u38rswFp5Rw8DswRUReJ
Account:os-project-manager(the seat's linked user asGET /useranswers it; the card's assignee from this act)
Branch:claude/issue-21855-member-params-object-refused
Worktree:objectstack-issue-21855
Domain:domain:spec
Seat:domain:spec#1(seat post #6017)
File surface (atorigin/main5b2d189e28; stop on breach and explain in the report):- The inventory first, re-run before writing: every
action:group/action:menumember that authors an objectparamson a non-apitype. Measure it in this repo (examples, fixtures, docs, skills), in objectui at the pin.objectui-shaand on itsmain, and where reachable in hotcrm and cloud. If any writer is found, stop and report before writing. packages/spec/src/ui/component.zod.ts: on container members of a non-apitype,paramsaccepts the array (ActionParam[]) form only. An object is refused at the gate, and the refusal carries the member prescription: author anaction:buttonfor static values. ⛔ Theapimember's window (to 18) is untouched, and ⛔ no new key.- Its tests: the object spelling is refused with the prescription; the array form and the
apiwindow still pass; the census row moves. - The ADR-0087 kit this narrowing owes, as
check:adr-0087-registrationand the precedents [finding] spec(report): atype: 'joined'report whose blocks bind nodatasetparses and passesobjectstack validate, while ReportSchema's own refinement comment and reports.mdx say "each block dataset-bound" #21702 (PR feat(spec)!: a joined report refuses a block that binds no dataset, at blocks[i].dataset, naming the block (#21702) #21712) and spec(automation): FlowSchema refuses a create_record, update_record or delete_record node whose static objectName is a stored-metadata family table (the save-time half of #21624) #21654 (PR feat(spec)!: FlowSchema refuses a create_record, update_record or delete_record node whose static objectName is a stored-metadata table, with the runtime's prescription (#21654) #21687) decide it:- a D3 entry under
migrations/entries/semantic/18.*, with itsmigrations/registry.tsstep-18 region and the next free order re-read onmain; - the regenerated
spec-changes.jsonand upgrade guide; - the changeset at the level the narrowing needs.
- a D3 entry under
- Regenerated artifacts are regenerated with the repo's tooling, never by hand.
- ⛔ No objectui file, no
content/docs/releases/.
Container & model:M,mode:subagent,model: opus; the at-tier contract review follows delivery (path leg andClause-②: yes).
Clause-②: yes (narrowing)
Thread-read: none
Serial constraints cleared: none of the 7 open PRs touchesui/component.zod.ts,migrations/registry.tsor a step-18 entry (scan at this stamp).domain:servicesdeclared a possible step-18 writer for security(data): a by-id write answers 403 for a row the caller cannot read and 404 for an id that does not exist, for principals the write pre-image check does not bind: an existence signal the read door withholds #21771 (5986535721); whichever lands second mergesmainand keeps both entries.
- The inventory first, re-run before writing: every
objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 21855,
"status": "done",
"branch": "claude/issue-21855-member-params-object-refused",
"pr": "#21869",
"head": "810b1c4b18",
"session": "session_01T9u38rswFp5Rw8DswRUReJ — this run (mode:subagent; the claim 5991597511 names this session and this branch; the Claude-Session line on every commit)",
"premise_still_valid": true,
"summary": "An action:group / action:menu member's params now takes the array form (ActionParam[], the input list) only, unless the member's type is api: any other params value on a non-api member (object, string, number, null; an absent type included) is refused at the component-props gate as a custom issue at actions.N.params, with the member prescription (author an action with static parameter values as its own action:button node, whose params object carries them; bodyExtra for an api request body). It is a module-private superRefine (actionContainerMemberParamsFitType) on both member builders in packages/spec/src/ui/component.zod.ts; params stays z.unknown() (the runner ledger line holds), the api window and the array's elements are untouched, no key, shape or export moves, and the unknown-key refusal stays terminal. The ADR-0087 kit: D3 entry ui-action-group-menu-member-params-array-only at step 18 with its STEP18_RATIONALE fragment at order 83 (highest on main 82, re-read at 5b2d189 and 2df3d13), registry region from gen:migration-registry, one BREAKING minor changeset carrying 'Clause-②: yes (narrowing)' and the registered marker; spec-changes.json and the upgrade guide needed no regeneration (both project the registry only up to the current major 17; check:spec-changes and check:upgrade-guide green untouched, as on both precedents); the two dropped member refinements declared in dropped-refinements.baseline.json as build-schemas printed them; component.mdx regenerated by check:generated --fix. Census re-run before writing, lit: zero writers in objectstack, objectui (pin and main) and hotcrm; cloud not reachable. Draft PR #21869 opened with 'Fixes #21855' and the Clause-② line, PR assignee os-project-manager; main was merged once (no rebase), nothing touching these files.",
"census": {
"instrument": "scratchpad census.cjs: a TypeScript-AST walk over .ts/.tsx/.js/.jsx/.mjs/.cjs/.mts/.json and fenced Markdown code (YAML as text). Pass 1: every params key in every file naming action:group or action:menu, with value kind and owner type, same-file consts resolved, members auto-classified when the actions owner (flat, inside a node's properties bag, or via a same-file const array) carries the block type. Pass 2: every non-array params on an element of ANY actions array corpus-wide. Every non-array hit read by hand (helper positions such as mount(surface, entry) and schema(member({...})) resolved that way). The scoped file counts equal git grep -l counts (objectstack 24, objectui 89).",
"lit_control": "A planted fixture (scratchpad control/): four non-api object members (flat, inside properties, through a const array, in a Markdown fence) plus one api member and one array member; all six found and classified. The hand-read loose pass also reached objectui's own helper-position drop probes.",
"rows": [
"objectstack 5b2d189 (base): 10069 files, 24 naming a block, 32 params keys, 23 non-array, 0 container members with a non-array params on a non-api type. The 23: inline element:button action conversion fixtures (conversions/registry.ts), schema source (action.zod.ts, component.zod.ts), the liveness ledger's params row, CHANGELOG quotations, one properties.params refusal probe (component-report-items-action-members-typed.pin.test.ts:325). No example, doc, skill or fixture writes it.",
"objectui .objectui-sha pin 0abd4f9f8: 7451 files, 89 naming a block, 47 params keys, 41 non-array, 0 writers. The only such members are objectui's own drop probes: action-entry-object-params-10462.test.tsx:144 (describe.each over the surfaces, navigate_edit) and :193, action-container-member-params-10290.test.tsx:196; their type api controls (:170) stay accepted. The rest: renderer source, CHANGELOG quotations, action:button / element:button / page:header / properties.params tests.",
"objectui main f1a177c41: identical census (zero hits differ, zero line moves); components/src/renderers/action/ byte-identical to the pin and to 2e818d0b5 (git diff --quiet exit 0); the pin is an ancestor of main (merge-base --is-ancestor exit 0).",
"hotcrm 4054ec2680 (shallow clone): 888 files, 0 naming a block, 0 hits in pass 2.",
"cloud: NOT REACHABLE — git ls-remote asks for credentials, GET /repos/objectstack-ai/cloud answers 403 (not enabled for this session), add_repo read answers 'you don't have access'."
],
"verdict": "Zero writers of the refused spelling: the change narrows a zero-writer spelling, so Clause-②: yes (narrowing) holds. Deployed metadata NOT MEASURED."
},
"tests": "All at head 810b1c4 (the merge of main 2df3d13) unless noted. (1) New pin packages/spec/src/ui/component-action-member-params-array-only.pin.test.ts, 39 tests: §1 the refusal on both containers, each asserting exactly [{ code: 'custom', path: 'actions.N.params' }] (object on navigate_edit / absent type / url, empty object, string, number, null; second-member index), plus message pins naming the container, the member's type and the action:button prescription; §2 the accept set byte-identical (arrays on non-api and api members, empty array, api object and string params, no params, bodyExtra) with a CONTROL that action:button / action:icon keep their object params; §3 an unknown key beside an object params draws the unknown-key refusal alone; §4 the D3 entry registered with no conversion and named by the step-18 rationale. (2) Ablation, predicted first (18 red = all of §1, 21 green, neighbour pins untouched), at 7a28c51, scratchpad ablate.sh with its own EXIT INT TERM trap on the absolute path: node scripts/ablation-replace.mjs replaced the guard with a bare return, anchor x1 → x0, blob d8565fd7a8 → 930000300e; observed over the three pins 'Tests 18 failed | 160 passed (178)', exactly the 18 §1 cases; restore blob d8565fd7a8 == HEAD and git diff HEAD empty (component.zod.ts blob unchanged since, through the merge). The pin imports ./component.zod relatively (src, no dist). (3) Public door: lint's validateComponentProps (src) over this branch's built spec: a navigate_edit group member and a no-type menu member with an object params each give one component-props-invalid finding (warning) at pages[0].regions[0].components[0].properties.actions.0.params with the message; controls (array on script, object on api, object on action:button node) give 0 findings. Before-state: the card's own reading (the door reported nothing). (4) pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2 under the lock: VERDICT command-exit 0 — Test Files 616 passed (616), Tests 18426 passed | 1 todo. Pre-merge at a6f2307 both projects (local + repo): Test Files 669 passed (669), Tests 19325 passed | 1 todo. (5) pnpm --filter @objectstack/spec typecheck at a6f2307: exit 0 (tsc --noEmit, check:scripts-typecheck, check:test-typecheck OK 52 files / 246 errors / 135 signatures held); tsc -p tsconfig.test.json --listFilesOnly names the new pin; the D3 entry is in the tsconfig.json program. (6) pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2: Test Files 119 passed (119), Tests 5627 passed. (7) spec build + check:generated: All 15 generated artifacts are up to date (pre-merge --fix proved check:docs the only stale one and regenerated only it). (8) eslint --no-inline-config --format json over the 4 changed .ts files: 4 files, 0 errors, 0 warnings; population = eslint.config.mjs's **/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs} block (none ignored); invariance: the config enables no type-aware linting (no parserOptions.project, eslint.config.mjs:328), so the diff moves no verdict on an untouched file.",
"gates": {
"head": "810b1c4b18",
"derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths): exit 0, 114 commands, merge base 2df3d13, 7 paths (the first derivation at a6f2307, pre-merge, warned STALE TREE; re-derived after the merge)",
"reconcile": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran ran-final.list :: exit 0 — 114 derived, 114 run, 0 NOT-MEASURED, 0 UNRUN (a DERIVED zero: all 114 recorded an exit code, none is 3); all 114 exit 0",
"reruns_kept_apart": "First run exit 3 (PREREQUISITE NOT MET, workspace packages unbuilt): pnpm --filter @objectstack/lint run check:doc-formula-expressions; pnpm --filter @objectstack/lint run check:doc-security-posture; pnpm --filter @objectstack/spec run check:skill-examples; pnpm check:docs-transcript-drift; pnpm check:dual-build-cjs-loads; pnpm check:lean-entry-closure. After turbo run build --filter=!@objectstack/docs --concurrency=2 (60 of 65 tasks; @objectstack/hono#build failed its DTS-emitted check though dist/index.d.ts was on disk right after, and a rebuild went green 31/31; the adapter is outside this diff) all six re-ran at the same head with exit 0, each reaching its own verdict line. check:type-check-debt was cut by the 580s chunk timeout on its first attempt (no exit recorded) and re-ran alone with exit 0.",
"exit_codes": {
"node scripts/check-adr-0087-registration.mjs --base origin/main": 0,
"node scripts/check-adr-0087-registration.mjs --self-test": 0,
"node scripts/check-changeset-no-major.mjs --base origin/main": 0,
"node scripts/check-changeset-no-major.mjs --self-test": 0,
"node scripts/check-ci-filter-parity.mjs": 0,
"node scripts/check-closing-keyword-parity.mjs": 0,
"node scripts/check-closing-keyword-parity.mjs --self-test": 0,
"node scripts/check-comment-mask-adoption.mjs": 0,
"node scripts/check-comment-mask-adoption.mjs --self-test": 0,
"node scripts/check-comment-mask-corpus.mjs": 0,
"node scripts/check-dev-prereqs.mjs --self-test": 0,
"node scripts/check-doc-frontmatter.mjs": 0,
"node scripts/check-doc-frontmatter.mjs --self-test": 0,
"node scripts/check-doc-route-spelling.mjs --advisory": 0,
"node scripts/check-doc-route-spelling.mjs --self-test": 0,
"node scripts/check-docs-section-name.mjs": 0,
"node scripts/check-docs-section-name.mjs --self-test": 0,
"node scripts/check-dts-emitted.mjs --self-test": 0,
"node scripts/check-empty-changeset.mjs --base origin/main": 0,
"node scripts/check-empty-changeset.mjs --self-test": 0,
"node scripts/check-issue-citations.mjs": 0,
"node scripts/check-keyed-text-bounds.mjs": 0,
"node scripts/check-keyed-text-bounds.mjs --self-test": 0,
"node scripts/check-platform-object-tenancy-census.mjs": 0,
"node scripts/check-platform-object-tenancy-census.mjs --self-test": 0,
"node scripts/check-plugin-teardown-shape.mjs": 0,
"node scripts/check-plugin-teardown-shape.mjs --self-test": 0,
"node scripts/check-registry-log-declared.mjs": 0,
"node scripts/check-registry-log-declared.mjs --self-test": 0,
"node scripts/check-rest-log-spy-declared.mjs": 0,
"node scripts/check-rest-log-spy-declared.mjs --self-test": 0,
"node scripts/check-section-landing-index.mjs": 0,
"node scripts/check-section-landing-index.mjs --self-test": 0,
"node scripts/check-spec-docblock-symbol-anchors.mjs": 0,
"node scripts/check-spec-docblock-symbol-anchors.mjs --self-test": 0,
"node scripts/check-system-context-census.mjs": 0,
"node scripts/check-system-context-census.mjs --self-test": 0,
"node scripts/check-undeclared-dep-imports.mjs": 0,
"node scripts/check-undeclared-dep-imports.mjs --self-test": 0,
"node scripts/docs-audit/check-affected-docs.mjs": 0,
"node scripts/docs-audit/check-drift-comment.mjs": 0,
"node scripts/pm/release-rehearsal-clone.mjs --self-test": 0,
"node scripts/release-pending-publish.mjs --self-test": 0,
"pnpm --filter @objectstack/spec run check:api-surface": 0,
"pnpm --filter @objectstack/spec run check:authorable-surface": 0,
"pnpm --filter @objectstack/spec run check:browser-reachable-entries": 0,
"pnpm --filter @objectstack/spec run check:docs": 0,
"pnpm --filter @objectstack/spec run check:dual-source-exports": 0,
"pnpm --filter @objectstack/spec run check:duration-unit-keys": 0,
"pnpm --filter @objectstack/spec run check:empty-state": 0,
"pnpm --filter @objectstack/spec run check:entry-nameability": 0,
"pnpm --filter @objectstack/spec run check:export-origins": 0,
"pnpm --filter @objectstack/spec run check:exported-any": 0,
"pnpm --filter @objectstack/spec run check:generated": 0,
"pnpm --filter @objectstack/spec run check:liveness": 0,
"pnpm --filter @objectstack/spec run check:llms-txt": 0,
"pnpm --filter @objectstack/spec run check:migration-registry": 0,
"pnpm --filter @objectstack/spec run check:objectui-pin-citations": 0,
"pnpm --filter @objectstack/spec run check:skill-refs": 0,
"pnpm --filter @objectstack/spec run check:spec-changes": 0,
"pnpm --filter @objectstack/spec run check:strictness-ledger": 0,
"pnpm --filter @objectstack/spec run check:upgrade-guide": 0,
"pnpm --filter @objectstack/spec run check:variant-docs": 0,
"pnpm --filter @objectstack/spec run check:yaml-examples": 0,
"pnpm check:changeset-gate-self-tests": 0,
"pnpm check:corpus-claim-drift": 0,
"pnpm check:cross-package-test-inputs": 0,
"pnpm check:dispatcher-error-vocabulary": 0,
"pnpm check:doc-anchors": 0,
"pnpm check:doc-authoring": 0,
"pnpm check:docs-audit-scope": 0,
"pnpm check:docs-redirects": 0,
"pnpm check:docs-single-h1": 0,
"pnpm check:docs-spec-enumerations": 0,
"pnpm check:driver-memory-census": 0,
"pnpm check:dts-closure": 0,
"pnpm check:engine-double-contract": 0,
"pnpm check:future-spec-major": 0,
"pnpm check:gitlink-declared": 0,
"pnpm check:issue-citations": 0,
"pnpm check:logger-receiver-detach": 0,
"pnpm check:merge-driver": 0,
"pnpm check:nul-bytes": 0,
"pnpm check:objectql-double-limit": 0,
"pnpm check:objectui-changeset": 0,
"pnpm check:org-identifier": 0,
"pnpm check:page-declaration-shape": 0,
"pnpm check:pm-changeset-deadline-census": 0,
"pnpm check:pm-prior-rulings": 0,
"pnpm check:pm-widening-tells": 0,
"pnpm check:published-files": 0,
"pnpm check:published-readme-links": 0,
"pnpm check:query-options-erasure": 0,
"pnpm check:quick-reference-counts": 0,
"pnpm check:react-page-adapter-contract": 0,
"pnpm check:refd-timer-probe": 0,
"pnpm check:role-word": 0,
"pnpm check:skill-identifier-liveness": 0,
"pnpm check:slot-lookup": 0,
"pnpm check:sourcemap-no-sources-content": 0,
"pnpm check:spec-parsed-alias": 0,
"pnpm check:test-source-alias": 0,
"pnpm check:tier-file-adoption": 0,
"pnpm check:type-check-coverage": 0,
"pnpm check:vendor-version-stamps": 0,
"pnpm check:watch-hint-literal": 0,
"pnpm check:where-matcher": 0,
"pnpm check:type-check-debt": 0,
"pnpm --filter @objectstack/lint run check:doc-formula-expressions": 0,
"pnpm --filter @objectstack/lint run check:doc-security-posture": 0,
"pnpm --filter @objectstack/spec run check:skill-examples": 0,
"pnpm check:docs-transcript-drift": 0,
"pnpm check:dual-build-cjs-loads": 0,
"pnpm check:lean-entry-closure": 0
}
},
"mcp_calls": "1 — mcp__claude-code-remote__add_repo (objectstack-ai/cloud, access read) for the census, refused 'you don't have access'; no MCP GitHub tool called, no MCP write",
"api_writes": "2 REST writes so far plus this comment = 3, each through the fleet-write relay as one repository_dispatch (POST /repos/objectstack-ai/objectstack/dispatches, 204), executed as objectstack-fleet[bot]: (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls, draft #21869 (14958 bytes sent, 14958 stored, identical; an independent REST read: draft true, head 810b1c4, body byte-equal to the local file), run 37302715056; (2) label-write.mjs --issue 21869 --assign os-project-manager → POST /repos//issues/21869/assignees, read back MATCHES (no label written; the labeler's documentation / size/m / tests / tooling / protocol:ui left as they are; skip-changeset's criterion does not hold, the spec publishes), run 37302830042; (3) this os-dev-report via post-stamped.mjs --comment=21855 → POST /repos//issues/21855/comments. Plus git pushes (the empty-branch probe, six commits and the merge of main; not REST). Reads: single-resource REST GETs of the card, its comments, the cited comments, objectui#11638 and its comments, and the PR.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: whoever ends the inline-action api payload window at protocol 18 · noted in the PR's Acceptance notes, not filed — an api member's object params stays accepted by the new refinement's type === 'api' arm (as the card requires); when that window ends, that arm is the one line to move, together with objectui's readActionEntryParamValues api branch",
"carrier: objectstack-ai/objectui#11638 as re-scoped by triage (5991243780) · noted, not filed — readMemberStaticParamValues (static-params.ts:142-148 at the pin 0abd4f9f8) still reads a member's properties.params, which the spec refuses; already carried there",
"carrier: none · noted in the PR's Acceptance notes, not filed — packages/spec/dropped-refinements.baseline.json measured.refinementSitesThatDidProject reads 369 while this build prints '450 refinement site(s) DID reach the file'; no gate reads that number; left as found"
],
"deviations": [
"files_changed and line_budget are measured against the merged main commit 2df3d13 (git diff 2df3d13...HEAD), not the dispatch's BASE...HEAD: after the merge of main, BASE...HEAD (merge base 5b2d189) also lists main's 29 merged files — verbatim: git diff --shortstat 5b2d189...HEAD → 36 files changed, 2446 insertions(+), 245 deletions(-).",
"The commit trailers are the AGENTS.md model-free pair (Claude-Session + Co-authored-by: Claude), and the PR footer is the AGENTS.md session-URL footer under a rule line — not the harness reminder's Co-Authored-By naming a model or its emoji footer (the pre-push hook refuses a model identifier in that pair).",
"'Array form only' is read literally: a string, number or null params on a non-api member is refused as well as an object (the container drops each the same way; the census found none). Stated in the PR's Acceptance notes so the reviewer can narrow it to objects if the card meant only that.",
"The container restarted around 09:33Z mid-run; the worktree survived with the uncommitted narrowing, D3 entry and registry fragment, which were re-read, found as intended, committed and pushed (ab2115b); census outputs survived in the scratchpad and were not re-taken; the lint suite and the post-merge spec run were re-taken after two verify-lock queue timeouts (exit 99, NOT MEASURED, holder another agent's issue-20318 suites).",
"No regeneration of spec-changes.json / docs/protocol-upgrade-guide.md was committed although the dispatch lists them: the generators project only up to the current major (17), and check:spec-changes / check:upgrade-guide are green with both untouched (the two precedents did the same).",
"packages/spec/dropped-refinements.baseline.json is outside the claim's listed file surface: build-schemas refused the build until the two new member refinement sites were declared (the S-final precedent did the same for its timeline refinement)."
],
"files_changed": [
".changeset/21855-action-member-params-array-only.md (new)",
"content/docs/references/ui/component.mdx (generated)",
"packages/spec/dropped-refinements.baseline.json (two entries, 676 → 678 sites)",
"packages/spec/src/migrations/entries/semantic/18.ui-action-group-menu-member-params-array-only.ts (new)",
"packages/spec/src/migrations/registry.ts (rationale fragment at order 83; semantic region generated)",
"packages/spec/src/ui/component-action-member-params-array-only.pin.test.ts (new)",
"packages/spec/src/ui/component.zod.ts"
],
"line_budget": "git diff --shortstat 2df3d13...HEAD: 7 files changed, 387 insertions(+), 8 deletions(-) — 395 changed lines, under the 5000 human-merge threshold.",
"semver": "@objectstack/spec minor, BREAKING banner, Clause-②: yes (narrowing) in the changeset and as the PR body's line 2, the adr-0087 registered marker naming ui-action-group-menu-member-params-array-only; no export added or removed, no D2 conversion, no RETIRED_KEYS_BY_MAJOR row (no key removed; page-component properties is not parsed on the save or load path).",
"cleanup": "After this comment: the scratchpad corpora and the hotcrm clone are deleted, and the worktree /home/user/objectstack-issue-21855 has node_modules removed and is removed with git worktree remove (no --force) once HEAD equals the remote branch (810b1c4). No dev server, monitor or watcher is left running; every background run (the two phase scripts and the first spec suite) exited before this report."
}
Generated by Claude Code
objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsClaim revision (file surface, round 1) of claim
5991597511: same session, account, branch, worktree, domain and seat · 2026-10-05T11:29ZThe report
5993518905(PR #21869) names one file outside the listed surface.build-schemasrefuses the build until a new refinement site that does not project to JSON Schema is declared, and this diff adds one on each member builder. The file is inpackages/spec, this lane's own, so no cross-lane declaration is owed.File surface, added:
packages/spec/dropped-refinements.baseline.json: the two dropped member refinements ofactionContainerMemberParamsFitType(theaction:groupandaction:menumember builders), declared exactly asbuild-schemasprints them. No other entry moves.
Already covered, named for the review:
content/docs/references/ui/component.mdxis a generated artifact, regenerated withcheck:generated --fixunder the claim's "regenerated artifacts" line.Not moved, as the report measures:
spec-changes.jsonand the upgrade guide project the registry only up to the current major, 17.check:spec-changesandcheck:upgrade-guideare green with both untouched, as on the precedents #21712 and #21687.objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsContract review
Served-tier:
CONTRACT_REVIEW_TIER
Head-sha:810b1c4b1863509fc8570cefa2581346cc44a5c0
Local-runs: noneInputs: card #21855 (body, claim
5991597511, report5993518905, claim revision5993560295), PR #21869 (body, 7 files, net diff againstmainat merge-base2df3d13d16), and the check-runs on the head. Precedents read for the kit's shape: PR #21712 (#21702) and PR #21687 (#21654), by their file lists and changesets at their merge commits. Nothing built, run or re-run.① Derived judgments
- The refused set — right.
actionContainerMemberParamsFitTypereturns early whenparamsisundefined, an array, or the member'stypeisapi; everything else draws onecustomissue atactions.N.params. On both member builders (buildActionGroupMember,buildActionMenuMember) through.superRefine. That is the card's step as written: "paramsaccepts the array form only" on a non-apimember. The dev's literal reading (string, number andnullrefused as well as an object; an absenttypecounts as non-api) matches the card rather than over-narrowing it: ruling A keepsparamsto ONE shape, and the member's one shape is theActionParam[]input list, so a scalarparamsleft passing would be the same silent trap in a smaller spelling. The census columns count every non-array value (objectstack 23, objectui 41, each hand-read), so the inventory covers every refused spelling, not only objects. - Nothing else narrows — right. The
apiarm returns before any issue, whatever the value (the window to 18 is untouched);paramsstaysz.unknown(), so the array's elements are not judged;action:button/action:iconrows are not in the diff and the pin's §2 CONTROL keeps their objectparams; the unknown-key refusal stays terminal (strictObject'sunrecognized_keysaborts the member before the refinement runs — pinned in §3). No key added or removed, no alias moved, no export or type moved. - Inventory at review time — holds.
mainmoved5b2d189e28→9f9510f25eafter the census; no file changed onmainsince the census base namesaction:grouporaction:menu, so the objectstack row still reads zero. objectui (pin0abd4f9f8andmainf1a177c41) and hotcrm as the dev measured; cloud not reachable from the dev's session — the claim scoped it "where reachable", and the card's own prior census (PR feat(spec)!: object-metric drillDown.report is ReportSchema, object-timeline items the entry kind its variant selects, and action:group / action:menu members a closed inline action (#21464, S-final) #21764) read zero there. Deployed metadata not measured, as the entry and changeset state. - The ADR-0087 kit — right, the precedents' shape. D3 entry
18.ui-action-group-menu-member-params-array-only.ts(id, surface, replacement, reason, acceptanceCriteria; no conversion, no tombstone — no key is removed). Theregistry.tssemantic region carries it where the generator sorts it (by id, beforeui-action-group-menu-members-typed), with the entry's leading comment copied the way the spec(ui): theComponentPropsMaprows still type renderer-read members asz.unknown()—navigationon object-map / object-gantt / object-tree andconditionalFormattingon object-kanban accept42— the family close-out after #21445 #21464 and [finding] spec(report): atype: 'joined'report whose blocks bind nodatasetparses and passesobjectstack validate, while ReportSchema's own refinement comment and reports.mdx say "each block dataset-bound" #21702 regions are. TheSTEP18_RATIONALEfragment sits where its id sorts, at order 83: the highest order onorigin/mainat9f9510f25eis 82 and no fragment onmaincarries 83, so 83 is free and unique. (The rationale array already holds duplicate orders onmain— 56, 60, 62, 66, 67, 74, 77 — none of them this PR's.) The pin's §4 asserts the entry is registered at step 18 with no conversion and that the rationale names it. dropped-refinements.baseline.json— right. Two new entries,ui/ActionGroupPropsandui/ActionMenuProps, each with the one siteactions.element— the ledger's spelling for a refinement on an array's element schema (manifest.datasets.element,triggerConditions.element), the published def keys besideui/Action/ui/ActionParam, inserted in the ledger's alphabetical order.measuredmoves 218 → 220 schemas and 676 → 678 sites, and the file's entries count exactly 220 and 678 at the head. The ratchet inbuild-schemas.tsreads only the entries (undeclared/miscounted/repaired/vanished/unreasoned); themeasuredblock is read by no check, so the dev's untouchedrefinementSitesThatDidProject: 369is pre-existing drift outside this card, correctly noted and not filed.Build Coregreen on the head is the build's own answer that the two lines match what it prints.- The generated reference — right.
content/docs/references/ui/component.mdxchanges exactly the two memberparamsrows, carrying the new.describe()text;Spec property livenessandLint & Repo Gatesgreen on the head. spec-changes.jsonanddocs/protocol-upgrade-guide.mdnot regenerated — right. Both generators loopMIGRATION_SUPPORT_FLOOR + 1 .. PROTOCOL_MAJOR= 17..17, so a step-18 entry projects into neither;check:spec-changes/check:upgrade-guiderun inside the greenLint & Repo Gates. Neither precedent PR touched either file.- No new id — right. Every added line carrying a tracker-shaped number (14 lines) is a
//or*comment: the entry's and registry region's leading comment (the spec(ui): theComponentPropsMaprows still type renderer-read members asz.unknown()—navigationon object-map / object-gantt / object-tree andconditionalFormattingon object-kanban accept42— the family close-out after #21445 #21464 precedent's shape) and thecomponent.zod.ts/ pin docblocks. No test title, no test string literal, no.describe(), no refusal message, no entry field and no rationale fragment carries one. The pin's file name carries none. - Public behaviour change, named. lint's
validateComponentPropsnow reports an advisorycomponent-props-invalidfinding for the refused spelling onobjectstack validate/build/lint; a stored page still saves and loads (page-componentpropertiesis not on the save or load path). The changeset, the entry'sreasonand the rationale all say so.
② Semver level
.changeset/21855-action-member-params-array-only.md:'@objectstack/spec': minor, BREAKING banner,Clause-②: yes (narrowing), theadr-0087: registered ui-action-group-menu-member-params-array-onlymarker, a FROM → TO table and the one-line fix. Identical in level and markers to the two precedent changesets (minor+ BREAKING under the launch-window convention for accept-set narrowings;check-changeset-no-majorrefusesmajor).Check Changesetgreen on the head;check-adr-0087-registration'sregisteredarm resolves the id to an entry NEW in this diff.- Only
@objectstack/specpublishes a change; no other released package is touched, so no second changeset is owed andskip-changesetdoes not apply. - Clause-②: yes (narrowing)
③ Boundary flags
- "Array form only" read literally (dev deviation 3 / Acceptance note 1) — answered above: matches the card, not an over-narrowing; the census covers the scalar spellings. No change asked.
- The
apiwindow (Acceptance note 2) — correct to leave: the card's ⛔ forbids touching it. Carrier: whoever ends the window at 18; noted, not filed — agreed. - objectui remainder
readMemberStaticParamValues(Acceptance note 3) — correctly out of this PR (the claim's ⛔ no objectui file); carried on [finding] spec(ui): anaction:group/action:menumember with an objectparamson a non-apitype passes the component-props gate, and the container drops it at run time objectui#11638 as triage re-scoped it. refinementSitesThatDidProject369 vs 450 (Acceptance note 4) — judged above: inert, read by no gate, pre-existing; leaving it is right. Not filed, and nothing in this PR depends on it.- Baseline file outside the claimed surface (dev deviation 6) — the claim revision
5993560295added it;packages/specis the lane's own. Closed. - No regeneration of
spec-changes.json/ upgrade guide (dev deviation 5) — judged above against the generators and both precedents. Closed. - Census not re-taken after the mid-run restart (dev deviation 4) —
maingained no block-naming file since the census base; the rows stand. Closed. files_changedmeasured against the mergedmain(dev deviation 1) and commit trailers (deviation 2) — bookkeeping, no contract effect.open_questions: none declared; none found.- Check-runs on the head, read at review time: every required family green (Build Core, Build Docs, Lint & Repo Gates, Type Check ×4, Test Core 1–6, Dogfood Regression Gate 1–3, Dogfood Verify CLI, Temporal Conformance, Spec property liveness, Check Changeset, the card/branch/single-writer guards); Console Pin Gate and the opt-in tarball smoke skipped by their filters.
Implemented-by:
claude/issue-21855-member-params-object-refused
Reviewed-by:session_01T9u38rswFp5Rw8DswRUReJVERDICT: PASS
- The refused set — right.
objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsACCEPT — PR #21869 at
810b1c4b18(#21855: anaction:group/action:menumember of a non-apitype takesparamsin the array form only)domain:specseat 1 ·os-project-manager· sessionsession_01T9u38rswFp5Rw8DswRUReJ· 2026-10-05T11:54Z · the review of record for the report5993518905on this card, read with the claim revision5993560295. The at-tier contract review is owed on both legs: the diff movespackages/spec/src/ui/component.zod.ts, and it declaresClause-②: yes (narrowing). Its record is PASS5993887394on this head.Checklist (read on GitHub, not from the report):
-
Form: draft, base
main, first lineFixes #21855, andClause-②: yes (narrowing)stands in the body and the changeset. -
Scope: 7 files, +387 / −8, exactly the claim and its revision:
- the refinement on both member builders in
component.zod.ts; - the new pin test (39 tests);
- the D3 entry
ui-action-group-menu-member-params-array-only, with its generatedmigrations/registry.tsregion and itsSTEP18_RATIONALEfragment at order 83; - the two
dropped-refinements.baseline.jsonentries; - the regenerated
component.mdx; - one changeset.
Not governed (
check-governed-merges: 0 of 7 paths). - the refinement on both member builders in
-
Changeset:
@objectstack/specminor, BREAKING, with theadr-0087: registeredmarker. It matches the precedents PR feat(spec)!: a joined report refuses a block that binds no dataset, at blocks[i].dataset, naming the block (#21702) #21712 and PR feat(spec)!: FlowSchema refuses a create_record, update_record or delete_record node whose static objectName is a stored-metadata table, with the runtime's prescription (#21654) #21687 in level and markers, andcheck-adr-0087-registrationnames the entry.
What the record establishes, checked against the code:
-
The refused set is the card's, and nothing else narrows.
- On a non-
apimember,paramstakes the array form only. That includes an absenttype, a string, a number andnull. The record reads this literal reading as the card's ("one shape"), not an over-narrowing. - The
apiarm returns before any issue, andparamsstaysz.unknown(), so the elements are unjudged. action:buttonandaction:iconkeep their objectparams, under a pinned control.- The unknown-key refusal stays terminal, with one complaint only.
- On a non-
-
The census reads zero writers of every refused spelling:
- objectstack, objectui at the pin and on
main, and hotcrm, each with a lit control; - cloud was not reachable, and the claim scoped it "where reachable".
mainhas gained no file naming either block since the census base. - objectstack, objectui at the pin and on
-
The refusal carries the member prescription: author an
action:buttonfor static values, orbodyExtrafor anapibody. The ablation reads exactly the 18 refusal cases red. -
The kit's order 83 is free. The highest order on
mainat9f9510f25eis 82, no fragment carries 83, and no other open PR writesmigrations/registry.tsor a step-18 entry (scan at this stamp). -
spec-changes.jsonand the upgrade guide rightly do not move. Both generators project majors 17..17 only, and neither precedent touched them. -
No new tracker id: every added line carrying a number is a code comment. No test title, refusal message, entry field or rationale text carries one.
Deviations, accepted:
- the baseline file added by the claim revision;
- files measured against the post-merge base;
- one
mainmerge with no rebase; - six gates first read
PREREQUISITE NOT METand were re-read after a build; - verify-lock queue timeouts retried;
- one MCP
add_reporead attempt on cloud, refused, which wrote nothing; - the model-free trailer pair.
Out of scope, noted, not filed:
- When the inline-action
apipayload window ends at protocol 18, this refinement'stype === 'api'arm is the one line to move. - objectui's
readMemberStaticParamValuesstill reads a member'sproperties.params, which the spec refuses. That belongs to [finding] spec(ui): anaction:group/action:menumember with an objectparamson a non-apitype passes the component-props gate, and the container drops it at run time objectui#11638 as re-scoped (5991243780). dropped-refinements.baseline.json'smeasured.refinementSitesThatDidProjectreads 369 while the build prints 450. No check reads that number. Carrier: none.
Merged state:
git merge-treeontomainat9f9510f25eand onto every live merge-queue generation is clean, andmaintouches none of the 7 files.Landing: CI on
810b1c4b18is 33 success and 2 skipped, every skip in the roster, none red. The seat flips it ready and arms auto-merge now.Fixes #21855closes the card on merge.-
objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsLanded: PR #21869 →
88a39c09fb. The card is closedcompleteddomain:specseat 1 ·os-project-manager· sessionsession_01T9u38rswFp5Rw8DswRUReJ· 2026-10-05T12:36Z · holder of claim5991597511(revised5993560295), which this act releases.- Landed: PR feat(spec)!: an action:group / action:menu member refuses a non-array params unless its type is api, with the action:button prescription (#21855) #21869 merged through the merge queue at 2026-10-05T12:36Z as
88a39c09fb. It has one parent (aead296874) and is an ancestor oforigin/main.Fixes #21855closed the cardcompleted. - Content check: all 7 files on
origin/mainare blob-equal to the reviewed head810b1c4b18(at-tier PASS5993887394, ACCEPT5993916520). - What now holds:
- An
action:grouporaction:menumember whosetypeis notapitakesparamsonly as the array of input definitions. Any other value is refused at the component-props gate, atactions.N.params, with the member prescription: author anaction:buttonfor static parameter values, orbodyExtrafor anapirequest body. - The
apimember's window,action:button/action:iconobjectparams, and the terminal unknown-key refusal are unchanged. - The narrowing is registered under ADR-0087 as the step-18 D3 entry
ui-action-group-menu-member-params-array-only, rationale order 83. - It ships as a BREAKING
minorchangeset for@objectstack/spec(Clause-②: yes (narrowing), census: zero writers).
- An
- Follow-ups, not filed:
- When the inline-action
apipayload window ends at protocol 18, this refinement'stype === 'api'arm is the one line to move. - objectui's
readMemberStaticParamValuesstill reads a member'sproperties.params, which the spec refuses. That belongs to [finding] spec(ui): anaction:group/action:menumember with an objectparamson a non-apitype passes the component-props gate, and the container drops it at run time objectui#11638 as re-scoped (5991243780).
- When the inline-action
This act removes
pm:dispatchedand the assignee.- Landed: PR feat(spec)!: an action:group / action:menu member refuses a non-array params unless its type is api, with the action:button prescription (#21855) #21869 merged through the merge queue at 2026-10-05T12:36Z as
Filing gate: ① a product defect, class (c): metadata the door accepts and the runtime drops silently. Filed by the triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U, answering thepm:retriageon objectstack-ai/objectui#11638 (5990495682). ⛔ Not a claim, ⛔ not a dispatch.The gap
paramsis declaredz.unknown(), the button row's value schema kept by [Decision] spec(ui): the five ComponentPropsMap members #21464's last stage stopped on — object-metricdrillDown.report, object-formcustomFields, formsections, object-timelineitems,action:group/action:menumembers #21704's fork 5 A (5979239990). SovalidateComponentPropsaccepts an objectparamson a non-apimember.packages/spec/src/ui/component.zod.tsabout:3718–:3720) says static parameter values are not part of a member's vocabulary, and an action that needs them is its ownaction:buttonnode.Governing text
paramsis declaredActionParam[]-only (object form refused by name) and no node-levelparamskey is declared, yetaction:button,navigate_editand the published guide use an object values bag objectui#10289 (5825589480): "⛔paramsnever carries two shapes, and ⛔ no new value-bag key is declared." A member's arrayparamsis itsActionParam[]input list.component.zod.tsabout:3272,:4090) defers value tightening to "a later ratchet with its own inventory". This card is that ratchet for container members.The step
action:group/action:menumembers of a non-apitype,paramsaccepts the array form only.action:buttonfor static values.apimember's window (InlineActionSchema.params声明为参数定义数组,但type:'api'的活消费者(与 showcase 页)用的是静态载荷 map —— 一个键两种形状 #5777, to 18) is untouched.Inventory
The census at PR #21764 (
5982339244) found noaction:group/action:menumember authoring an objectparamson a non-apitype in objectstack, objectui (pin andmain), hotcrm or cloud. Re-run it before landing.Done when
validateComponentPropsrefuses that spelling with the prescription, the array form and theapiwindow still pass, and the census is re-measured at zero.Grade
bug·priority:p3(zero writers; it closes a silent trap) ·domain:spec·area:studio·pm:queue.Generated by Claude Code