Skip to content

fix(spec)!: a page action row declares ActionSchema's operation and refuses onSuccess beside operation: 'update', with the action's text - #23425

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-23332-action-row-operation
Oct 14, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-23332-action-row-operation

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #23332

Clause-②: no (narrowing)

The page action rows (action:button, action:icon, and each action:group / action:menu member) and ActionSchema now give one answer for operation: 'update' plus onSuccess. Direction of record: triage 6154972534 (consistent with #23168's answer C, 6148462268), within the PM's premised ruling.

Premise check, done first: ActionSchema's refusal is the right half

The ruling's premise: no runtime consumer honors onSuccess after an operation: 'update' write as a declared, tested behavior. It holds. Read at objectui's .objectui-sha pin 1fb1ced36243 (the runner file is byte-identical at objectui main b9814b3):

  • What the runner does mechanically. ActionRunner.execute reads operation before any executor (core/src/actions/ActionRunner.ts:1384-1388). executeUpdateOperation posts the write through the registered script dispatch, and then handlePostExecution runs. That function performs the onSuccess hop for every branch on success (:1639-1642). So the hop would fire after the write, but only because every branch shares the same post-execution path.
  • It is not declared. The runner's own docblock for onSuccess (:1620-1638) says the key is "refine-scoped to type: 'api' and type: 'script'" and that the spec's declared meaning is its only meaning. objectui's metadata-admin form puts onSuccess in UPDATE_OPERATION_REFUSES (app-shell/src/views/metadata-admin/inspectors/ActionDefaultInspector.tsx:336-353), calls it "a navigation the write has no business doing", and clears it when an author switches an action into an update (commitOperation, :620-628).
  • It is not tested. No objectui file at the pin pins the pairing. ActionRunner.onSuccessNavigation.test.ts has no operation in it, and ActionRunner.updateOperation.test.ts has no onSuccess. Of the 55 files that write operation: 'update', the 10 that also mention onSuccess were each read: none puts the two on one action.

So the rule stays as ActionSchema has it, and the rows take it. ⛔ ActionSchema was not widened.

What changes

1. A row's operation is ActionSchema's own enum, by reference (component.zod.ts, actionOperation() = ActionSchema.in.shape.operation.unwrap()). It is read the same way actionOnSuccessBlock() reads onSuccess, so there is no second vocabulary: the one member is 'update', and 'delete' / 'custom' get the action's own "bulk-def operation with no row-level form" refusal. Before this, the rows typed it z.unknown(). The runner tests operation === 'update' and lets any other value fall through to the executor, where it did nothing.

2. A row's onSuccess beside operation: 'update' is refused whatever executor the row names (or none), as ActionSchema refuses it on every type. refuseRowOnSuccessBesideExecutor reads operation first, the way the runner does, and gives ONE issue at onSuccess. Before this:

row before after
actionType: 'api' or 'script' or a host handler, operation: 'update', onSuccess accepted refused at onSuccess, with the update text
no executor, operation: 'update', onSuccess refused with the no-executor text ("has no executor to follow" … declare actionType: 'script'), which does not fit this shape refused with the update text
operation: 'update' and no onSuccess, on any executor accepted accepted (unchanged)

3. One refusal text, from one place. The new onSuccessBesideUpdateOperationRefusal(subject) lives in action-on-success-executor.ts, the module PR #23331 created for this purpose (relative import only, outside the ui barrel). ActionSchema's DECLARATIVE_UPDATE_REFUSED_KEYS onSuccess entry and the rows both read it. The text says what the row does at run time, as the card asks:

An action:button's onSuccess (operation: 'update') would follow a result the contract does not declare: the console's action runner reads operation before any executor, so an operation: 'update' action runs no handler and no executor of its own (the platform action route writes patch to the current record as the caller), and the runner then performs the post-success hop after that write succeeds. ${result.*} reads a handler's return or a call's response, and the declarative write declares neither. Drop onSuccess; post-success navigation after the declarative write is a spec proposal, not a silent key.

On ActionSchema the subject is "An operation: 'update' action's onSuccess". The text used to open "onSuccess is not defined for an operation: 'update' action yet". Its reach is unchanged, and the "Drop onSuccess" prescription is kept.

Accept-set narrowing (BREAKING, minor, ADR-0087 registered ui-page-action-row-operation-declared)

Values a row accepted before and now refuses:

  • any operation other than 'update': 'delete' and 'custom' (with the action's prescription), and anything else (invalid_value);
  • onSuccess beside operation: 'update', on any executor.

Producers measured: none in this repository at 339604baf. I checked examples/ (the only page action rows are in examples/app-showcase/src/ui/pages/index.ts and src/ui/actions/index.ts, and neither carries operation), the CLI corpus (packages/cli, skills/) and the dogfood fixtures (packages/qa). In objectui at the pin 1fb1ced36243 and at main b9814b3, the action renderers' tests carry operation: 'update' rows without onSuccess (accepted, unchanged). No row carries another operation value or the pairing. objectui's registration already publishes operation as enum: ['update'] (action-button.tsx:584-590), so its registry-inputs-spec-parity enum check gets a member the spec accepts. hotcrm: NOT MEASURED (no checkout in this container).

Semantic migration entry packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-operation-declared.ts, beside the 18.ui-page-action-row-* family. Changeset .changeset/23332-page-action-row-operation.md.

Pins, each with a negative control

  • Row side (component-action-row-on-success-executor.test.ts §4), on all four rows:
    • REFUSAL: onSuccess beside operation: 'update' with no executor and with api / script / run. The test asserts ONE issue at onSuccess plus the full first sentence.
    • NEGATIVE CONTROL: the same rows without onSuccess parse.
    • ONE TEXT: the row message after its subject equals ActionSchema's message after its subject.
    • NO SECOND VOCABULARY: every row holds the very enum instance ActionSchema.operation declares.
    • 'delete' is refused with the action's exact message, 'create' gets invalid_value at operation, and the 'update' accepted control passes.
  • ActionSchema side (action-row-update.test.ts):
    • The mixing-rule pin is flipped to the new opening, a prefix of the text that bears the load.
    • New: onSuccess beside operation: 'update' (no type and explicit type: 'script') gives exactly one issue, at onSuccess, and the same action without it parses.
  • The os validate door (packages/lint/src/validate-component-props-row-on-success.test.ts): one component-props-invalid finding at …onSuccess for api + update + onSuccess, nothing without onSuccess, and one finding at …operation for 'delete'.
  • Ledger: component-props-unknown-members.pin.test.ts deletes the operation line, because a typed member deletes its line.

Pin sweep: I grepped the old message ("not defined for an operation: 'update'") across this repo: one pin, flipped (action-row-update.test.ts:218). objectui at the pin and at main: zero hits.

Ablations (committed tree, scripts/ablation-replace.mjs; every leg restored to blob == HEAD with git diff HEAD empty; spec tests import src, so no dist was involved):

  • The row branch was turned off (if (row.operation === 'update') changed to if (false as boolean)). Result: 20 red (16 REFUSAL + 4 ONE TEXT), and the negative controls stayed green.
  • ActionSchema's onSuccess entry was deleted. Result: 6 red (both ActionSchema pins + 4 ONE TEXT). My first attempt used an empty --replacement, which the tool refused as a no-op (count 0 to 0). It was re-run with --delete.
  • The row enum was replaced by z.unknown(). Result: 9 red (8 operation refusals + NO SECOND VOCABULARY).

Verification at 339604baf (after merging origin/main 5656c5638)

  • @objectstack/spec:
    • Targeted files after the merge rebuild: 4 files, 289 passed.
    • --project local: 672 of 673 files green before the ledger fix. The 3 failures were all in component-props-unknown-members.pin.test.ts (the typed operation member's line), and that file is now 69/69.
    • test:repo: 59 files, 1011 passed.
    • typecheck (tsc, scripts, test-typecheck): exit 0.
    • check:generated: green after gen:docs (content/docs/references/ui/component.mdx regenerated).
  • @objectstack/lint: 138 files, 6706 passed; typecheck: exit 0.
  • Consumer readings (ComponentPropsMap parsers): cloud-connection and mcp canonical-expression-envelopes.test.ts 16 and 11 passed; platform-objects pages/canonical-expression-envelopes.test.ts 15 passed.
  • dispatch-gates.mjs --commands: 111 families, all 111 run on 339604baf, every one exit 0. --ran reconciliation: "111 run, 0 NOT-MEASURED (a DERIVED zero — all 111 recorded an exit code and none of them is 3)". check:nul-bytes exit 0.

Acceptance notes


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 9 documentable anchor(s).

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

  • content/docs/ui/react-pages.mdx (via onSuccess (literal, a string literal in DECLARATIVE_UPDATE_REFUSED_KEYS; a string literal in refuseRowOnSuccessBesideExecutor))

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

  • content/docs/releases/v17/17-1.mdx (via onSuccess (literal, a string literal in DECLARATIVE_UPDATE_REFUSED_KEYS; a string literal in refuseRowOnSuccessBesideExecutor))
  • content/docs/releases/v17/17-3.mdx (via onSuccess (literal, a string literal in DECLARATIVE_UPDATE_REFUSED_KEYS; a string literal in refuseRowOnSuccessBesideExecutor))
  • content/docs/releases/v17/17-7.mdx (via onSuccess (literal, a string literal in DECLARATIVE_UPDATE_REFUSED_KEYS; a string literal in refuseRowOnSuccessBesideExecutor))

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
  • 3 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 — 142 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 4f81f315266354e7b70adc589d6ee38a76d24f41 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from ea35a797847a3417588e5c51c9670ae845c6d176 — the merge of head 339604baf420a246fc5841dffd75d8a440346648 into base 4f81f315266354e7b70adc589d6ee38a76d24f41, 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 ea35a797847a3417588e5c51c9670ae845c6d176 && git checkout ea35a797847a3417588e5c51c9670ae845c6d176
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 4f81f315266354e7b70adc589d6ee38a76d24f41 339604baf420a246fc5841dffd75d8a440346648 && git checkout -B drift-repro 4f81f315266354e7b70adc589d6ee38a76d24f41 && git merge --no-ff 339604baf420a246fc5841dffd75d8a440346648

node scripts/docs-audit/affected-docs.mjs --json 4f81f315266354e7b70adc589d6ee38a76d24f41

⚠️ 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 4f81f315266354e7b70adc589d6ee38a76d24f41 → 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: 339604baf420a246fc5841dffd75d8a440346648
Local-runs: none

Inputs read: card #23332 (body, triage first grade 6154972534, the unlock scan, the PM claim, the os-dev-report), PR #23425 (body, 10-file list, net diff against main at the head; merge base 5656c5638), and the check-runs on the head, read once. Reference reads of packages/spec/src/ui/action.zod.ts, component.zod.ts, migrations/entries/README.md and ADR-0087 at the container's checkout (e4ec6ec1a, the claim's own read sha, pre-PR) to confirm what the diff builds on; nothing built, run or re-run.

Check-runs on the head. Completed and green: filter, The card this PR closes must claim this branch, Spec property liveness, Part-of PR must not also close its card, No other open PR may claim the same single-writer path, No other open PR may claim the same issue, Lint · scoped · tooling self-tests, Lint · PM dispatch-gates, Governed Surface Queue Guard, Flag docs affected by code changes, Dogfood Regression Gate, Check PR Size, Check Documentation Links, Check Changeset, Auto Label. Skipped (opt-in or matrix placeholders): Temporal Conformance (live PG + MySQL), Packed-tarball smoke (opt-in), Dogfood Verify CLI, Dogfood Regression Gate (${{ matrix.shard }}/3), Console Pin Gate. In progress at brief time (18): Build Core, Build Docs, Lint · ESLint, Lint · CI, release & engine gates, Lint · docs & code guards, Lint · repo & tooling gates, Lint · scoped · query-options erasure, Lint · scoped · slot lookup & corpus, Test Core (1/6) … (6/6), Type Check · consumer gates, Type Check · debt ledger, Type Check · source gates, Type Check · workspace. Their conclusions are the derived-gate verdicts for the pins, the docs regeneration and the migration-registry entry; this record does not pre-empt them, and the branch protection (mergeable_state: blocked) holds the merge until they report.

① Derived judgments

Every accept-set and public-surface change the diff implies, each judged:

  1. The four page action rows' operation: z.unknown() → ActionSchema's own enum, by reference (component.zod.ts, actionOperation() = ActionSchema.in.shape.operation.unwrap(), applied on ActionButtonPropsSchema, ActionIconPropsSchema and actionContainerMemberShape() for the action:group / action:menu members). Accept-set narrowing: any value but 'update' is now refused at operation; 'delete' / 'custom' carry the enum's own error map ("bulk-def operation with no row-level form"), every other value invalid_value. Right. It is the triage direction verbatim ("the rows declare operation and judge it as ActionSchema does. One rule, read from one place, not a second copy"), the same read-by-reference pattern the rows already use for onSuccess (actionOnSuccessBlock()), and the NO SECOND VOCABULARY pin asserts identity of the enum instance on all four rows. The runner's isUpdateOperationAction tests operation === 'update', so no previously-parsing non-'update' value did anything at run time.

  2. The rows refuse onSuccess beside operation: 'update' whatever executor the row names, ONE issue at onSuccess (refuseRowOnSuccessBesideExecutor reads operation before effectiveRowExecutor). Accept-set narrowing for rows with api / script / a host handler plus the pairing; for the no-executor row it is a text change only (that row was already refused, with the no-executor text). Right. The card's "Wanted" bullet 1 (one answer in both places) and bullet 2 (the refusal text for an operation: 'update' row says what it does at run time) are both met, and the order of reads matches the runner's (ActionRunner.ts:1384-1388 at the pin), so the no-executor case gets the fitting text, which was the card's named wording point. Pins on all four rows × {none, api, script, run}, each with a same-row negative control (no onSuccess parses), plus the os validate door pins in packages/lint.

  3. ActionSchema's DECLARATIVE_UPDATE_REFUSED_KEYS onSuccess entry: text replaced, reach unchanged. The entry stays keyed onSuccess in the same table, so the action accept set is identical before and after; only the message changes (from "onSuccess is not defined for an operation: 'update' action yet …" to the shared onSuccessBesideUpdateOperationRefusal("An operation: 'update'action'sonSuccess")). Right, and the direction's "measurement moves the rule on ActionSchema's side" branch was not taken: the PR's premise check (runner docblock scopes onSuccess to api/script; objectui's ActionDefaultInspector UPDATE_OPERATION_REFUSES lists and clears it; no objectui test pins the pairing) is the measurement triage asked for, and it lands on ActionSchema's refusal being the right half. ⛔ ActionSchema was not widened. Pin sweep: the one pin on the old text (action-row-update.test.ts:218) is flipped; the new ActionSchema-side test asserts ONE issue at onSuccess for {} and { type: 'script' } with a negative control.

  4. One text from one place: onSuccessBesideUpdateOperationRefusal(subject) added to action-on-success-executor.ts (the module PR feat(spec)!: a page action row's onSuccess is refused beside a runner built-in other than api / script, and beside no executor #23331 created) and read by both action.zod.ts and component.zod.ts. Right. The module is not re-exported from packages/spec/src/ui/index.ts or packages/spec/src/index.ts (checked at the reference checkout), so this is an internal export, not a public-surface widening; the ONE TEXT pin compares the row message after its subject to ActionSchema's after its subject, and the ablation table in the PR (row branch off → 20 red; action entry deleted → 6 red; enum back to z.unknown() → 9 red) shows each pin bears load.

  5. D3 semantic entry 18.ui-page-action-row-operation-declared.ts, id matching the changeset's adr-0087: registered ui-page-action-row-operation-declared marker, beside its 18.ui-page-action-row-* family, in the per-file registry shape entries/README.md prescribes (no index to edit; gen:migration-registry concatenates). Right. surface names both narrowed keys on all four rows, replacement is marked NOT mechanically convertible (the author chooses between the write and the hop), acceptanceCriteria is non-empty and names the os validate finding paths.

  6. Generated docs content/docs/references/ui/component.mdx: 8 lines, the four rows' operation type cell (any → the enum of 'update') and onSuccess description (adds "and beside operation: 'update', whose write the runner performs before any executor"). Right — matches the .describe() strings in the diff one for one; Flag docs affected by code changes is green and Build Docs is in progress.

  7. Unknown-members ledger component-props-unknown-members.pin.test.ts: operation removed from ACTION_RUNNER_PATHS. Right — a typed member deletes its ledger line, as onSuccess did under PR feat(spec)!: a page action row's onSuccess is refused beside a runner built-in other than api / script, and beside no executor #23331; the comment is updated to say so.

  8. Producers: none in this repository (examples, CLI corpus, dogfood fixtures per the PR); objectui at pin 1fb1ced36243 and main b9814b3 carries operation: 'update' rows without onSuccess (still accepted) and objectui's registration already publishes operation as enum: ['update']. hotcrm NOT MEASURED — carried to ③.

Nothing in the diff touches packages/spec/src/ui/page.zod.ts (#23357's surface) — the claim's exclusion holds.

② Semver level

Changeset .changeset/23332-page-action-row-operation.md: '@objectstack/spec': minor, **BREAKING** banner, FROM → TO table, ADR-0087 registered disposition naming the D3 entry, measured producers stated. Matches what the diff publishes. The only package whose published accept set changes is @objectstack/spec (packages/lint gains test-only lines; content/docs is generated). minor for a pre-GA metadata-facing accept-set narrowing is ADR-0087's stated level (the 2026-09-13 #18003 amendment: "Pre-GA, a metadata-facing retirement or break ships minor, carrying the **BREAKING** banner and its ADR-0087 disposition entry"), and it is the level the sibling narrowings .changeset/23015-*, 23072-*, 15953-* carry. Check Changeset is green.

Clause-②: the PM claim declared yes conditionally (a possible widening of ActionSchema); the PR, the changeset and the dev report declare no (narrowing). The diff is a narrowing on every surface it touches and widens none: the rows refuse strictly more (items 1 and 2 above), ActionSchema's accept set is byte-for-byte the same table of refused keys with one message reworded (item 3), and the new helper is not on the barrel (item 4). The widening the claim hedged against did not occur, and triage's own first-grade note records exactly this outcome as "Clause ②: no if the rows take the refusal". Clause-②: no (narrowing) is right, and the PR body's acceptance note explains the deviation from the claim, as required. (The sibling changeset #23015 spells the same class yes (narrowing); the spelling convention is not this card's to settle — the direction of record for this card says no, and this review was rendered at contract tier regardless.)

Cosmetic, not a gate: the PR title is fix(spec)!: while the changeset heading is feat(spec)!:. Both carry !; the changeset is what versions.

③ Boundary flags

open_questions: [] — none to answer.

Dev flags (deviations and out_of_scope_findings in the os-dev-report, and the PR's acceptance notes), each answered or escalated:

  1. Clause-② yes (claim) vs no (narrowing) (PR). Answered in ②: the PR is right; the claim's yes was declared for a branch measurement ruled out.

  2. File surface: two test files outside the claim's listed surface — packages/spec/src/ui/component-props-unknown-members.pin.test.ts and packages/lint/src/validate-component-props-row-on-success.test.ts. Answered: accepted. The first is a packages/spec test the claim's "their tests" covers (a typed member must delete its ledger line or that pin goes red, which the dev's --project local run showed). The second is the os validate door's pin for the very refusal this PR adds, test-only (+26/-0), in the file PR feat(spec)!: a page action row's onSuccess is refused beside a runner built-in other than api / script, and beside no executor #23331 established for the rows' onSuccess door; it is the pin the card's "Wanted" implies for the author-facing surface, the dev stopped and explained it in the report, and the single-writer-path guard on the head is green. No source outside the surface was touched.

  3. Out of scope 1 (class b): the rows carry none of ActionSchema's other operation: 'update' refusals (patch without operation, target beside update, a non-script executor beside update, update with nothing to write, list_toolbar beside update each parse on a row and are refused by ActionSchema). Escalated, not fixed here — right. The card asks for one answer on the onSuccess pairing; these are the same family on other keys and the dev names a fold-in target ("the family's closing card") with dedupe words. For the adopting seat / triage: this finding has a static repro and no carrier card yet; a report comment is not a carrier (PR feat(spec)!: a page action row's onSuccess is refused beside a runner built-in other than api / script, and beside no executor #23331's review said the same of its own escalation, which became this card). Route it to the family's closing card or file it.

  4. Out of scope 2: the new entry's sentence "A page's properties is not parsed on the metadata save or load path" is true at this head and follows its three siblings' current wording, but PR feat(lint)!: the two page component rules run on the runtime publish door (ruling B) #23418 rewrites that sentence in the siblings. Carrier named: whichever of feat(lint)!: the two page component rules run on the runtime publish door (ruling B) #23418 / fix(spec)!: a page action row declares ActionSchema's operation and refuses onSuccess beside operation: 'update', with the action's text #23425 lands later aligns this entry. Answered: correct at this head, no action in this PR; the adopting seat should note the follow-up on feat(lint)!: the two page component rules run on the runtime publish door (ruling B) #23418 if this lands first.

  5. hotcrm NOT MEASURED (no checkout in the dev's container). Escalated as a residual: the card itself records that no producer was measured, the entry's replacement is a by-hand rewrite, and os validate reports the refusal at the row's operation / onSuccess so a hotcrm row carrying either shape is found at its next validate rather than silently dropped. Not a blocker for a minor pre-GA narrowing with a D3 entry; the adopting seat may measure hotcrm 0f84c9c separately if it wants a zero rather than an unknown.

  6. component.zod.ts:6030 ("a stored page still saves and loads") left as is per the PM pointer, since feat(lint)!: the two page component rules run on the runtime publish door (ruling B) #23418 owns that text. Answered: right, outside this diff.

  7. Check-runs in progress (18, named above). Not a dev flag, but the one open input: the pins, typecheck, docs build and migration-registry test for this head are theirs to confirm. This verdict is on the contract; the merge waits on them by branch protection.

Implemented-by: claude/issue-23332-action-row-operation
Reviewed-by: session_01D6F9axfoPzR7t8G1J7Ytzz

VERDICT: PASS

Adopted by domain:spec seat 3 (#18883), baozhoutao, session session_01D6F9axfoPzR7t8G1J7Ytzz, at 2026-10-14T08:08Z: rendered by an isolated read-only reviewer at CONTRACT_REVIEW_TIER on the brief from record-recognisers.mjs --brief-template; spot-checked by the seat (action-on-success-executor is on neither packages/spec/src/index.ts nor packages/spec/src/ui/index.ts at the head). ③-3 is carried by #23427 (the family closing card, filed by the seat).


Generated by Claude Code

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/m tests tooling

Projects

None yet

2 participants