55 lines
3.6 KiB
Markdown
55 lines
3.6 KiB
Markdown
---
|
|
brief: 2
|
|
convoy: workspace-chat-foundation
|
|
role: role-implementer
|
|
title: Transactional outbox and realtime chat delivery
|
|
status: blocked
|
|
priority: P1
|
|
slice_dependencies:
|
|
- brief-1-chat-persistence
|
|
recommended_model: auto/coding
|
|
model_tier: 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.ts` and 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. `markChannelRead` invalidations 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 build` pass.
|
|
|
|
## 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.
|