Skip to main content
Status: Proposed (RECONSTRUCTION — requires validation) · Date: 2026-08-17 (reconstructed) Refs: ../constitution/founding-constitution.md
⚠️ Reconstructed from the material we have, not recovered. Tags: [derived] is supported by evidence in the repository · [inferred] is deduced · [proposed] is a gap I filled.

Context

A console showing live queue depth, a member portal updating a balance, an operator watching an import — all need server-to-client push. The options are managed (Pusher, Ably, Supabase Realtime) or self-hosted WebSockets, which means owning connection state, scaling and reconnection. [inferred]

Decision

Pusher Channels, with tokens signed by our API. [derived] A client never authenticates directly against Pusher. It asks our API for a signed token scoped to the channels it may hear, which keeps channel authorization inside our permission model rather than in a second one. [inferred] Realtime is a consumer of the outbox (ADR-004), not a parallel publish path. [derived] Anything delivered in realtime already happened and is already recorded.

Consequences

  • No connection state, no scaling of long-lived sockets, no reconnection logic.
  • Channel authorization uses our permissions, so there is one authorization model. − A vendor in the hot path of a user-visible experience, and a per-connection cost. − Realtime is behind RealtimePort (ADR-003), so replacing Pusher is an adapter — which is exactly why the port exists. [inferred]

What I could not determine

Whether Supabase Realtime was considered and rejected, given Supabase is already the database. It would be the obvious alternative and I would expect the original ADR to say why not. [proposed]