e71063b16e
The durable JetStream stream of a persisted (ModeMatrix) room was created only
by the Go client's first publish/subscribe. A client that speaks only core NATS
(the browser client uniweb, which has no JetStream) therefore never created it,
so its messages were captured nowhere and lost on reload. Move stream ownership
to the control plane and expose the backlog over plain HTTP.
- handleCreateRoom ensures the room's stream (idempotent CreateOrUpdateStream)
BEFORE writing the room row, so the subject is captured from the first message
whoever publishes it. Done before the store write so a stream failure leaves no
orphan room. Skipped when no JetStream is wired (room still works, no history).
- New member-only GET /rooms/{id}/history?limit=N (default 200, hard cap 1000):
reads the stream server-side via the modern jetstream API (Stream.Info +
GetMsg by sequence, no consumer) and returns the last N frames oldest->newest
as {"messages":[<base64-std of the marshaled frame>]}. The server never
decrypts — it relays the E2E ciphertext bytes the stream already holds.
Existence is checked first (404), then membership (403); enforce rejects an
unsigned request with 401 before the handler runs.
- Lazy backfill: the history endpoint ensures the stream of a pre-existing
persisted room, so it starts capturing from now on. Messages sent before the
stream existed were never captured and are unrecoverable.
- The stream config (streamConfigForRoom) mirrors pkg/client/persist.go
byte-for-byte plus Replicas (matched to the control-plane KV replication). It
is copied rather than imported because pkg/client imports pkg/membership and
the reverse would be an import cycle; the source of truth is documented in a
comment.
- Server gains SetJetStream(js, replicas) to wire the privileged JetStream
context and the room-stream replication factor.
Tests (history_test.go): golden (3 frames round-trip in order, decodable),
core-NATS capture (the central fix), handleCreateRoom creates the stream, limit,
empty room ([] not null), 401 unsigned, 403 non-member, 404 missing room.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>