Shared registration loop for action-dispatcher endpoints.
create_rpc_endpoint (HTTP RPC) and register_action_ws (WebSocket)
both build a Map<method, RpcAction> from a list of action specs and
gate the build on the same registry-time invariants:
- Auth-shape biconditional — per assert_route_auth_acting_biconditional
in http/auth_shape.ts: `auth.actor !== 'none' ⟺ input declares
acting?: ActingActor`. Fires for every spec with non-null auth.
- Rate-limit / account axis —
rate_limit: 'account' | 'both'
requires auth.account === 'required'; without an account on the
request there is no key for the per-account bucket. - JSON-RPC §4.2 wire validity —
request_response specs whose
handler will reach the dispatch map must not use z.null() for
input (the wire format forbids "params": null; use z.void()
for parameterless methods). - Duplicate method names — JSON-RPC keys on
method, so every
spec in the array must declare a unique method regardless of
kind / handler presence.
Pre-consolidation each dispatcher inlined these checks; the comment
in actions/register_action_ws.ts literally said "mirrors the HTTP RPC
registration check" but nothing kept them mirrored. Centralizing the
loop closes the most likely future drift surface.