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:
1. 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.
2. 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.
3. 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).
4. 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 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.