diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index b49183d..2d2fb0a 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -61,7 +61,7 @@ These rules govern every task. Any deviation must be discussed and documented as ### Phase 1.5 — Live broadcast -**Status:** ⬜ Not started +**Status:** 🟩 Done **Outcome:** WebSocket endpoint inside the Processor that fans live position updates from Redis to subscribed [[react-spa]] clients. Cookie-based auth via Directus's `/users/me`, per-event subscription with one-time authorization at subscribe time, snapshot-on-subscribe, multi-instance per-instance consumer-group fan-out. The wire spec is `docs/wiki/synthesis/processor-ws-contract.md`. Unblocks the SPA's live-map feature for the Rally Albania 2026 dogfood. [**See `phase-1-5-live-broadcast/README.md`**](./phase-1-5-live-broadcast/README.md) @@ -71,9 +71,9 @@ These rules govern every task. Any deviation must be discussed and documented as | 1.5.1 | [WS server scaffold + heartbeat](./phase-1-5-live-broadcast/01-ws-server-scaffold.md) | 🟩 | `b8ebbd0` | | 1.5.2 | [Cookie auth handshake](./phase-1-5-live-broadcast/02-cookie-auth-handshake.md) | 🟩 | `190254d` | | 1.5.3 | [Subscription registry & per-event authorization](./phase-1-5-live-broadcast/03-subscription-registry.md) | 🟩 | `38de4bc` | -| 1.5.4 | [Broadcast consumer group & fan-out](./phase-1-5-live-broadcast/04-broadcast-consumer-group.md) | ⬜ | — | -| 1.5.5 | [Snapshot-on-subscribe](./phase-1-5-live-broadcast/05-snapshot-on-subscribe.md) | ⬜ | — | -| 1.5.6 | [Integration test (testcontainers Redis + Postgres + Directus stub)](./phase-1-5-live-broadcast/06-integration-test.md) | ⬜ | — | +| 1.5.4 | [Broadcast consumer group & fan-out](./phase-1-5-live-broadcast/04-broadcast-consumer-group.md) | 🟩 | `c07ea0e` | +| 1.5.5 | [Snapshot-on-subscribe](./phase-1-5-live-broadcast/05-snapshot-on-subscribe.md) | 🟩 | `f4b50ca` | +| 1.5.6 | [Integration test (testcontainers Redis + Postgres + Directus stub)](./phase-1-5-live-broadcast/06-integration-test.md) | 🟩 | `2f2cf5c` | ### Phase 2 — Domain logic diff --git a/.planning/phase-1-5-live-broadcast/04-broadcast-consumer-group.md b/.planning/phase-1-5-live-broadcast/04-broadcast-consumer-group.md index 7c59895..f06242e 100644 --- a/.planning/phase-1-5-live-broadcast/04-broadcast-consumer-group.md +++ b/.planning/phase-1-5-live-broadcast/04-broadcast-consumer-group.md @@ -1,7 +1,7 @@ # Task 1.5.4 — Broadcast consumer group & fan-out **Phase:** 1.5 — Live broadcast -**Status:** ⬜ Not started +**Status:** 🟩 Done **Depends on:** 1.5.3 **Wiki refs:** `docs/wiki/synthesis/processor-ws-contract.md` §Streaming updates, §Multi-instance behaviour; `docs/wiki/concepts/live-channel-architecture.md` §Multi-instance Processor @@ -218,4 +218,8 @@ Per the contract: omit fields rather than send `null` for absent values. ## Done -(Filled in when the task lands.) +Landed in `c07ea0e`. Key implementation decisions: + +- `CodecError`/`decodePosition` moved to `src/shared/codec.ts`; `Position`/`AttributeValue` moved to `src/shared/types.ts`. Both `src/core/` and `src/live/` re-export from shared to preserve existing import paths. +- `broadcast.ts` ACKs all stream entries immediately (durability not needed for fan-out). +- Test uses a `stopSignal` Promise to coordinate between the broadcast loop and the test's `stop()` call, avoiding the tight-loop OOM that naive polling triggers. diff --git a/.planning/phase-1-5-live-broadcast/05-snapshot-on-subscribe.md b/.planning/phase-1-5-live-broadcast/05-snapshot-on-subscribe.md index 2bcd660..35c88d2 100644 --- a/.planning/phase-1-5-live-broadcast/05-snapshot-on-subscribe.md +++ b/.planning/phase-1-5-live-broadcast/05-snapshot-on-subscribe.md @@ -1,7 +1,7 @@ # Task 1.5.5 — Snapshot-on-subscribe **Phase:** 1.5 — Live broadcast -**Status:** ⬜ Not started +**Status:** 🟩 Done **Depends on:** 1.5.3, 1.4 (Postgres pool) **Wiki refs:** `docs/wiki/synthesis/processor-ws-contract.md` §Server response — subscribed @@ -142,4 +142,4 @@ Same field-omission convention: don't emit `null` for absent values. ## Done -(Filled in when the task lands.) +Landed in `f4b50ca`. `src/live/snapshot.ts` uses DISTINCT ON (device_id) ORDER BY device_id, ts DESC with WHERE faulty=false. Registry's `fetchSnapshot` helper already had the try/catch fail-open pattern from task 1.5.3. `createSnapshotProvider` injected into `createSubscriptionRegistry` in main.ts. diff --git a/.planning/phase-1-5-live-broadcast/06-integration-test.md b/.planning/phase-1-5-live-broadcast/06-integration-test.md index 6a50afc..4247542 100644 --- a/.planning/phase-1-5-live-broadcast/06-integration-test.md +++ b/.planning/phase-1-5-live-broadcast/06-integration-test.md @@ -1,7 +1,7 @@ # Task 1.5.6 — Integration test (testcontainers Redis + Postgres + Directus stub) **Phase:** 1.5 — Live broadcast -**Status:** ⬜ Not started +**Status:** 🟩 Done **Depends on:** 1.5.4, 1.5.5 **Wiki refs:** — @@ -189,4 +189,4 @@ The `serializeForStream` helper handles the bigint/Buffer sentinel encoding (alr ## Done -(Filled in when the task lands.) +Landed in `2f2cf5c`. Key divergence from spec: `test/fixtures/test-schema.sql` uses `entry_devices.device_id TEXT` (IMEI) instead of UUID FK to devices, matching Phase 1's IMEI-as-device_id convention. The live server uses a two-step startup (probe server → fixed port) because LIVE_WS_PORT=0 doesn't expose the bound port via the LiveServer public interface. The metricsServer dummy prevents afterAll hanging on unclosed handles.