-- Migration 016: sync_conflicts (Fase 9.3) -- -- Append-only log of last-write-wins conflicts surfaced by the offline -- mutation queue (apps/web/src/lib/sync/queue.ts). When the client detects -- that a local UPDATE's pre-image disagrees with the remote row it just -- patched (i.e. someone else mutated the same row in between), it appends -- a row here with both payloads so users can audit and operators can -- debug. The resolution column records which side ended up persisted — -- always 'remote_won' under MVP's last-write-wins; the column is kept for -- forward compatibility when a smarter merger lands. -- -- RLS rules: -- * SELECT: only the user who owns the row (auth.uid() = user_id) -- * INSERT: only the user inserting their own row -- * UPDATE / DELETE: blocked entirely — the log is append-only. -- -- `collective_id` is intentionally nullable: most conflicts will be scoped -- to a collective, but the column also covers future user-level entities -- (e.g. theme, language) that have no collective context. We can tighten -- to NOT NULL later if we never use it that way. CREATE TABLE public.sync_conflicts ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, collective_id uuid NULL REFERENCES public.collectives(id) ON DELETE CASCADE, entity_type text NOT NULL, entity_id uuid NOT NULL, local_version jsonb NOT NULL, remote_version jsonb NOT NULL, resolution text NOT NULL CHECK (resolution IN ('remote_won', 'local_won')), created_at timestamptz NOT NULL DEFAULT now() ); COMMENT ON TABLE public.sync_conflicts IS 'Append-only audit log of offline-sync conflicts (last-write-wins). Inserted by the client when a queued UPDATE landed on a row that had been mutated remotely in the meantime.'; -- Most queries are "give me the latest N conflicts for the current user". CREATE INDEX sync_conflicts_user_created_idx ON public.sync_conflicts (user_id, created_at DESC); ALTER TABLE public.sync_conflicts ENABLE ROW LEVEL SECURITY; -- A user can read only their own conflicts. CREATE POLICY sync_conflicts_select_own ON public.sync_conflicts FOR SELECT USING (auth.uid() = user_id); -- A user can insert only rows attributed to themselves. CREATE POLICY sync_conflicts_insert_own ON public.sync_conflicts FOR INSERT WITH CHECK (auth.uid() = user_id); -- No UPDATE / DELETE policies — append-only by design. The absence of -- ALL / UPDATE / DELETE policies + RLS being enabled means any non-owner -- INSERT also gets rejected, and UPDATE / DELETE are uniformly denied.