--- kind: task slug: chat-persistence title: Persist workspace channels, messages, and read state plan_slug: workspace-communications epic_slug: workspace-chat status: in_progress priority: P1 tenant_id: global owner: unassigned cursor_todo_id: null updated_at: "2026-10-26" --- # Task summary Add the tenant-scoped relational foundation and tRPC API for durable workspace chat. ## Description Model channels, memberships, messages, threads, member read cursors, and the chat outbox contract in Postgres. Use `workspaceProcedure` and explicit `workspace_id` filters for every operation. This foundation accepts workspace context only and rejects object/unknown contexts without fallback. Authorization is workspace AND context AND channel permission: public workspace channels require active workspace access; private/direct require membership too; archived channels are read-only. Workspace admins/owners have no private/direct read or self-join bypass. Membership targets must be authorized, and all discovery/history/mention/count endpoints share the policy seam. Project/private-object access is deferred to `Task-contextual-chat-authorization.md`, not inferred from tenant ownership. Message writes must atomically include an outbox event, support idempotency, and allocate per-channel monotonic sequence cursors. Generate a new Drizzle migration; do not alter existing migrations. ## Edit/tombstone continuation — 2026-10-26 The user reports the migration blocker resolved and migrations applied; this session has not independently verified that database. Implement revision-checked edits and tombstones, immutable creation retry intent, authorization-checked mutation receipts, safe public projections, and a separately bounded durable mutation recovery cursor. Tombstones must not expose old body/mentions through history, receipts, or realtime replay. Acceptance remains open until integrated tests and live PostgreSQL gates pass. ## Live PostgreSQL gates - [ ] Concurrent identical direct-channel creation produces one channel, one exact membership set, and one lifecycle event set. - [ ] Apply migrations and verify private staging transitions only with an exact canonical participant set; reject malformed transitions and direct membership/identity mutations. - [ ] Concurrent identical direct creation produces one channel/event set; mutation receipt replay returns the original result, collisions reject, and failed mutations roll back receipt/events. ## Subtasks - [ ] Add chat channel, membership, message, read-state, and outbox schemas with workspace indexes and foreign keys. - [ ] Define public/private/direct and archived authorization, channel ownership/admin rules, immutable exact-participant direct channels, per-channel sequence cursors, one-level thread semantics, author unions, message statuses, persisted message mentions, and client idempotency keys. - [ ] Add zod inputs and a workspace-scoped chat router for channel create/list/rename/archive, private membership changes, idempotent direct-channel creation, root-only channel history, one-level thread history, send/reply/edit/tombstone, mention counts, and mark-read. - [ ] Atomically write versioned outbox events for message, reply, tombstone, lifecycle, membership, and read-state changes. - [ ] Add authorization, provisioning, idempotency, thread, mark-read, and cross-workspace isolation tests. - [ ] Against disposable PostgreSQL with migrations through `0019`, verify advisory-lock blocking for simultaneous same-key sends; exactly one message, mention set, logical outbox event, and sequence increment; rollback after an injected post-allocation failure; and Drizzle `bigint` runtime shape from the atomic sequence `RETURNING` expression. Unit transaction fakes do not satisfy this gate. ## Owner or assignee `unassigned` ## Status `ready` ## Estimation `L` ## Acceptance criteria - [ ] Unsupported/same-workspace object contexts fail closed without a public fallback; private/direct admin bypass and unauthorized title/preview/mention/count leaks are denied. - [ ] Foundation human mention parsing never installs, adds or invokes an agent; reviewed agent membership is a separate task. - [ ] Every tenant-owned table includes indexed, non-null `workspace_id`. - [ ] Public, private, direct, archived, channel-owner, workspace-admin/owner, author-edit, and moderation operations enforce their documented rules without cross-tenant disclosure. - [ ] Direct-channel membership is immutable and exact sorted participant sets resolve idempotently. - [ ] Channel history excludes replies; replies are one level and their root is constrained to the same workspace/channel. - [ ] Unread mention counts are derived from persisted workspace-scoped mention rows above the recipient's channel read cursor. - [ ] Duplicate message/reply retries with the same scoped idempotency key produce one durable row and one logical outbox event, demonstrated under real concurrent PostgreSQL transactions rather than only unit fakes. - [ ] Channel and thread history support stable per-channel sequence pagination and reconnect catch-up. - [ ] `markChannelRead` only advances the highest intentionally viewed sequence and emits an invalidation event for other devices. - [ ] `pnpm lint && pnpm type-check && pnpm test` pass. ## Links to related Epic / Plan - Epic: `./Epic-workspace-chat.md` - Plan: `../Plan-workspace-communications.md`