Repository navigation
Commit 69f342b
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 #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 (#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](https://claude.ai/code/session_01WnW7HvfnfHbLg1fpkHX3Mu)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 26501ae commit 69f342b
23 files changed
Lines changed: 1126 additions & 159 deletions
File tree
- .changeset
- content/docs/references/ui
- packages
- lint/src
- spec
- authorable-surface
- liveness
- src
- conversions
- migrations
- entries
- retired-keys
- semantic
- ui
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
0 commit comments