3.6 KiB
3.6 KiB
| brief | convoy | role | title | status | priority | slice_dependencies | recommended_model | model_tier | |
|---|---|---|---|---|---|---|---|---|---|
| 2 | workspace-chat-foundation | role-implementer | Transactional outbox and realtime chat delivery | blocked | P1 |
|
auto/coding | standard |
Brief 2 — Task-chat-realtime-delivery.md
Goal
Deliver committed chat events to authenticated subscribers across instances while preserving Postgres replay as the recovery path.
Files
Expected scope; refine after brief 1 lands.
packages/database/src/schema/— consume the outbox and per-channel sequence contracts from brief 1; add a generated migration only if claim/retry state genuinely requires a schema extension.packages/shared/src/— consume or refine the versioned chat event envelopes and(channel_id, sequence)cursor schemas from brief 1 without creating a second contract.apps/collab-server/src/index.tsand focused modules — authenticated WebSocket subscriptions and Redis fan-out.apps/worker/src/or the established background-job boundary — claim/publish/retry outbox records.apps/web/realtime client integration — authenticated connection and catch-up trigger only; visual chat UI belongs to brief 3.- Adjacent tests for authorization, retries, ordering, reconnect, and Redis outage behavior.
Required design
- Commands remain HTTP/tRPC; do not introduce browser gRPC.
- Brief 1 already commits message/read-state rows and outbox events atomically; this brief must preserve that boundary and must not publish before commit.
- Publishers are at-least-once; consumers and clients handle duplicates by outbox event ID and durable entity ID.
- Redis carries cross-instance fan-out, not canonical history.
- Subscriptions and replay use Brief 1's workspace AND context AND channel authorization, never workspace membership alone. Public is relative to the authorized context; private/direct also require membership. Archived channels allow authorized read/catch-up only. Recheck on delivery and permission changes, close revoked subscriptions and stop fan-out; a Redis topic or cached subscription is not authority. Object contexts remain rejected until the context-policy follow-on.
- Each subscribed channel tracks its own
(channel_id, sequence)cursor. Reconnect catch-up queries Postgres independently for each authorized channel; no workspace-global ordering is implied. - Message, thread-reply, tombstone, channel lifecycle, membership, and read-state events use one versioned envelope.
markChannelReadinvalidations update badges on the user's other devices. - Reuse Hocuspocus/Yjs only for ephemeral presence, typing, or drafts.
Acceptance
- A committed message remains queryable and is eventually delivered after Redis recovers.
- Two realtime instances fan the same event to their local authorized subscribers.
- Duplicate outbox publishes do not duplicate rendered durable messages or read-state changes.
- Multi-channel reconnect catches each channel up from its own sequence without assuming cross-channel ordering.
- Public, private, direct, and archived subscription rules match Brief 1 and unauthorized attempts disclose no data.
- A read-state event clears or recalculates unread state on another connected device.
- Heartbeat, bounded buffers/backpressure, graceful shutdown, and reconnect behavior are covered.
pnpm lint && pnpm type-check && pnpm test && pnpm buildpass.
Prompt
Read config/CursorSync.md for collab-server lifecycle, the current Hocuspocus/Redis configuration, and brief 1's resulting schema/API. Keep durable messages out of Yjs. Document any deploy-impacting port or process changes explicitly.