# ADR 0137: Live state opens one continuous source generation

> Source: https://github.com/max-listov/stitchkit/blob/master/docs/decisions/0137-live-state-opens-one-continuous-source-generation.md

---

# ADR 0137 — Live state opens one continuous source generation

## Context

An open realtime transport does not prove that application state is current. Reading a snapshot
and attaching an event listener as unrelated operations can miss the change between them. Several
applications need the same small state machine—buffer events while a snapshot is opening, reject
gaps, fence old generations and clean up—but their schemas, cursors, replay and reconnect policy
are different.

Socket.IO already owns transport connection and retry. Contract HTTP streams already own framing
and schema validation. A new transport adapter, cursor format or event store would duplicate those
boundaries rather than solve the shared race.

## Decision

The browser-safe root exports `createLiveStateController`. Its host supplies one typed `source.open`
operation, a synchronous provider-owned event classifier/reducer, explicit item/byte limits and
exact event sizing. `open` receives `onEvent` before doing asynchronous work and resolves with a
snapshot only when every event after that snapshot's consistency point has already been or will be
delivered to that callback.

The controller owns finite pre-snapshot buffering, ordered drain, observable phases/counters,
duplicate/gap outcomes, generation fencing, subscriptions and explicit resync. It fails closed on
gaps, overflow and source loss. It never automatically retries, compares opaque cursors, replays
commands, validates schemas, stores events or claims that caller-owned work can be forcibly
cancelled. A separate snapshot request followed by subscription does not satisfy the source
contract; a single typed HTTP stream beginning with its snapshot does.

Socket.IO and HTTP remain different bindings over the same semantic boundary. Socket.IO can
establish subscription/room membership and acknowledge the matching snapshot; an HTTP response can
emit a validated snapshot as its first frame and subsequent events in that same response
generation. Existing renderers, React Query caches and managed resources may subscribe without
becoming dependencies of the controller.

## Consequences

- Applications share one race-free receiver state machine while retaining their own wire,
  authorization, schemas, cursors, storage and reducers.
- Physical reconnect and application resynchronization remain separate facts; a reconnected socket
  may still require a fresh source generation.
- Memory is explicit and finite before the snapshot boundary. Late work from retired generations
  cannot mutate current state, and non-cooperative source cleanup cannot hold controller settlement.
- The root remains browser-safe and peer-free. No second Socket.IO adapter, HTTP framing layer,
  React hook library, event database or process supervisor is introduced.

The external-store subscriber is synchronous. Async subscribers are removed on their first
thenable result so one unresolved rendering task cannot be retained per event. At most two
physical source open/cleanup operations in total may remain unsettled. Crossing that operation
bound publishes `unavailable/controller-capacity`; settlement publishes
`resync-required/controller-capacity` so the host can retry explicitly without a controller-owned
transport loop. Source cleanup is idempotent because AbortSignal and the returned close handle may
observe the same retirement.
