Submatter/plans/Plan-workspace-communications/Epic-workspace-chat/Task-chat-persistence.md

73 lines
5.3 KiB
Markdown
Raw Permalink Normal View History

---
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`