\c dcntrlized; -- 007_alarm_analytics.sql — anonymized, append-only lifecycle counters. -- -- User request: "anonymized 'Alarm X created by Y', 'Alarm X subscribed by Y', -- 'Alarm X delivered/triggered to/by Y', along with timestamps". Nothing raw, no -- foreign keys, and it must survive the alert it describes — hence no REFERENCES -- and no cascade. A destroyed alert still has its history here. -- -- Identity columns are SHA-256 hex of the UUID *string* form. That is genuinely -- one-way (a UUID carries ~122 random bits, so there is no address space to -- enumerate), which is why this file uses a plain digest while the request-log -- IP/UA hashing uses HMAC with a secret key: an IPv4 address is only 2^32 and a -- plain digest would be reversible by lookup. Do not "unify" the two schemes. -- -- `reason` is set only for `destroyed` rows: `delivered` (the ledger drained) or -- `expired` (the persistence cap fired). -- -- Idempotent (same guard pattern as 002–006). DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = 'alarm') THEN RETURN; END IF; CREATE TABLE IF NOT EXISTS alarm.alarm_analytics ( id BIGSERIAL PRIMARY KEY, alert_hash TEXT NOT NULL, user_hash TEXT, action TEXT NOT NULL, reason TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), CONSTRAINT alarm_analytics_action_check CHECK (action IN ('created', 'subscribed', 'triggered', 'delivered', 'destroyed')) ); CREATE INDEX IF NOT EXISTS alarm_analytics_alert_idx ON alarm.alarm_analytics (alert_hash, created_at); CREATE INDEX IF NOT EXISTS alarm_analytics_created_idx ON alarm.alarm_analytics (created_at); IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'alarm_user') THEN GRANT SELECT, INSERT ON alarm.alarm_analytics TO alarm_user; GRANT USAGE, SELECT ON SEQUENCE alarm.alarm_analytics_id_seq TO alarm_user; END IF; END $$;