Skip to content

feat(spec)!: retire a page action row's own patch — a row's operation: 'update' runs the declared action its name names (#24394) - #24442

Queued
objectstack-fleet[bot] wants to merge 7 commits into
mainfrom
claude/issue-24394-row-update-runs-declared-action
Queued

objectstack-fleet[bot] wants to merge 7 commits into
mainfrom
claude/issue-24394-row-update-runs-declared-action

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #24394

Clause-②: yes (narrowing)

What

A page action row's operation: 'update' runs the declared action its name names. The platform action route resolves that declaration by name (ADR-0110 D2, "resolve, then address: declaration first") and writes the declaration's own patch, merged under the request's params, which it judges against the declaration (ADR-0104 D2, landed as d79dc4883c). The row's own patch reached no request. This PR takes route B from the seat's claim 6257461893: the row's patch retires, and the rows' describes and their half of the declarative-update judge are re-trued to what the route writes.

surface change
packages/spec/src/ui/component.zod.ts patch becomes a retiredKey() tombstone on action:button, action:icon and the shared action:group / action:menu member (rowPatchRetired, which replaces the by-reference actionPatch). The three operation describes, the node params describes and the three name describes now say the row runs its named action, needs a name beside operation: 'update', and that its static params are written only where that action declares them. rowDeclarativeUpdate passes writesNamedAction: true.
packages/spec/src/ui/action-declarative-update.ts staticParams retires; the surface flag is writesNamedAction. On a row, rule 1 ("patch without operation") stands down, because the tombstone refuses every row patch. Rule 4 ("nothing to write") asks for a non-empty name, refused at name. On ActionSchema both rules and their texts are unchanged.
packages/spec/src/ui/action.zod.ts one line: the surface descriptor's staticParams: false becomes writesNamedAction: false (behaviour-neutral)
packages/spec/src/conversions/registry.ts D2 conversion page-action-row-patch-removed (toMajor 18, retiredFromLoadPath, retiredAfter: '17.7.0', order 70), stripping from every node and member, scoped by component type
migrations/entries/retired-keys/18.ui__ActionButtonProps__patch.ts, 18.ui__ActionIconProps__patch.ts RETIRED_KEYS_BY_MAJOR[18] entries. The member's keys have no authorable-surface row of their own, so build gate (b) named exactly these two.
migrations/entries/semantic/18.ui-page-action-row-patch-retired.ts + a STEP18_RATIONALE fragment (order 104, after the merge of 538f923cb9, beside #24417's order-103 fragment) ADR-0087 D3 entry
migrations/entries/semantic/18.ui-page-action-row-{operation-declared,declarative-update-rules}.ts, 18.ui-action-group-menu-members-typed.ts same-major D3 texts that still prescribed a row patch, re-trued so the 18 upgrade guide does not contradict itself
packages/spec/liveness/page.json the regions row's evidence segment [23]: the row patch clause now records the measured read (Undo only, never posted) and the tombstone. Status and verifiedAt are unmoved.
generated authorable-surface/ui.json (two [RETIRED] lines), content/docs/references/ui/component.mdx
.changeset/24394-page-action-row-patch-retired.md @objectstack/spec minor, BREAKING, FROM → TO, one-line fix, ADR-0087 registered marker

Premises P1–P3 (tested before building; all hold)

  • P1, the row's patch reaches no write request. objectui at the pin 0707b4ae37 and at main 4b7ce47 has identical code. ActionRunner.executeUpdateOperation (pin packages/core/src/actions/ActionRunner.ts:1782, the patch read at :1799–:1802; main :1801, :1818–:1821) reads action.patch only into written, which feeds the Undo snapshot. The request is wire = { ...collected }. createServerActionHandler (packages/core/src/actions/serverActionHandler.ts, pin :208–:243) posts { recordId, params } to /api/v1/actions/OBJECT/NAME, and refuses a nameless action before any request ("Action has no name — a server action must declare one"). A second Undo-only read: packages/core/src/utils/predicate-fields.ts:155 (undoableWrittenKeys).
  • P2, no named producer. No page action row appears in examples/** or packages/apps/**. Control: the same grep finds element:button / page:header / record:* rows in 10 example files. In objectui, the page block inspector has no curated editor for any action row (previews/block-config.ts, pin and main), and its Advanced editor edits present keys only. The registry's patch input on action:button / action:icon (action-button.tsx:591, action-icon.tsx:422) is an advertisement, not an authored instance.
  • P3, no typed read of the spec row type's patch at the pin. objectui non-test source imports ActionButtonProps only for size (packages/types/src/ui-action.ts:900), and the four row schemas whole into packages/types/src/zod/public-blocks.zod.ts (propsBag). That helper already composes tombstoned rows (page:header, element:number). UIActionSchema.patch is SpecAction['patch'], which is ActionSchema's and untouched. The first P3 grep was truncated by head and missed the ActionButtonProps import; the untruncated re-read above is the reading.

Clause-② measured on the built rows

Both directions. Narrowing: every row patch is refused, and a row with operation: 'update' and no name (an input list or static params included) is refused at name. Widening: a row with operation: 'update' and a name but no patch and no params now parses; before, it was refused at patch as having nothing to write. So the claim's provisional yes (narrowing) stands.

The row half of rule 4, read as "names its action"

The ruling says the row half re-trues to "the row's write is its named action's patch". Read literally, a row with no name has no named action and nothing to write: the console refuses it before any request, and the route has no declaration to resolve. So the row's rule now refuses a nameless operation: 'update' row at name, and accepts a named one with no patch and no params. The alternative reading is dropping the row half entirely. It would have newly admitted a bare nameless { operation: 'update' } that the console refuses at click. That is raised for the seat in the report.

Pins

  • New: packages/spec/src/ui/component-action-row-patch-retirement.test.ts, in the repo project (listed in vitest.repo-tests.json). It covers:
    • every patch value on all four rows is ONE invalid_type issue at the key, with the prescription, and no unrecognized_keys (strict-arm control);
    • a named update row with no patch parses;
    • the type-level never (@ts-expect-error) and the walked shape keeping the key;
    • ActionSchema.patch untouched;
    • the page parse still accepts a stored row;
    • the D2 strip for stored and built rows, with its type scope, idempotence and load-path retirement;
    • the ledger rows;
    • a tree-scoped absence pin over the radius @objectstack/spec already declares, with a structural matcher (a patch key inside an object whose type is a page action row) and anti-vacuity cases.
  • Reworked: component-action-row-declarative-update.test.ts keeps the three members a row shares with the action (2, 3, 5) under ONE TEXT. Members 1 and 4 move to the row's own surface: "nothing to write is no name", "params are no write of the row's own", and "the action's half does not move".
  • Re-spelled fixtures (they used a row patch only as a way to write a valid update): component-action-row-on-success-executor.test.ts and packages/lint/src/validate-component-props-row-on-success.test.ts.
  • Ledger: component-props-unknown-members.pin.test.ts drops the four row patch{} lines (the member is never now). retired-key-removal-version.pin.test.ts gains the JOIN row.
  • Lint: validate-component-props.test.ts pins component-props-invalid + error + path + prescription on each node and member, and the nameless-row refusal at properties.name.

Tests and gates (union run after the final commit, head d6976f1c92)

  • @objectstack/spec: pnpm test gave 696 files, 21258 passed. typecheck exit 0, test layer included: the @ts-expect-error compiles against an identity-pinned debt ledger that held.
  • @objectstack/lint (downstream of spec): test 145 files, 6866 passed; typecheck exit 0.
  • @objectstack/metadata-protocol (downstream, the page save door): test 246 files, 20655 passed.
  • test:repo for spec, narrowed and declared: the full repo project exceeded the 10-minute foreground limit (SIGTERM). The new pin and its sibling ran under --project repo: 2 files, 47 passed. The full set is Test Core's.
  • node scripts/pm/dispatch-gates.mjs --ran reported 113 derived, 113 run, 0 NOT-MEASURED, 0 UNRUN. Every exit code was captured before any pipe; one family recorded exit 1 (below). node scripts/check-platform-checklist.mjs --changed-from origin/main: OK, 4 anchors judged, 0 lost. Local-only check:dev-prereqs: exit 0.
  • Ablation, one shot per behaviour (both legs through scripts/ablation-replace.mjs, anchor count x1 → x0, restored by blob hash and empty git diff HEAD). A1 reverted the rows' rule 4 to the action's: 78 tests went red. A2 replaced the tombstone with a record: the 2 tombstone pins went red.
  • Reverse verification of the rebuilt .d.ts: a throwaway file in packages/lint/src typed an ActionButtonProps literal with patch. tsc went red on that literal only (TS2353 against the tombstone's never brand); the control literal compiled. The file was deleted and the tree verified clean.

Acceptance notes

  • Pre-existing main red, not this diff. pnpm check:platform-checklist exits 1 on origin/main (e22defb13c) and at this branch's base. docs/qa/platform-checklist/areas/access-security.json anchors packages/core/src/security/resolve-authz-context.ts#legacyGrantAnchorRetired, which b2e78701f7 (PR fix(security): the single-posture platform-admin grant is owned by the Default Organization, and every reader of platform standing reads it #24373) deleted. Neither file is in this diff. The gate is not in per-PR CI (lint.yml, the maintainer decision recorded at its checklist step); its watchdog carries the red (check:platform-checklist is red on main #24438).
  • objectui still reads the row patch, outside this PR's surface: the four renderers forward it (action-button.tsx:323, action-icon.tsx:191, action-group.tsx:371, action-menu.tsx:304). The runner's Undo snapshot (ActionRunner.ts:1799) and undoableWrittenKeys (predicate-fields.ts:155) read it, and the registry lists a patch input. So the tracked sdui.manifest.json advertises patch on action:button / action:icon to the html-tier JSX gate until the pin moves. After this lands, the Undo snapshot of a row with no patch covers only collected params, not the named action's patch the route wrote. That is the follow-up the seat files.
  • main was not merged before opening: the dispatch limits merges to a reported conflict, a main-caused red or a queue removal. The two incoming commits touch 11 files, none under packages/spec and none in this diff.

Generated by Claude Code

…w-update-runs-declared-action

# Conflicts:
#	packages/spec/src/migrations/registry.ts.template
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 23 documentable anchor(s). ⚠️ 6 changed file(s) yielded no anchor (packages/spec/authorable-surface/ui.json, packages/spec/liveness/page.json, packages/spec/src/migrations/entries/retired-keys/18.ui__ActionButtonProps__patch.ts, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/automation/flows.mdx (via actionType (literal, a string literal in rowDeclarativeUpdate))
  • content/docs/deployment/validating-metadata.mdx (via actionType (literal, a string literal in rowDeclarativeUpdate))

⛔ 5 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v16.mdx (via actionType (literal, a string literal in rowDeclarativeUpdate))
  • content/docs/releases/v17/17-0.mdx (via actionType (literal, a string literal in rowDeclarativeUpdate))
  • content/docs/releases/v17/17-5.mdx (via retiredAfter (symbol, a field of const object pageActionRowPatchRemoved), retiredFromLoadPath (symbol, a field of const object pageActionRowPatchRemoved))
  • content/docs/releases/v17/17-6.mdx (via actionType (literal, a string literal in rowDeclarativeUpdate))
  • content/docs/releases/v17/17-7.mdx (via actionType (literal, a string literal in rowDeclarativeUpdate))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 6 changed file(s) yielded no anchor (packages/spec/authorable-surface/ui.json, packages/spec/liveness/page.json, packages/spec/src/migrations/entries/retired-keys/18.ui__ActionButtonProps__patch.ts, …) — pages documenting those are invisible to this run
  • 6 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 143 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 9112bcd887dbe3716466a55637df43fde00655a6 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 480a0ab6071420406afa6b252d3c6908ab8b3d5c — the merge of head daf710a3aa825ecfc9a34c3c6a7e7c7f79fec7b6 into base 9112bcd887dbe3716466a55637df43fde00655a6, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 480a0ab6071420406afa6b252d3c6908ab8b3d5c && git checkout 480a0ab6071420406afa6b252d3c6908ab8b3d5c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9112bcd887dbe3716466a55637df43fde00655a6 daf710a3aa825ecfc9a34c3c6a7e7c7f79fec7b6 && git checkout -B drift-repro 9112bcd887dbe3716466a55637df43fde00655a6 && git merge --no-ff daf710a3aa825ecfc9a34c3c6a7e7c7f79fec7b6

node scripts/docs-audit/affected-docs.mjs --json 9112bcd887dbe3716466a55637df43fde00655a6

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 9112bcd887dbe3716466a55637df43fde00655a6 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: daf710a3aa825ecfc9a34c3c6a7e7c7f79fec7b6
Local-runs: none
Scope: packages/spec/src/conversions/registry.ts, packages/spec/src/migrations/entries/retired-keys/18.ui__ActionButtonProps__patch.ts, packages/spec/src/migrations/entries/retired-keys/18.ui__ActionIconProps__patch.ts, packages/spec/src/migrations/entries/semantic/18.ui-action-group-menu-members-typed.ts, packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-declarative-update-rules.ts, packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-operation-declared.ts, packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-patch-retired.ts, packages/spec/src/migrations/registry.ts.template, packages/spec/src/ui/action-declarative-update.ts, packages/spec/src/ui/action.zod.ts, packages/spec/src/ui/component.zod.ts + packages/spec/src/shared/retired-key.ts, packages/spec/src/conversions/types.ts, packages/spec/src/conversions/walk.ts, packages/spec/src/migrations/types.ts, .changeset/24394-page-action-row-patch-retired.md, packages/spec/authorable-surface/ui.json, packages/spec/liveness/page.json, packages/spec/src/shared/retired-key-removal-version.pin.test.ts, packages/spec/src/ui/component-action-row-patch-retirement.test.ts, packages/spec/vitest.repo-tests.json, packages/lint/src/validate-component-props.test.ts, packages/runtime/src/action-execution.ts, packages/runtime/src/domains/actions.ts, docs/adr/0110-action-identity-and-declaration-admission.md, docs/adr/0104-field-runtime-value-shape-contract.md, .github/workflows/ci.yml (the Console Pin Gate job), scripts/build-console.sh, and objectui at the pin 0707b4ae37: packages/core/src/actions/ActionRunner.ts, packages/core/src/actions/serverActionHandler.ts, packages/types/src/ui-action.ts, packages/types/src/zod/public-blocks.zod.ts, apps/console/src/tests/registry-inputs-spec-parity.test.ts

Inputs read: card #24394 (body and all 5 comments, the seat's claim 6257461893 with its route B and premises P1-P3, the two os-dev-report records 6259032573 and 6259634070), PR #24442 (body, 23-file list, the net diff against main at base 538f923cb9, which my local git diff 538f923cb9..daf710a3aa reproduces file-for-file at +1126 / -159), and the 48 check-runs on daf710a3aa. The head is a merge commit (parents d6976f1c92 and 538f923cb9); the one conflict resolution the newest report names (registry.ts.template, STEP18_RATIONALE) was read in the head tree. Check-runs: every required context is success (Lint and Repo Gates, TypeScript Type Check, Test Core with its six shards, Build Core, Governed Surface Queue Guard), as are Check Changeset, Spec property liveness, Build Docs and every Lint and Type Check lane; the only skipped runs are the full-battery-only jobs (Console Pin Gate, Dogfood shards, Temporal Conformance, packed-tarball smoke) and the re-triggered label/size duplicates. No run on the head concluded failure. Not a governed-surface diff (no path under docs/adr, .claude, .github/workflows, skills, AGENTS.md, CLAUDE.md, NORTH-STAR); head repo is the base repo; 1,285 changed lines, under the 3,000 ceiling.

① Derived judgments

Each accept-set or surface change the diff implies, judged against the cited code.

  1. A page action row's own patch is refused (narrowing) — RIGHT. component.zod.ts replaces the by-reference actionPatch with rowPatchRetired(owner), a retiredKey() tombstone on action:button (:4021), action:icon (:4121) and the shared action:group / action:menu member (:4271). retiredKey() (shared/retired-key.ts) is z.never({ error }).optional(), so a present value yields ONE invalid_type issue carrying the prescription and the input type is the never-marked object, which is what the new pin asserts (fails tsc at the authoring site) and what the lint pins assert at properties.patch and properties.actions.0.patch with severity error. The premise is true in the cited code: at the pin, ActionRunner.executeUpdateOperation (:1782-:1810) reads action.patch only into written for the Undo snapshot and posts wire = { ...collected }; createServerActionHandler (:208-:243) posts { recordId, params } to /api/v1/actions/OBJECT/NAME; on this side declarativeUpdateWrite (runtime/src/action-execution.ts:2213) builds { ...declaration.patch, ...params } from the declaration resolveRouteActionDeclaration resolved by name (ADR-0110 D2, read at its section heading: "Resolve, then address: declaration first"). The row's patch reaches no request. The prescription closes with the standardized os migrate meta --from 17 sentence, and no leftover actionPatch or staticParams reference survives in packages/spec/src or packages/lint/src.

  2. operation: 'update' on a row with no (or an empty) name is refused at name (narrowing) — RIGHT, as reading A. action-declarative-update.ts rule 4 on a writesNamedAction surface asks namesAction(data.name); previously a nameless row with a patch or non-empty params parsed. The refused shape never worked: the dispatch at the pin returns Action has no name — a server action must declare one before any request (serverActionHandler :211-:213), and the route has no declaration to resolve. The seat's answer that reading A stands is recorded in report 6259634070; the built judge matches it, and the PR body's own paragraph explains the choice.

  3. A named row with operation: 'update', no patch and no params now parses (widening) — RIGHT, and declared. Before, rule 4 refused it at patch as having nothing to write; its write is the named declaration's patch, which declarativeUpdateWrite performs with an empty params bag. The PR body and the changeset both name this direction, so Clause-②: yes (narrowing) is measured, not assumed.

  4. A node's static object params no longer count as the row's write but remain accepted — RIGHT. paramsWrite drops the staticParams arm; the three params describes now say the values ride the request and are written only where the named action declares them. That is the route as it is: actions.ts:673-:679 runs enforceActionParams (ADR-0104 D2) before the handler and answers a 400 on an undeclared key, on a param-less update too. The describe's admission that the name-to-declaration cross-reference is one "this row cannot see" is accurate; see ③ F1.

  5. ActionSchema is behaviour-neutral — RIGHT. The one action.zod.ts line renames the descriptor's staticParams: false to writesNamedAction: false. On that surface rule 1 still runs (!surface.writesNamedAction && data.patch !== undefined) and rule 4's paramsWrite(params) reduces to the same test the old staticParams: false arm computed (an array params with a positive length, nothing else). The pin ActionSchema.patch is untouched holds it. DeclarativeUpdateSurface is not exported from spec/ui's index and appears in no api-surface shard, so the field rename is an internal change, not a public-surface one.

  6. The D2 conversion page-action-row-patch-removed — RIGHT. toMajor: 18, retiredFromLoadPath: true, retiredAfter: '17.7.0' (the same stamp as 33 sibling 18-train entries; the type's docblock in conversions/types.ts says it is the artifact-ingestion floor and is pinned by the census test, which Test Core ran green). Scoped by component type through ACTION_RUNNER_BLOCK_TYPES and ACTION_CONTAINER_BLOCK_TYPES, never by key name, so an open-namespace acme:quick_edit keeps its own patch. mapPageComponents (conversions/walk.ts:31) visits regions, named slots and nested trees, which is the reach the fixture exercises (button, bare icon, group member, nested menu member, slotted icon); stripKeys emits one notice per deleted key and returns the same reference when nothing is touched, so expectedNotices: 5, the by-reference control and idempotence follow. Order 70 is the list's new maximum; duplicate orders already exist in MAJOR_18_CONVERSIONS, so uniqueness was never a constraint.

  7. The ledger rows — RIGHT. RETIRED_KEYS_BY_MAJOR[18] gains ui/ActionButtonProps:patch and ui/ActionIconProps:patch, the same spelling as the sibling 18.ui__Action__aria.ts; authorable-surface/ui.json marks exactly those two rows [RETIRED], and the member rows have no surface entry, so the two-key registration is correct rather than short. The retired-key-removal-version.pin.test.ts JOIN row names the family under page-action-row-patch-removed with the two tombstones and the two member refusals. The liveness/page.json edit touches the regions row's note only, status and verifiedAt unmoved; Spec property liveness is green.

  8. The D3 entry and the STEP18 fragment — RIGHT. ui-page-action-row-patch-retired carries every required SemanticMigration field (id, surface without backticks, replacement, reason, acceptanceCriteria); it names the judgement the strip cannot make (moving stripped values onto the named action). The fragment with the same id sits at order 104 beside fix(spec)!: defineStack's page cross-reference checks judge components inside page:tabs and page:accordion item children #24417's order-103 fragment; the template differs from base 538f923cb9 by these 15 added lines only. The three sibling 18 D3 texts (operation-declared, declarative-update-rules, action-group-menu-members-typed) are re-trued so the 18 upgrade guide no longer prescribes a row patch or lists patch? among member keys; left as they were, they would have contradicted the tombstone.

  9. The pinned sibling at 0707b4ae37 keeps building — RIGHT (P3 verified independently). objectui's non-test source reaches the row types only as SpecActionButtonProps['size'] (packages/types/src/ui-action.ts:900) and as whole schemas through stripImportedDefaults / propsBag / flatPropRefusals in public-blocks.zod.ts, helpers that already compose tombstoned rows (page:header, element:number); ui-action.ts:647's patch?: SpecAction['patch'] is ActionSchema's, untouched; every ComponentPropsMap['action:...'] mention in non-test source is a comment. packages/types builds with tsc, so this is the compile path that matters, and it is clean. The Console Pin Gate job (ci.yml, console-pin) runs scripts/build-console.sh plus check:console-sha and check:console-injection, a build and no objectui test run, so the retirement creates no main-red at the pin.

  10. Describes and generated docs — RIGHT. The three operation, three name and two node params describes say what the route does; check:docs (in the TypeScript Type Check job) and check:authorable-surface are green, so component.mdx and ui.json are the generators' output.

② Semver level

.changeset/24394-page-action-row-patch-retired.md: '@objectstack/spec': minor, body opening with BREAKING, the FROM → TO table (three rows), the one-line fix, the exact prescription an author sees, the retirement kit, and the adr-0087: registered page-action-row-patch-removed, ui-page-action-row-patch-retired marker that check:adr-0087-registration read green. This matches what the diff publishes: the only released package with a source change is @objectstack/spec (packages/lint changes are test files; content/docs is not published). minor for a BREAKING accept-set narrowing is the 18-train convention stated on the sibling retired-keys entry (accept-set narrowings ride minor releases) and satisfies the rule that a yes takes at least minor. The changeset names no model identifier.

Clause-②: line — yes (narrowing), present and identical on the PR body and the changeset; both directions are measured on the built rows (narrowing in ① 1-2, widening in ① 3), so the arm is correct and the claim's provisional line stands unamended.

③ Boundary flags

Dev flags (report 6259032573 deviations, report 6259634070 deviations), each answered:

  • File-surface breaches named by the dev: action.zod.ts (one behaviour-neutral line, ① 5), three sibling D3 entries (① 8), two lint test files, vitest.repo-tests.json (the repo-project listing that check:cross-package-test-inputs holds), the removal-version pin, the unknown-members ledger. Each is required by the kit; none touches a hold-off (ActionSchema's rules and texts, packages/runtime, objectui, ADR-0104, stack.zod.ts, release notes all untouched). Accepted.
  • H4 refuted in form (no per-key liveness row exists; the page ledger walks top-level props): the regions note was re-trued instead. Accepted.
  • test:repo narrowed locally to the new pin and its sibling under the 10-minute cap; Test Core on the head ran the full set green. Accepted.
  • main not merged before opening, then merged at the seat's patch round through os-regen-merge.sh with one conflict (STEP18_RATIONALE): both fragments kept, fix(spec)!: defineStack's page cross-reference checks judge components inside page:tabs and page:accordion item children #24417's byte-for-byte, this card's renumbered 103 to 104. The renumber was not required (that list already carries three fragments at order 95) but is harmless. origin/main moved twice since (4d12b23f04, fa26308bb0), 0 overlap with this diff. Accepted.
  • Harness attribution overridden by AGENTS.md's model-free trailer pair and session-URL footer; container restart mid-run with verification re-run from a committed state; one gate batch over the cap, the killed gate re-run whole. Accepted.
  • pr_body_amend_for_the_seat: applied. The body now reads order 104 and says the checklist gate is not per-PR CI (its watchdog carries check:platform-checklist is red on main #24438), which the head's green Lint and Repo Gates confirms.

open_questions, both answered by the seat and recorded in report 6259634070:

  • Q1, the row half of rule 4: reading A stands (nameless update row refused at name). The built judge matches it (① 2). Closed.
  • Q2, a validate-time cross-reference from a row's name to a declared operation: 'update' action and from its static params to that action's declared params: the seat ruled "noted, not filed". Recorded as answered. F1, escalated as a recommendation, not a FAIL: after this PR the row's entire write is a reference the spec cannot resolve, and an AI-authored row with a name that resolves nowhere, or static params the action does not declare, fails only at click (a 404 or the ADR-0104 D2 400 with no write). The dev's option A names an existing walk (packages/lint/src/validate-action-name-refs.ts) that already resolves names for four other blocks, so the fix is located and cheap. Under Prime Directive 10 this reads as a metadata-authoring trap with a located fix; a p3 card is the right carrier. The PR itself is right without it, and the gap predates it (no action row's name was cross-referenced before).

Reviewer's own flags:

  • F2 (objectui follow-up, the seat's at landing, out of this PR's surface): at the pin the four renderers forward patch, the runner and predicate-fields.ts:155 read it for Undo, and the registry publishes a patch input on action:button / action:icon, so the tracked sdui.manifest.json still advertises it to the html-tier JSX gate until the pin moves. Once objectui adopts a spec with this tombstone, its registry-inputs-spec-parity.test.ts pin the harvested tombstoned keys are recognised, not exempted — and not published either reds on that input; and after the retirement the Undo snapshot of a row covers only the collected params, not the named action's patch the route wrote. Neither affects the Console Pin Gate (build only, ① 9). The card the seat files should carry all three.
  • F3 (note): the D3 entry omits conversionIds. The strip-class siblings (page-header-show-star-retired, ui-record-json-retired, ui-marketplace-installed-list-retired) omit it too, and the new pin reads the D2 id from the entry's prose; convention held, nothing to change.
  • F4 (note): namesAction admits a whitespace-only name, exactly as the console's if (!actionName) does; the same class as F1 and not a defect of this diff.

Implemented-by: claude/issue-24394-row-update-runs-declared-action
Reviewed-by: session_01WnW7HvfnfHbLg1fpkHX3Mu

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:spec seat 1 · os-warren · session session_01WnW7HvfnfHbLg1fpkHX3Mu · 2026-10-19T04:47Z · head daf710a3aa · card #24394 (claim 6257461893).

  • Form: first line Fixes #24394, and no other closing keyword. Clause-②: yes (narrowing) at line start on the body and on the changeset, matching the claim. Assignee os-warren. The seat amended the body twice, per the dev's patch-round report: the fragment order reads 104, and the wrong "expect it red in Lint & Repo Gates" sentence is replaced.
  • Path face (check-governed-merges.mjs --pr 24442): NOT governed; 1285 changed lines, under 3000. The file-surface breaches the dev named (action.zod.ts, one neutral line; three sibling D3 entries; lint test files; vitest.repo-tests.json; the removal-version pin) are the retirement kit's. None touches a hold-off.
  • Contract review of record: 6259877767: Served-tier: CONTRACT_REVIEW_TIER, Head-sha = this head, Local-runs: none, PASS. It confirms premises P1–P3 in the cited code, the BREAKING minor changeset with its FROM → TO table and ADR-0087 marker, and the yes (narrowing) arm.
  • Claim Basis line: the claim said the row's "nothing to write" rule would be dropped. The dev built reading A instead: a nameless operation: 'update' row is refused at name, and a named row with no patch parses. The seat decided A in the patch round (recorded in 6259634070). The Clause-② value is unchanged.
  • Prose face: the changeset was read against the diff. "patch on a page action row … is now refused" is true for action:button, action:icon and the shared member. "Beside operation: 'update' a row now needs a name" is reading A. "ActionSchema.patch … is unchanged" holds: action.zod.ts changes one descriptor name only. The Docs Drift Check's two hand-written pages need no edit. content/docs/automation/flows.mdx:1197 is about an action's params, not a row's patch, and content/docs/deployment/validating-metadata.mdx has 0 hits.
  • Gates: 48 check-runs on daf710a3aa, every one success or an expected skip (read 2026-10-19T04:47Z); mergeable_state: clean.
  • Reviewer's flags:
    • F1 (a validate-time cross-reference from a row's name to a declared action): stays noted, not filed. It has zero producers and no wrong answer measured at a public door, so it does not pass the filing gate. The seat post's pointers carry it.
    • F2: the objectui follow-up is filed in objectui at this landing. It covers the Undo snapshot's read of the row patch, the four renderer forwards and the registry's patch input in sdui.manifest.json, and the registry-inputs-spec-parity.test.ts pin that reds once objectui adopts this spec.
    • F3: noted, no change.
  • Landing: ready and auto-merge through the relay; MERGED confirmed on the queue branch and on origin/main; then pm:dispatched comes off.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 19, 2026 04:48
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 19, 2026 04:48
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 19, 2026
Any commits made after this event will not be merged.

This branch has not been deployed

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

Labels

documentation Improvements or additions to documentation protocol:ui size/xl tests tooling

Projects

None yet

2 participants