feat(copilot): add copilot_chat_messages table with dual-write rollout#4724
feat(copilot): add copilot_chat_messages table with dual-write rollout#4724waleedlatif1 wants to merge 2 commits into
Conversation
Introduces a dedicated `copilot_chat_messages` table to replace the JSONB messages array on `copilot_chats`. The JSONB column is preserved as the source of truth during rollout, with dual-writes capturing every new message in both places. Schema (additive only): - New `copilot_chat_messages` table with FK to `copilot_chats` (ON DELETE CASCADE), unique (chat_id, message_id), partial indexes for hot reads - Migration runs an inline `INSERT ... SELECT jsonb_array_elements ...` backfill, idempotent via ON CONFLICT — self-hosters need no scripts Dual-write helper: - `lib/copilot/chat/messages-dual-write.ts` — best-effort append + replace helpers, errors logged but not thrown so JSONB write remains canonical Dual-write callsites (every place that mutates copilot_chats.messages): - `lib/copilot/chat/post.ts` — user message append - `lib/copilot/chat/terminal-state.ts` — assistant message append (writes after the FOR UPDATE transaction commits) - `lib/mothership/inbox/executor.ts` — inbox-derived message persistence - `app/api/mothership/chats/[chatId]/fork/route.ts` — forked transcript - `app/api/copilot/chat/update-messages/route.ts` — snapshot replace Catch-up sweep: - `lib/copilot/chat/messages-catchup.ts` — bounded 7-day sweep on server boot to close the rolling-deploy window where old code may write to JSONB before the new dual-write code is live everywhere
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview Adds best-effort dual-write helpers ( Schedules a fire-and-forget, 7-day bounded boot-time catch-up sweep to fill any gaps during rolling deploys where older servers may have written JSONB-only. Reviewed by Cursor Bugbot for commit 069f8bd. Configure here. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 069f8bd. Configure here.
Greptile SummaryThis PR introduces a dedicated
Confidence Score: 3/5Safe to merge for R+0 since reads still use JSONB, but two correctness problems in the new table population logic will need to be fixed before the R+1 read cutover. The catch-up sweep inserts a fresh duplicate row for every message that lacks an id field in the JSONB blob on each server boot — ON CONFLICT DO NOTHING can't deduplicate rows whose message_id differs. Additionally, replaceCopilotChatMessages will silently delete all rows for a chat if validation causes rows to be empty while messages is non-empty. Neither bug is user-visible at R+0 because no reads hit the new table yet, but both would corrupt conversations when the R+1 read cutover lands. messages-dual-write.ts (replaceCopilotChatMessages delete-without-insert risk) and messages-catchup.ts (gen_random_uuid non-idempotency) need the most attention before the R+1 read switch. Important Files Changed
Sequence DiagramsequenceDiagram
participant Client
participant Route as API Route / Executor
participant JSONB as copilot_chats (JSONB)
participant DW as messages-dual-write.ts
participant NewTable as copilot_chat_messages
participant Sweep as messages-catchup.ts
Note over Route,NewTable: R+0 Dual-Write Flow (reads still use JSONB)
Client->>Route: POST /api/copilot/chat
Route->>JSONB: "UPDATE messages = messages || [userMsg]"
JSONB-->>Route: updated row (source of truth)
Route->>DW: appendCopilotChatMessages(chatId, [userMsg])
DW->>NewTable: INSERT ON CONFLICT DO UPDATE (best-effort)
NewTable-->>DW: ok / error (swallowed)
Route->>JSONB: finalizeAssistantTurn (tx + FOR UPDATE)
JSONB-->>Route: committed
Route->>DW: appendCopilotChatMessages(chatId, [assistantMsg])
DW->>NewTable: INSERT ON CONFLICT DO UPDATE (best-effort)
Note over Sweep,NewTable: Server Boot Catch-Up Sweep
Sweep->>JSONB: SELECT chats updated in last 7 days
Sweep->>NewTable: INSERT ON CONFLICT DO NOTHING (idempotent only for messages WITH id)
Reviews (1): Last reviewed commit: "feat(copilot): add copilot_chat_messages..." | Re-trigger Greptile |

Summary
copilot_chat_messagestable to replace the JSONB messages array oncopilot_chats. The JSONB column is preserved indefinitely as the source of truth during rollout — no breaking schema change ever.0212_cynical_jack_murdock.sqlruns an inlinejsonb_array_elements ... WITH ORDINALITYbackfill, idempotent viaON CONFLICT (chat_id, message_id) DO NOTHING. Self-hosters need no scripts —bun run db:migratedoes everything.0091_backfill_user_stats.sql,0192_invitation_unification.sql): multi-statement migration,gen_random_uuid(), FK with CASCADE.lib/copilot/chat/messages-dual-write.ts) wired into every JSONB-mutation callsite. Best-effort — errors logged but not thrown so the legacy JSONB write remains canonical during rollout.Callsites wired
lib/copilot/chat/post.ts— user message appendlib/copilot/chat/terminal-state.ts— assistant message append (after FOR UPDATE tx commits)lib/mothership/inbox/executor.ts— inbox-derived persistenceapp/api/mothership/chats/[chatId]/fork/route.ts— forked transcriptapp/api/copilot/chat/update-messages/route.ts— snapshot replaceIndexes
UNIQUE (chat_id, message_id)— idempotent appends, dedup streaming retries(chat_id, created_at, id) WHERE deleted_at IS NULL— transcript load, last-N, DISTINCT ON(chat_id, stream_id) WHERE stream_id IS NOT NULL— stream replay/resumeRollout plan (this PR = R+0)
Type of Change
Testing
Migration verified to apply cleanly to a copy of production schema. Dual-write helper is best-effort with try/catch so any runtime failure logs but doesn't break the request. Lint passes.
Checklist