← stitchkitDocumentation

ADR 0069 — Realtime contracts validate without owning delivery

  • Status: Accepted — extends ADR 0008; the raw binary lane in ADR 0020 remains orthogonal.
  • Date: 2026-08-10

Context

HTTP operations and Socket.IO events have different identities. HTTP has method, path, scope and exposure; realtime has two directional registries, variadic tuple arguments and acknowledgements travelling against the event direction. Folding both into defineContract would either weaken those distinctions or turn the HTTP contract into a transport union.

Runtime validation also has two owners. An inbound peer payload is untrusted and must be dropped without crashing the listener. An invalid outbound payload is a local programming error and must fail at the call site. Rooms add an emit-only target, while connected sockets support both subscription and emission.

Decision

Realtime uses the separate defineRealtimeContract primitive. It derives both event maps from Zod tuple schemas and validates inbound arguments, outbound arguments and acknowledgement values. The registry is closed at runtime: unknown event names are contract violations, not best-effort emissions.

Inbound rejection is reported through onRejected and the message is dropped. Outbound rejection throws REALTIME_CONTRACT_VIOLATION because the application owns that defect. A bad local acknowledgement is reported and dropped rather than thrown into a peer-controlled listener frame. Every rejection names event, direction, phase and local/peer fault; hook failures cannot break transport.

Targets are capability-based. Server and connected-socket targets may subscribe and emit; room/broadcast targets are valid emit-only targets and resolve on only when subscription is actually requested. The server exposes the raw socket for Socket.IO-owned policy such as auth and room membership; the browser client keeps its durable subscription abstraction and does not expose a competing raw transport.

The contract layer validates delivery calls but does not own presence, guaranteed delivery, replay, RPC, room authorization or transport selection.

Alternatives rejected

  • Put events inside defineContract: HTTP-only fields become meaningless and tuple/ack semantics become an awkward special case.
  • Replace Socket.IO: duplicates reconnection, heartbeat and delivery machinery forbidden by ADR 0008.
  • Throw for malformed inbound peer data: one hostile packet can crash an application listener.
  • Silently drop invalid outbound data: hides a local programming error at its cheapest diagnostic boundary.
  • Require subscription capability from every target: room broadcast operators are emit-only and fail during construction despite supporting the requested operation.
  • Expose a raw browser socket: bypasses validated durable subscriptions and creates two client APIs for the same connection.

Consequences

  • Shared Zod schemas are the single source of event and acknowledgement types.
  • All supported target capabilities require direct runtime tests.
  • Applications retain Socket.IO policy and lifecycle ownership.
  • Rejection telemetry is structured and deterministic without turning peer input into process failure.