From 6458180bf51dcd3a6f0ce998fa3392d6d491731e Mon Sep 17 00:00:00 2001 From: Mattsson <111893710+mattssonn@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:45:11 +0200 Subject: [PATCH] fix(db): reconcile orphaned mail-hunt migrations applied to prod (#1447) * fix(db): reconcile orphaned mail-hunt migrations applied to prod Prod carries 20260807090000_mail_connections and 20260807090100_inbox_mail_hunt_source (applied 2026-08-07 morning) with no files in supabase/migrations/, so the Supabase integration fails every push to main with "Remote migration versions not found in local migrations directory". This commits the byte-identical SQL recovered from prod's supabase_migrations.schema_migrations statements under the exact applied versions. No new DDL runs on prod: the versions already exist there, this only reconciles the repo. Also classifies mail_connections in full-archive-export.ts (excluded: live OAuth refresh tokens, not portable), which the archive-completeness test requires for any new company_id table. Whoever owns the mail-hunt feature branch: these files now exist on main; reuse them as-is instead of re-creating the versions. Co-Authored-By: Claude Fable 5 * fix(db): annotate reconciled mail_connections migration as pg-test skip The file is a byte-recovered reconciliation of DDL already applied to prod; it never executes again, so pg-real coverage belongs to the mail-hunt feature branch that owns the schema. Co-Authored-By: Claude Fable 5 --------- Co-authored-by: Claude Fable 5 --- lib/reports/full-archive-export.ts | 1 + .../20260807090000_mail_connections.sql | 46 +++++++++++++++++++ .../20260807090100_inbox_mail_hunt_source.sql | 28 +++++++++++ 3 files changed, 75 insertions(+) create mode 100644 supabase/migrations/20260807090000_mail_connections.sql create mode 100644 supabase/migrations/20260807090100_inbox_mail_hunt_source.sql diff --git a/lib/reports/full-archive-export.ts b/lib/reports/full-archive-export.ts index 1e6d73f0..046931e7 100644 --- a/lib/reports/full-archive-export.ts +++ b/lib/reports/full-archive-export.ts @@ -994,6 +994,7 @@ export const ARCHIVE_EXCLUDED_TABLES: Record = { graph_transaction_counterparties: 'derived AI context graph, regenerable', idempotency_keys: 'infrastructure', inbox_rate_counters: 'infrastructure', + mail_connections: 'mailbox OAuth grants (live refresh tokens), not portable', mcp_tasks: 'MCP task handles: transient tool-call state with a 1-hour TTL', metered_events: 'billing telemetry', notification_log: 'notification dedup log', diff --git a/supabase/migrations/20260807090000_mail_connections.sql b/supabase/migrations/20260807090000_mail_connections.sql new file mode 100644 index 00000000..b4af5ced --- /dev/null +++ b/supabase/migrations/20260807090000_mail_connections.sql @@ -0,0 +1,46 @@ +-- pg-test: skip (reconciliation only: this DDL was already applied to prod +-- out-of-band on 2026-08-07 and recovered byte-identical from +-- supabase_migrations.schema_migrations; the file exists so Supabase +-- branching stops failing on the orphaned version and will never re-run. +-- The mail-hunt feature branch that owns this schema must ship the real +-- pg-real coverage for the trigger and RLS.) +CREATE TABLE IF NOT EXISTS public.mail_connections ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + company_id uuid NOT NULL REFERENCES public.companies(id) ON DELETE CASCADE, + provider text NOT NULL CHECK (provider IN ('gmail', 'microsoft')), + email_address text NOT NULL, + connected_by uuid REFERENCES auth.users(id) ON DELETE SET NULL, + encrypted_refresh_token text NOT NULL, + encrypted_access_token text, + access_token_expires_at timestamptz, + scopes text[] NOT NULL DEFAULT '{}', + scope_label text, + backfill_from date, + backfill_completed_at timestamptz, + status text NOT NULL DEFAULT 'active' + CHECK (status IN ('active', 'needs_reconsent', 'revoked')), + last_error_code text, + last_error_at timestamptz, + last_searched_at timestamptz, + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now() +); + +CREATE UNIQUE INDEX IF NOT EXISTS idx_mail_connections_identity + ON public.mail_connections (company_id, provider, email_address); + +CREATE INDEX IF NOT EXISTS idx_mail_connections_company_active + ON public.mail_connections (company_id) + WHERE status = 'active'; + +ALTER TABLE public.mail_connections ENABLE ROW LEVEL SECURITY; + +DROP TRIGGER IF EXISTS mail_connections_updated_at ON public.mail_connections; +CREATE TRIGGER mail_connections_updated_at + BEFORE UPDATE ON public.mail_connections + FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column(); + +COMMENT ON TABLE public.mail_connections IS + 'Read-only mailbox grants for receipt hunting. Service-role only: rows carry live refresh tokens.'; + +NOTIFY pgrst, 'reload schema'; diff --git a/supabase/migrations/20260807090100_inbox_mail_hunt_source.sql b/supabase/migrations/20260807090100_inbox_mail_hunt_source.sql new file mode 100644 index 00000000..aa958c43 --- /dev/null +++ b/supabase/migrations/20260807090100_inbox_mail_hunt_source.sql @@ -0,0 +1,28 @@ +ALTER TABLE public.invoice_inbox_items + DROP CONSTRAINT IF EXISTS invoice_inbox_items_source_check; + +ALTER TABLE public.invoice_inbox_items + ADD CONSTRAINT invoice_inbox_items_source_check + CHECK (source IN ('email', 'upload', 'whatsapp', 'mail_hunt')) NOT VALID; + +ALTER TABLE public.invoice_inbox_items + VALIDATE CONSTRAINT invoice_inbox_items_source_check; + +ALTER TABLE public.document_attachments + DROP CONSTRAINT IF EXISTS document_attachments_upload_source_check; + +ALTER TABLE public.document_attachments + ADD CONSTRAINT document_attachments_upload_source_check + CHECK (upload_source IN ( + 'camera', 'file_upload', 'email', 'e_invoice', 'scan', 'api', 'system', + 'whatsapp', 'mail_hunt' + )) NOT VALID; + +ALTER TABLE public.document_attachments + VALIDATE CONSTRAINT document_attachments_upload_source_check; + +CREATE UNIQUE INDEX IF NOT EXISTS idx_invoice_inbox_mail_message_unique + ON public.invoice_inbox_items (company_id, ((channel_context->>'mail_message_id'))) + WHERE source = 'mail_hunt'; + +NOTIFY pgrst, 'reload schema';