8.1 KiB
| brief | convoy | role | title | status | priority | slice_dependencies | recommended_model | model_tier |
|---|---|---|---|---|---|---|---|---|
| 1 | workspace-chat-foundation | role-implementer | Durable chat persistence and workspace API | in_progress | P1 | auto/coding | standard |
Brief 1 — Task-chat-persistence.md
Execution checkpoint — 2026-10-22
- Shared command/context validation and the fail-closed authorization policy are implemented and accepted after standards/spec review;
@tasks/sharedhas 65 passing tests. - The tenant-scoped chat schema and generated migration are implemented and accepted after standards/spec/migration review; database tests pass. A separate generated
0018repairs pre-existing omitted budget tables, and chat is isolated in generated migration0019. - The concrete Drizzle human send/reply transaction, mention-aware idempotency, atomic sequence allocation, validated
chat.message.created.v1outbox event, and workspace-authorizedchat.sendtRPC procedure are implemented and review-clean. Acceptance remains blocked on the live PostgreSQL integration gate below; channel lifecycle, history, edit/tombstone, mention counts, and read-state operations are pending. - Blocking integration gate: against disposable PostgreSQL with migrations through
0019, verify advisory-lock blocking under simultaneous same-key sends; exactly one message, mention set, logical outbox event, and sequence increment; full rollback of the sequence/message/mentions/outbox after an injected post-allocation failure; and Drizzle's runtimebigintshape forUPDATE … RETURNING next_sequence - 1. Unit fakes verify orchestration only and must not be cited as transaction/concurrency proof. - IA/UX review completed; private-object authorization remains blocked, and agent approval semantics and AI side-panel convergence require resolution before their dependent integrations ship.
Goal
Implement the durable, tenant-scoped channel/message foundation and workspace-authorized tRPC API described by plans/Plan-workspace-communications/Epic-workspace-chat/Task-chat-persistence.md.
Files
Expected scope; confirm exact module names against current package exports before editing.
packages/database/src/schema/— add chat channel, membership, message, read-state, and the outbox contract schemas and export them.packages/database/migrations/— generate a new migration and metadata; never edit an existing migration.packages/shared/src/— add reusable zod schemas/types for chat commands, author/message states, and the versioned outbox event where cross-process sharing is needed.apps/web/server/routers/— add aworkspaceProcedurechat router for channel list/create/archive, membership management, history/send/reply/edit/delete, and mark-read.- Adjacent
*.test.tsfiles — authorization, provisioning, pagination, idempotency, thread, and cross-workspace isolation coverage.
Required design
- Every tenant-owned table has non-null indexed
workspace_id. - Channel types are
public,private, anddirect; visibility is separate from context. This brief accepts only an explicit workspace context (including a documented workspace default for existing callers); reject object references/unknown context keys rather than dropping them. Add a shared authorization seam with workspace AND context AND channel checks; do not implement a project ACL in this brief. - Public workspace channels are readable by active workspace members; private/direct channels additionally require active channel membership. Every list/title/preview/history/thread/mention/count/read-state query uses the same access predicate. Archived channels permit authorized reads only.
- Active members create workspace public/private channels and become channel owner. An authorized channel owner or workspace
admin/ownercan manage that channel only with channel access; role alone is not a private/direct read bypass or self-join mechanism. All membership targets must themselves be authorized. Direct-channel participant sets are immutable; changes resolve a different exact-participant channel. Object/private-project access remains blocked until an authoritative context policy exists. - Message authors may edit their own non-deleted messages. They may tombstone their own messages; channel owners and workspace
admin/ownermay tombstone messages for moderation. System/agent edits require the owning service path, not an impersonated user mutation. - Channel lifecycle includes public/private creation, rename/archive, private membership management, and idempotent direct-channel creation for an exact sorted participant set.
- Message authors use a discriminated user/agent/system representation without fake user rows.
- A client-generated idempotency key is unique within the workspace, channel, and author scope.
- Every message receives a transactionally allocated, per-channel monotonic sequence. History and reconnect cursors are
(channel_id, sequence); message ID breaks ties only if allocation semantics ever permit one. - Sending a message or thread reply inserts its versioned outbox event in the same transaction. Brief 1 owns this boundary; Brief 2 consumes the outbox.
- Threads are one level. Channel history returns root messages only plus reply counts/latest-reply metadata; thread history returns direct replies ordered by the same channel sequence; replies cannot themselves receive replies. Every root reference must resolve inside the same workspace and channel.
- Deletes are tombstones where thread/history integrity requires them.
- Read state stores the highest intentionally viewed message sequence per member/channel.
markChannelReadmay only advance it and inserts a read-state invalidation event for other devices. - Mention parsing creates validated, workspace-scoped persisted message-mention rows in the message transaction. This convoy derives unread mention counts from those rows above the recipient's read cursor; the notifications epic later creates inbox projections from the same source events.
- All reads and mutations authorize through
ctx.workspaceplus the channel-type and role rules above.
Scope checkpoint and acceptance
Next implementable chat brief: this one. Start with failing policy tests and schema contracts, then migration and workspace-only router/outbox. Existing objects includes projects but has no private-object ACL: do not invent project memberships or authorize by workspace_id alone. See context follow-on.
-
Same-workspace unsupported object contexts fail closed; no fallback public channel is created.
-
Nonmember workspace admins cannot read/self-add to private/direct channels via administrative endpoints; counts/previews cannot reveal them.
-
Access removal is respected by subsequent queries/writes; stale membership cannot override context denial.
-
Foundation mention parsing cannot install/add/invoke agents; the agent-channel-membership task owns reviewed addition.
-
Duplicate send retries result in one persisted message.
-
Cross-workspace and unauthorized private-channel requests return non-disclosing errors.
-
Stable cursor pagination works for forward catch-up and backward history.
-
Channel creation, rename/archive, private membership changes, immutable exact-participant direct channels, and message edit/moderation enforce the documented ownership and workspace-role rules.
-
One-level thread roots/replies, persisted mention rows/counts, and durable per-member read state have working API coverage.
-
markChannelReadonly advances the per-channel sequence and atomically emits its invalidation event. -
Message and thread-reply commits atomically include the corresponding outbox event.
-
pnpm db:generate,pnpm lint,pnpm type-check, and relevant tests pass.
Prompt
Read AGENTS.md, docs/Glossary.md, database schema exports, apps/web/server/trpc.ts, and apps/web/server/lib/resolve-workspace.ts before writing. Use TDD for authorization and idempotency. Keep this brief limited to durable persistence/API and atomic outbox writes; do not implement outbox publication, WebSockets, or UI.