feat(spec)!: action:button / action:icon refuse endpoint with ActionSchema's rename to target, read from one table (#21005) - #21122
Conversation
…ema's target rename, read from one table Claude-Session: https://claude.ai/code/session_017VaLJnYwhPsanVCe9dMCJU Co-authored-by: Claude <noreply@anthropic.com>
…rd (proof 2: def unreachable from metadata roots) Claude-Session: https://claude.ai/code/session_017VaLJnYwhPsanVCe9dMCJU Co-authored-by: Claude <noreply@anthropic.com>
…tep-18 rationale, pins Claude-Session: https://claude.ai/code/session_017VaLJnYwhPsanVCe9dMCJU Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017VaLJnYwhPsanVCe9dMCJU Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017VaLJnYwhPsanVCe9dMCJU Co-authored-by: Claude <noreply@anthropic.com>
…tion-row-endpoint-refused
…tion-row-endpoint-refused
…ment follows main's newest Claude-Session: https://claude.ai/code/session_017VaLJnYwhPsanVCe9dMCJU Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 13 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 8 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 137 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 95d207c6f033d7626f86435740fc3e6336c70bdd && git checkout 95d207c6f033d7626f86435740fc3e6336c70bdd
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 99398542b328eb06be8dbd781200f06a5df608b0 dcaabdbbb6cb0348653b5f2273b8dedfaf87263e && git checkout -B drift-repro 99398542b328eb06be8dbd781200f06a5df608b0 && git merge --no-ff dcaabdbbb6cb0348653b5f2273b8dedfaf87263e
node scripts/docs-audit/affected-docs.mjs --json 99398542b328eb06be8dbd781200f06a5df608b0
|
Contract reviewServed-tier: Card #21005 · PR #21122 · written 2026-10-01T09:26Z. Inputs: the card body and its five comments (triage 5923326115, serial note 5923644301, claim 5925158541, dev report 5928425328, claim amendment 5928458619), the PR body, its 12-file list, the net diff against the merge base ① Derived judgments
No accept-set or public-surface change beyond these nine was found in the diff. ② Semver level
Clause-②: yes (narrowing) ③ Boundary flagsThe seat's four points.
The dev's deviations. (1) The landing site: answered by judgment 4, right. (2) Scope: answered by point 2. (3) Conversion shape: answered by point 3. (4) H4 partly refuted: answered by point 4. (5) The AGENTS.md conflict: answered by point 1. (6) Two merges and the fragment's move from 54 to 55: order 55 is unique and the list's maximum plus one, and the head's gates are green. (7) Consumer suites narrowed locally: the check-runs on the head are the gate verdicts (below), all green; the local narrowing is not a reading this record relies on.
Check-runs on the head, read at 2026-10-01T09:23Z: 35 runs, 33 Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #21005
Clause-②: yes (narrowing)
What changes
action:button/action:icon(ComponentPropsMap) no longer declareendpoint. They refuse it with the renameActionSchemaalready prints, read from ONE table, so the action and the two blocks that run it give one verdict for one concept.packages/spec/src/ui/action-target-aliases.tsholdsACTION_TARGET_ALIASES(url/endpoint/path/hrefmapped totarget).ActionSchema'sstrictObjectaliases spread it in place of its inline row, and the rows' sharedACTION_NODE_ALIASESspreads the same object. The message comes from the onestrictUnknownKeyErrorrenderer. Measured on source after the change: the action gives "Did you meanendpoint→target?", andaction:buttonandaction:icongive the same clause. The module is reached by relative import only (thefilter-rule-array.tsprecedent). Exported fromaction.zod.ts, it would have ridden theuibarrel into the published API surface.pathwith the edit-distance guesspatch.patchis the declarative write's field values,z.unknown(), and it parses. Reading the whole target row of the table fixes that too: all four aliases now rename totargeton the action and on both rows.action-block-endpoint-to-target(protocol 18,MAJOR_18_CONVERSIONSorder 52, retired from the load path,retiredAfter: '17.5.0'). Its shape followsfield-reference-to-alias: the schema refuses the key through an alias, and the entry is retired from day one. Onaction:button/action:iconblocks whoseactionTypeisapiit renamesendpointtotarget, usingrenameKey's house precedence: a redundant twin is dropped, and a disagreeing pair is kept. It reaches every positionmapPageComponentsvisits. Sites with no lossless rewrite are left byte-identical and reported throughcontext.reportTodo: noactionType(the runner's legacy fallback calls anendpointwith no type as an API call, and atargetwith no type calls nothing), anotheractionType, a non-stringendpoint, or a disagreeing pair.action-block-endpoint-spelling-retired(one entry file plusgen:migration-registry), and aSTEP18_RATIONALEfragment (order 55).ui/Action{Button,Icon}Props:endpointlines leaveauthorable-surface/ui.json, and thecomponent.mdxreference loses its twoendpointrows.check:authorable-surfaceproves the deletion itself with proof 2 (the def is not reachable from the 31 metadata-type roots).spec-changes.jsonand the upgrade guide do not move, because both fold majors only up to the current protocol major, 17.Mechanism hypotheses, measured
2821e9f15b,git grepover every tracked file found noaction:button/action:iconnode that authorsendpoint. The checked set was examples, templates, platform pages and fixtures. The only hits were the spec row test's key-set list and comments. At objectui5262f7dd, there is noexamples/appsdirectory.examples/and every authoredendpoint:outside the renderers are tests, and objectui's registration never publishedendpointas a designer input. So nothing in either tree needed a fixture rewrite. The one in-repo pin naming the key, the whole-key-set test incomponent-action-element-rows-20371.test.ts, drops it from the declared set.ActionSchemaprints, not against a hand-copied string.field-reference-to-alias(alias refusal plus a D2 entry retired from day one plus a D3 entry plus a rationale fragment). The page-block walking andrenameKeyprecedence followpage-card-body-to-childrenandrecord-picker-display-field-to-label-field. Theapi-only scope followsinline-action-api-params-to-body-extra.check-adr-0087-registrationpasses withregistered action-block-endpoint-to-target, action-block-endpoint-spelling-retired.os validate/os build/os lint, and only as the advisory props gate. Measured throughvalidateComponentPropson a stack with{ type: 'action:button', properties: { actionType: 'api', endpoint } }: onewarningcomponent-props-unknown-keyatproperties.endpoint, carrying "Did you meanendpoint→target?". Thetargetcontrol gives no finding.defineStackaccepts the page either way:PageComponent.propertiesis an open bag. The metadata save path does not judgepropertiesby type. That is the posture recorded for every row of this map; this card does not change it.Tests
packages/spec/src/ui/component-action-row-endpoint-21005.test.ts: both rows refuseendpoint(unrecognized_keys, keyendpoint), and their rename clause equalsActionSchema's.targetparses as the control.endpointis absent from both shapes. Every alias in the shared table renames identically on the action and on both rows.packages/spec/src/conversions/action-block-endpoint-to-target.test.ts: the stored-row seam rewrites a storedendpointtotargeton both blocks, and the result parses against the row. Twin and disagreeing-pair handling. The three TODO classes, each byte-identical with a reason. Anendpointon another block type is left alone. Copy-on-write identity, idempotence, and that the authoring funnel does not replay the entry.component-action-element-rows-20371.test.ts: the whole-key-set pin dropsendpoint, with a comment naming why.dist/*.d.ts: a typedActionButtonProps/ActionIconPropsliteral carryingendpointfailstscwith TS2353 on both lines, and thetargetline compiles. The temp file was removed and the tree is clean.Readings, gate commands and exit codes are in the dev report on #21005.
Acceptance notes
retiredKey()tombstone. Triage's direction on this card is the alias prescription read from one table, with no second message string, so this PR follows the direction. The alias carries the FROM → TO, andtscstill refuses the key, as TS2353 without the[REMOVED]mark. The authorable-surface gate accepts the deletion on its own proof 2. Recorded here rather than chosen silently.hintfor an unknown key reads "Removeendpoint, or declare it onaction:button's props schema if the component honours it". When the message carries a rename, the second half points the other way. Noted, not filed (validate-component-props.ts; carrier: none).endpointentries booked to objectui#11168 strike.registry-inputs-spec-parity.test.tscountsunpublishedKeys: 3(the twoendpointentries andundoable); that should become 1. The two renderers'endpoint: schema.endpointforwards inaction-button.tsx/action-icon.tsxlose their spec counterpart. The runner's deprecatedendpointdialect inActionRunner.executeAPI/execute's legacy fallback stays objectui's to retire. The seat files that coordination child after landing.Generated by Claude Code