Submatter/.convoys/workspace-chat-foundation/brief-2-chat-realtime.md

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
brief-1-chat-persistence
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.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.