feat(copilot): add copilot_chat_messages table with dual-write rollout#4726
feat(copilot): add copilot_chat_messages table with dual-write rollout#4726waleedlatif1 wants to merge 9 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
…mestamps Production data review (580,300 messages across all history) confirms every message in copilot_chats.messages has id, role, and timestamp set — making the previous fallback handling unreachable. Also corrects the migration's timestamp source: the field is `timestamp` (ISO 8601), not `createdAt`, which was always null and silently triggered the synthesized ordinal-offset fallback. - messages-dual-write: typed as PersistedMessage[], no defensive guards - migration backfill: pulls real `timestamp` instead of always falling back to chat.created_at + ordinal microseconds - catch-up sweep: same simplification
…rom update-messages
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview Wires best-effort dual-write into existing chat message mutation paths: snapshot saves now Adds a non-blocking, on-boot 7-day catch-up sweep to close rolling-deploy gaps, plus updates/tests to cover the new dual-write helpers and adjusted DB Reviewed by Cursor Bugbot for commit b7d172b. Configure here. |
Greptile SummaryThis PR introduces a dedicated
Confidence Score: 5/5Safe to merge — the dual-write is fully best-effort, the JSONB column remains the source of truth, and the migration backfill is idempotent. All callsites correctly gate on the returned row before writing to the normalized table, preventing FK-violation noise. The catch-up sweep is fire-and-forget and bounded to 7 days. The transaction patterns in terminal-state.ts and replaceCopilotChatMessages are sound. No breaking schema changes, and all previously flagged gaps have been addressed. No files require special attention. Important Files Changed
Reviews (3): Last reviewed commit: "fix(copilot): thread chatModel through p..." | Re-trigger Greptile |
…ions, pass chat model from inbox
|
@greptile |
|
@cursor review |
Cover row shape, ordering, options propagation, ON CONFLICT semantics, and error swallowing for appendCopilotChatMessages / replaceCopilotChatMessages. Also adds copilotChatMessages to the central schemaMock so the imports resolve. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The user-message append path (post.ts) and assistant-finalize path (terminal-state.ts) were calling appendCopilotChatMessages without chatModel, leaving the model column NULL on every interactive turn. post.ts now projects model from .returning(...); terminal-state.ts adds model to the in-tx SELECT and surfaces it through a closure. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
@greptile |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit b7d172b. Configure here.
…cript The boot-time messages catch-up ran on every replica × every deploy, burning ~210K row-touches/day on prod to solve a problem (dual-write transient drift) that doesn't affect users while JSONB is canonical. Replace it with a one-shot bun script that can be run manually before the eventual R+1 read cutover, or after a known outage. Default scope is the full table; --since='<interval>' narrows the window. bun apps/sim/scripts/copilot-messages-reconcile.ts bun apps/sim/scripts/copilot-messages-reconcile.ts --since='7 days' Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.0212_cynical_jack_murdock.sqlruns an inlinejsonb_array_elements ... ON CONFLICT DO NOTHINGbackfill.bun run db:migratedoes everything; no scripts required.0091_backfill_user_stats.sql,0192_invitation_unification.sql): multi-statement withgen_random_uuid(), FK with CASCADE, inlineINSERT ... SELECT ... ON CONFLICT DO NOTHING.lib/copilot/chat/messages-dual-write.ts) wired into every JSONB-mutation callsite (post, terminal-state, inbox, fork, update-messages, superuser-import). Best-effort — errors logged, never thrown.Indexes
UNIQUE (chat_id, message_id)— idempotent appends, dedup streaming retries(chat_id, created_at, id) WHERE deleted_at IS NULL— transcript load, last-N(chat_id, stream_id) WHERE stream_id IS NOT NULL— stream replay/resumeType of Change
Testing
Tested manually. Lint passes.
Checklist