You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* feat(spec)!: action closes, and the ADR-0010 envelope debt list reaches zero (#4001)
`ActionParamSchema` has been strict since #3746 — the template this campaign was
generalized from, and the source of its sharpest lesson: `visibleWhen` →
`visible` showed the most valuable alias entry is rarely a typo but a key that
reads as a control and silently is not one. The action AROUND the param stayed
open for three more releases.
The AI exposure block is why this one mattered. `ActionAiSchema` is the
governance gate — its own doc says a half-finished or unreviewed action must
never be silently armed — yet `requireConfirmation`, one letter off, was dropped
in silence, so an author who asked for a human-in-the-loop gate on an
AI-invoked action did not get one and was not told.
The root tombstones the key this campaign cares most about. `permissions` on an
action is NOT a permission gate: authorization comes from the object's
permission sets and, on the AI surface, from the agent's access/permissions
(#1884). The rejection says so and names the trap beside it — `visible` and
`disabled` are UI predicates, they hide or grey a button, they do not stop a
request. Third time in this campaign after skill.permissions and
agent.visibility, always a different surface, always reading like a control.
The undeclared-envelope debt list is now EMPTY. The structural walk opened it
with eight names after replacing a probe that had been hiding seven; `action`
was the last. The empty set is kept, not deleted: with no exemptions the
DECLARES case runs over every registered type, so a new type shipping without
the spread fails immediately instead of being quietly added to a list.
Registered types closed: 24 of 25. Only `view` remains.
Two lint-layer facts recorded rather than papered over: the array-index test has
run out of subject (no strip-mode array-of-objects remains anywhere), and `view`
is the last open root — when it closes, change the floor to 0 and assert the
empty set deliberately rather than deleting the test, because an empty result
nobody chose is indistinguishable from a derivation that broke.
Verified: 284 files / 7240 tests, tsc clean, 8 generated artifacts current, all
spec gates green, and CRM/Todo/showcase/platform-objects build.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY
* fix(spec): an action's capability gate is real — point the near-misses AT it (#4001)
The first draft of this batch's guidance claimed `permissions` on an action is
not a permission gate and sent authors to the object's permission sets. That is
wrong: `requiredPermissions` (ADR-0066 D4) is a declared key enforced with a 403
on the platform action route, and `ui/actions.mdx` teaches it by name. Anyone
following the prescription would have been invited to delete a working gate.
- `permissions` / `capabilities` / `requiresPermissions` / `acl` now RENAME
onto `requiredPermissions` instead of pointing away from it.
- The dead `requiredPermissions` guidance entry is removed — the key is
declared, so strict never reaches it; the entry was wrong AND unreachable.
- `location` → `locations` was backwards: `locations` is the declared key, so
the alias would have suggested renaming a correct key to a nonexistent one.
- The `visible`/`disabled` warning survives on `hidden`, where it belongs:
hiding is not gating, and a headless action is `locations: []`.
Ledger finding 7 for the fourth time — this campaign's own prescriptions are a
surface that can be confidently wrong. Caught by checking the docs the drift
report flagged against the schema rather than against memory of it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY
* test(metadata-protocol): a stored-row fixture wrote `object` where the key is `objectName` (#4001)
`ActionSchema` has never declared `object` — the key is `objectName`. Two
stored-row fixtures wrote it anyway, `.strip` ate it, and the tests passed. With
the shape closed the row now fails spec validation after conversion and the
protocol reports `invalid: 1`.
Worth naming because of where it was: these fixtures stand in for pre-17 rows in
a database, so a typo in one reads as a claim about real legacy data — "stored
actions carry `object`" — when no such key ever existed. There is no missing
conversion here; `git log -S` finds no commit that ever declared it.
Sixth strip-era fiction this campaign has found in a test, and the first dressed
as a stored ROW rather than an authored literal.
Also the reason this was caught by CI rather than locally: the previous pass ran
only `packages/spec`. The blast radius of closing a registered type is every
package that parses that type.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY
---------
Co-authored-by: Claude <noreply@anthropic.com>
`action` rejects unknown keys, and the ADR-0010 protection-envelope debt list reaches zero.
6
+
7
+
`ActionParamSchema` has been strict since #3746 — the template this whole campaign was generalized from, and the source of its sharpest lesson: `visibleWhen` → `visible` showed that the most valuable entry in an alias table is rarely a typo, it is a key that reads as a control and silently is not one. The action *around* the param stayed open for three more releases.
8
+
9
+
**The AI exposure block is the reason this one mattered.**`ActionAiSchema` is the governance gate — its own doc says the platform's value is that "a human can govern exactly which capabilities the agent fleet is allowed to invoke", and that "a half-finished or unreviewed action must never be silently armed". Yet `requireConfirmation` (one letter off `requiresConfirmation`) was dropped in silence, so an author who asked for a human-in-the-loop gate on an AI-invoked action did not get one and was not told. Both that block and the action root now reject, with prescriptions for the two keys authors reach for at the wrong level (`exposed` and `requiresConfirmation` belong under `ai`).
10
+
11
+
**An action's capability gate is real, and the near-misses now rename onto it.**`requiredPermissions` (ADR-0066 D4) is enforced with a 403 on the platform action route, so `permissions` / `capabilities` / `acl` are aliased to it rather than being told the gate lives elsewhere. What *is* tombstoned is the trap beside it: `visible` and `disabled` are UI predicates — **they hide or grey a button, they do not stop a request** — and an action with no UI surface is `locations: []`, still gated.
12
+
13
+
That entry was wrong in the first draft of this change, in the direction that matters. It claimed an action carries no permission key and sent authors to the object's permission sets, which — had anyone followed it — invites deleting a working `requiredPermissions` gate. Caught by checking the docs the drift report flagged (`ui/actions.mdx` teaches exactly that key) against the schema. It is the ledger's finding 7 for the fourth time: **this campaign's own prescriptions are themselves a surface that can be confidently wrong**, and the only defence is verifying each one against the schema rather than against memory of it.
14
+
15
+
`resultDialog` and its fields, the AI param hints, and the `bodyShape` wrapper close alongside.
16
+
17
+
**The undeclared-envelope debt list is now empty.** The structural walk opened it with eight names (`action`, `book`, `field`, `job`, `mapping`, `page`, `translation`, `validation`) after replacing a probe that had been hiding seven of them; `action` was the last. The empty set is kept rather than deleted — with no exemptions, the `DECLARES the protection envelope` case now runs over every registered type, so a new type shipping without the spread fails immediately instead of being quietly added to a list. Adding a name back is filing a bug, not granting an exemption.
18
+
19
+
Registered types closed at the top level: **24 of 25**. Only `view` remains.
20
+
21
+
Two things the lint layer surfaced, recorded rather than papered over:
22
+
23
+
-**The array-index test has run out of subject.** It was `pages[].regions[]`, then `objects[].actions[]`; with `action` closed there is no declared array-of-objects left anywhere in the registered surface that is still strip-mode. The walker's array handling is unchanged and still correct — what is gone is any metadata type that exercises it. The test now asserts the hand-off plus the per-node descent under a closed root (the #4522 fix), and says in place that an indexed assertion should be restored if a new strip surface ever appears.
24
+
-**`view` is the last open root**, so when it closes this layer has nothing left to warn about at a root. The test says to change the floor to 0 and assert the empty set *deliberately* — not to delete the test, because an empty result nobody chose is indistinguishable from a derivation that broke.
25
+
26
+
Authoring impact: a key `ActionSchema` does not declare is now rejected instead of silently discarded — it was already being ignored, so no working action changes.
Copy file name to clipboardExpand all lines: content/docs/references/ui/action.mdx
+7Lines changed: 7 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -113,6 +113,13 @@ const result = Action.parse(data);
113
113
|**opensInNewTab**|`boolean`| optional | Open the action result in a new tab. The renderer pre-opens the tab synchronously on click (popup-blocker-safe) and navigates it to the handler's redirectUrl. |
114
114
|**newTabUrl**|`string`| optional | Direct new-tab URL template (`{recordId}` placeholder). When set with opensInNewTab, the renderer navigates the pre-opened tab here immediately — no action POST. The endpoint must enforce auth itself. |
0 commit comments