\c dcntrlized; -- 005_alarm_deliveries.sql — a durable per-device delivery ledger. -- -- Why this exists: pg_notify and the WebSocket fan-out are not durable. A phone -- that is offline at trigger time misses the frame forever, and the in-memory -- catch-up cache only lives in the process that produced the event. This table is -- the durable record of who still owes an alarm, so "no auto-stop" is safe: the -- alarm stays live until the owner disarms it, and every subscribed device has a -- row here until it has been told. -- -- Shape: one row per (alert, subscription, event), written in the SAME -- transaction as the alarm_events insert and therefore before the NOTIFY on -- commit. If this insert fails the trigger fails — a fan-out that records no debt -- would be exactly the silent failure this design prevents. -- -- Cascades are in the schema, not in application code: deleting an alert or a -- subscription removes its ledger rows. event_id deliberately has no foreign key: -- the ledger is the record of what a device owes, and it must outlive nothing but -- its alert/subscription (both of which cascade). -- -- Idempotent (same guard pattern as 002/003/004): a re-apply of the whole -- sequence is a clean no-op. DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = 'alarm') THEN RETURN; END IF; CREATE TABLE IF NOT EXISTS alarm.alarm_deliveries ( alert_id UUID NOT NULL REFERENCES alarm.alerts(id) ON DELETE CASCADE, subscription_id UUID NOT NULL REFERENCES alarm.subscriptions(id) ON DELETE CASCADE, event_id UUID NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), acked_at TIMESTAMPTZ, PRIMARY KEY (alert_id, subscription_id, event_id) ); -- The check-in reads by alert + subscription; the pending index keeps -- "who still owes this alert" a small partial scan instead of a table scan. CREATE INDEX IF NOT EXISTS alarm_deliveries_alert_idx ON alarm.alarm_deliveries (alert_id); CREATE INDEX IF NOT EXISTS alarm_deliveries_pending_idx ON alarm.alarm_deliveries (alert_id, subscription_id) WHERE acked_at IS NULL; IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'alarm_user') THEN GRANT SELECT, INSERT, UPDATE, DELETE ON alarm.alarm_deliveries TO alarm_user; END IF; END $$;