\c dcntrlized; -- 006_alert_expiry.sql — a hard persistence cap chosen by the owner. -- -- Contract decision (2026-10-18, user ruling): the creator sets a maximum -- persistence time, at most 14 days, after which devices that never came back -- are ignored and the alert "along with everything about it" is destroyed. This -- is what stops a phone that never returns from pinning an alert (and its -- delivery debt) forever once auto-stop is gone. -- -- The application computes the absolute timestamp from the requested duration, -- rejects anything above the cap (API.md: ERR_VALIDATION, not a silent clamp) and -- inserts it explicitly. The DEFAULT is a safety net only: 24 hours, the -- documented default duration, so a row inserted by an older binary or by hand -- still expires instead of living forever. -- -- Existing rows are backfilled to `created + 14 days` (the maximum): they were -- created under a contract with no cap, so they get the most generous value the -- new contract allows, not an instant expiry. -- -- Read paths also filter `expires_at > now()` (db.GetAlert, transition, …) so an -- alert is invisible the moment it expires, even before the sweeper reaches it. -- -- Idempotent (same guard pattern as 002–005): 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; ALTER TABLE alarm.alerts ADD COLUMN IF NOT EXISTS expires_at TIMESTAMPTZ; UPDATE alarm.alerts SET expires_at = created + interval '14 days' WHERE expires_at IS NULL; ALTER TABLE alarm.alerts ALTER COLUMN expires_at SET NOT NULL; ALTER TABLE alarm.alerts ALTER COLUMN expires_at SET DEFAULT (now() + interval '24 hours'); CREATE INDEX IF NOT EXISTS alerts_expires_at_idx ON alarm.alerts (expires_at); END $$;