Shared peer/ping action — the protocol action that exercises the
server→client request/response direction of ActionPeer.
peer/ping is initiator: 'both': the client→server direction overlaps
with heartbeat, but the new direction this action drives is the reverse —
a connected client invokes peer/ping over its socket, and the handler
turns around and *initiates* a peer/ping request back to that same socket
(ctx.request_client), awaits the client's echo, validates it against
PingResponse, and returns the validated shape. So one client RPC drives
the whole round-trip and every outcome surfaces as that RPC's wire response.
Public auth. A liveness echo is non-sensitive; the WebSocket upgrade
itself still authenticates, so only admitted sockets reach the handler.
No return transport (HTTP RPC). Over HTTP there is no socket to ping —
ctx.request_client is absent — so the handler refuses with
peer_no_transport rather than method_not_found. That requires the action
to be mounted on the HTTP RPC endpoint as well as the WS endpoint (it is, via
the spine's full mount); the manifest excludes it as a protocol action so the
cross-impl diff stays apples-to-apples.
The data.reason discriminators and the PingResponse shape are the
cross-impl wire contract — twins of the Rust spine's REASON_PEER_*
constants and PingResponse (parity by test, not codegen).
Frontend-safe. Like heartbeat.ts / cancel.ts, this module names
the dispatcher types (ActionContext, RpcAction) type-only and builds
peer_ping_action as a plain literal — it never imports the runtime
action_rpc.ts (which would drag the HTTP dispatch core + Hono into any
frontend that pulls protocol_action_specs). The frontend responder
(peer_ping_responder) lives here too so a real frontend can answer the
server's probe.