ui/phase-24: push events, turn-ready toast, single SubscribeEvents consumer
Wires the gateway's signed SubscribeEvents stream end-to-end:
- backend: emit game.turn.ready from lobby.OnRuntimeSnapshot on every
current_turn advance, addressed to every active membership, push-only
channel, idempotency key turn-ready:<game_id>:<turn>;
- ui: single EventStream singleton replaces revocation-watcher.ts and
carries both per-event dispatch and revocation detection; toast
primitive (store + host) lives in lib/; GameStateStore gains
pendingTurn/markPendingTurn/advanceToPending and a persisted
lastViewedTurn so a return after multiple turns surfaces the same
"view now" affordance as a live push event;
- mandatory event-signature verification through ui/core
(verifyPayloadHash + verifyEvent), full-jitter exponential backoff
1s -> 30s on transient failure, signOut("revoked") on
Unauthenticated or clean end-of-stream;
- catalog and migration accept the new kind; tests cover producer
(testcontainers + capturing publisher), consumer (Vitest event
stream, toast, game-state extensions), and a Playwright e2e
delivering a signed frame to the live UI.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+13
-7
@@ -672,13 +672,19 @@ runtime status, per-player stats). The engine's "game finished"
|
||||
report drives the `running → finished` transition ([Section 3.5](#35-cancellation-and-finish))
|
||||
and triggers Race Name Directory promotions ([Section 5](#5-race-name-directory)).
|
||||
|
||||
The `game.*` notification kinds (`game.started`, `game.turn.ready`,
|
||||
`game.generation.failed`, `game.finished`) are reserved in the
|
||||
documentation but have **no producer** in the codebase today; the
|
||||
notification catalog explicitly omits them (`backend/internal/notification/catalog.go`).
|
||||
Adding a producer is purely additive: register the kind in the
|
||||
catalog, populate `MailTemplateID` if email fan-out is desired, and
|
||||
have the appropriate domain module call `notification.Submit`.
|
||||
Among the `game.*` notification kinds, `game.turn.ready` is wired:
|
||||
`lobby.Service.OnRuntimeSnapshot` (`backend/internal/lobby/runtime_hooks.go`)
|
||||
emits one intent per advancing `current_turn`, addressed to every
|
||||
active membership of the game, with idempotency key
|
||||
`turn-ready:<game_id>:<turn>` and JSON payload `{game_id, turn}`. The
|
||||
catalog routes the intent through the push channel only; email is
|
||||
deliberately omitted to avoid per-turn spam.
|
||||
|
||||
The remaining `game.*` kinds (`game.started`, `game.generation.failed`,
|
||||
`game.finished`) and `mail.dead_lettered` are reserved without a
|
||||
producer; adding one is purely additive (register the kind in the
|
||||
catalog, extend the migration `CHECK` constraint, and call
|
||||
`notification.Submit` from the appropriate domain module).
|
||||
|
||||
### 6.6 Cross-references
|
||||
|
||||
|
||||
+13
-8
@@ -690,14 +690,19 @@ status, per-player-stats). Engine-отчёт "game finished" гонит
|
||||
([Раздел 3.5](#35-отмена-и-завершение)) и триггерит Race Name
|
||||
Directory-промоушен ([Раздел 5](#5-реестр-названий-рас)).
|
||||
|
||||
`game.*`-виды уведомлений (`game.started`, `game.turn.ready`,
|
||||
`game.generation.failed`, `game.finished`) зарезервированы в
|
||||
документации, но **не имеют поставщика** в кодовой базе сегодня;
|
||||
notification-каталог явно их опускает
|
||||
(`backend/internal/notification/catalog.go`). Добавление поставщика
|
||||
аддитивно: зарегистрировать вид в каталоге, заполнить
|
||||
`MailTemplateID`, если нужен email-веер, и заставить нужный
|
||||
доменный модуль вызвать `notification.Submit`.
|
||||
Из `game.*`-видов уведомлений подключён `game.turn.ready`:
|
||||
`lobby.Service.OnRuntimeSnapshot` (`backend/internal/lobby/runtime_hooks.go`)
|
||||
выпускает один intent на каждое увеличение `current_turn`, адресуя
|
||||
его всем активным membership-ам игры, с idempotency-ключом
|
||||
`turn-ready:<game_id>:<turn>` и JSON-payload-ом `{game_id, turn}`.
|
||||
Каталог направляет intent только в push-канал; email-фан-аут
|
||||
сознательно опущен, чтобы избежать спама на каждом ходе.
|
||||
|
||||
Остальные `game.*`-виды (`game.started`, `game.generation.failed`,
|
||||
`game.finished`) и `mail.dead_lettered` зарезервированы без поставщика;
|
||||
добавление поставщика чисто аддитивное (зарегистрировать вид в
|
||||
каталоге, расширить `CHECK`-констрейнт миграции и вызвать
|
||||
`notification.Submit` из подходящего доменного модуля).
|
||||
|
||||
### 6.6 Перекрёстные ссылки
|
||||
|
||||
|
||||
Reference in New Issue
Block a user