Repository navigation
feat(spec)!: retire a page action row's own patch — a row's operation: 'update' runs the declared action its name names (#24394) - #24442
Conversation
…uns its named action Claude-Session: https://claude.ai/code/session_01WnW7HvfnfHbLg1fpkHX3Mu Co-authored-by: Claude <noreply@anthropic.com>
…keys, D3 entry, step-18 rationale) Claude-Session: https://claude.ai/code/session_01WnW7HvfnfHbLg1fpkHX3Mu Co-authored-by: Claude <noreply@anthropic.com>
…ow patch tombstone Claude-Session: https://claude.ai/code/session_01WnW7HvfnfHbLg1fpkHX3Mu Co-authored-by: Claude <noreply@anthropic.com>
…tion rule Claude-Session: https://claude.ai/code/session_01WnW7HvfnfHbLg1fpkHX3Mu Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WnW7HvfnfHbLg1fpkHX3Mu Co-authored-by: Claude <noreply@anthropic.com>
…in in the repo project Claude-Session: https://claude.ai/code/session_01WnW7HvfnfHbLg1fpkHX3Mu Co-authored-by: Claude <noreply@anthropic.com>
…w-update-runs-declared-action # Conflicts: # packages/spec/src/migrations/registry.ts.template
📓 Docs Drift CheckThis PR changes 1 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 5 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 143 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # 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
|
Contract reviewServed-tier: Inputs read: card #24394 (body and all 5 comments, the seat's claim ① Derived judgmentsEach accept-set or surface change the diff implies, judged against the cited code.
② Semver level
③ Boundary flagsDev flags (report
Reviewer's own flags:
Implemented-by: VERDICT: PASS Generated by Claude Code |
席位核对ACCEPT ·
Generated by Claude Code |
Fixes #24394
Clause-②: yes (narrowing)
What
A page action row's
operation: 'update'runs the declared action itsnamenames. The platform action route resolves that declaration by name (ADR-0110 D2, "resolve, then address: declaration first") and writes the declaration's ownpatch, merged under the request's params, which it judges against the declaration (ADR-0104 D2, landed asd79dc4883c). The row's ownpatchreached no request. This PR takes route B from the seat's claim6257461893: the row'spatchretires, and the rows' describes and their half of the declarative-update judge are re-trued to what the route writes.packages/spec/src/ui/component.zod.tspatchbecomes aretiredKey()tombstone onaction:button,action:iconand the sharedaction:group/action:menumember (rowPatchRetired, which replaces the by-referenceactionPatch). The threeoperationdescribes, the nodeparamsdescribes and the threenamedescribes now say the row runs its named action, needs anamebesideoperation: 'update', and that its staticparamsare written only where that action declares them.rowDeclarativeUpdatepasseswritesNamedAction: true.packages/spec/src/ui/action-declarative-update.tsstaticParamsretires; the surface flag iswritesNamedAction. On a row, rule 1 ("patchwithoutoperation") stands down, because the tombstone refuses every rowpatch. Rule 4 ("nothing to write") asks for a non-emptyname, refused atname. OnActionSchemaboth rules and their texts are unchanged.packages/spec/src/ui/action.zod.tsstaticParams: falsebecomeswritesNamedAction: false(behaviour-neutral)packages/spec/src/conversions/registry.tspage-action-row-patch-removed(toMajor 18,retiredFromLoadPath,retiredAfter: '17.7.0', order 70), stripping from every node and member, scoped by component typemigrations/entries/retired-keys/18.ui__ActionButtonProps__patch.ts,18.ui__ActionIconProps__patch.tsRETIRED_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+ aSTEP18_RATIONALEfragment (order 104, after the merge of538f923cb9, beside #24417's order-103 fragment)migrations/entries/semantic/18.ui-page-action-row-{operation-declared,declarative-update-rules}.ts,18.ui-action-group-menu-members-typed.tspatch, re-trued so the 18 upgrade guide does not contradict itselfpackages/spec/liveness/page.jsonregionsrow's evidence segment [23]: the rowpatchclause now records the measured read (Undo only, never posted) and the tombstone. Status andverifiedAtare unmoved.authorable-surface/ui.json(two[RETIRED]lines),content/docs/references/ui/component.mdx.changeset/24394-page-action-row-patch-retired.md@objectstack/specminor, BREAKING, FROM → TO, one-line fix, ADR-0087registeredmarkerPremises P1–P3 (tested before building; all hold)
patchreaches no write request. objectui at the pin0707b4ae37and atmain4b7ce47has identical code.ActionRunner.executeUpdateOperation(pinpackages/core/src/actions/ActionRunner.ts:1782, thepatchread at:1799–:1802; main:1801,:1818–:1821) readsaction.patchonly intowritten, which feeds the Undo snapshot. The request iswire = { ...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).examples/**orpackages/apps/**. Control: the same grep findselement: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'spatchinput onaction:button/action:icon(action-button.tsx:591,action-icon.tsx:422) is an advertisement, not an authored instance.patchat the pin. objectui non-test source importsActionButtonPropsonly forsize(packages/types/src/ui-action.ts:900), and the four row schemas whole intopackages/types/src/zod/public-blocks.zod.ts(propsBag). That helper already composes tombstoned rows (page:header,element:number).UIActionSchema.patchisSpecAction['patch'], which isActionSchema's and untouched. The first P3 grep was truncated byheadand missed theActionButtonPropsimport; the untruncated re-read above is the reading.Clause-② measured on the built rows
Both directions. Narrowing: every row
patchis refused, and a row withoperation: 'update'and noname(an input list or staticparamsincluded) is refused atname. Widening: a row withoperation: 'update'and anamebut nopatchand noparamsnow parses; before, it was refused atpatchas having nothing to write. So the claim's provisionalyes (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 nonamehas 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 namelessoperation: 'update'row atname, and accepts a named one with nopatchand noparams. 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
packages/spec/src/ui/component-action-row-patch-retirement.test.ts, in therepoproject (listed invitest.repo-tests.json). It covers:patchvalue on all four rows is ONEinvalid_typeissue at the key, with the prescription, and nounrecognized_keys(strict-arm control);patchparses;never(@ts-expect-error) and the walked shape keeping the key;ActionSchema.patchuntouched;@objectstack/specalready declares, with a structural matcher (apatchkey inside an object whosetypeis a page action row) and anti-vacuity cases.component-action-row-declarative-update.test.tskeeps 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 noname", "paramsare no write of the row's own", and "the action's half does not move".patchonly as a way to write a valid update):component-action-row-on-success-executor.test.tsandpackages/lint/src/validate-component-props-row-on-success.test.ts.component-props-unknown-members.pin.test.tsdrops the four rowpatch{}lines (the member isnevernow).retired-key-removal-version.pin.test.tsgains the JOIN row.validate-component-props.test.tspinscomponent-props-invalid+error+ path + prescription on each node and member, and the nameless-row refusal atproperties.name.Tests and gates (union run after the final commit, head
d6976f1c92)@objectstack/spec:pnpm testgave 696 files, 21258 passed.typecheckexit 0, test layer included: the@ts-expect-errorcompiles against an identity-pinned debt ledger that held.@objectstack/lint(downstream of spec):test145 files, 6866 passed;typecheckexit 0.@objectstack/metadata-protocol(downstream, the page save door):test246 files, 20655 passed.test:repofor 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 isTest Core's.node scripts/pm/dispatch-gates.mjs --ranreported 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-onlycheck:dev-prereqs: exit 0.scripts/ablation-replace.mjs, anchor count x1 → x0, restored by blob hash and emptygit 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..d.ts: a throwaway file inpackages/lint/srctyped anActionButtonPropsliteral withpatch.tscwent red on that literal only (TS2353 against the tombstone'sneverbrand); the control literal compiled. The file was deleted and the tree verified clean.Acceptance notes
pnpm check:platform-checklistexits 1 onorigin/main(e22defb13c) and at this branch's base.docs/qa/platform-checklist/areas/access-security.jsonanchorspackages/core/src/security/resolve-authz-context.ts#legacyGrantAnchorRetired, whichb2e78701f7(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).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) andundoableWrittenKeys(predicate-fields.ts:155) read it, and the registry lists apatchinput. So the trackedsdui.manifest.jsonadvertisespatchonaction:button/action:iconto the html-tier JSX gate until the pin moves. After this lands, the Undo snapshot of a row with nopatchcovers only collected params, not the named action'spatchthe route wrote. That is the follow-up the seat files.mainwas 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 underpackages/specand none in this diff.Generated by Claude Code