Files
Alarm/database/migrations/008_log_events.sql
T
Admin 3ccea3e9f6 [feat](alarm): keep one anonymised lifecycle event log
Record created/joined/triggered/delivered/destroyed with SHA-256 hashes of the alert and user UUIDs only, and a reason on destruction. The earlier analytics and stdout-mirror tables were superseded by the user ruling, so 009 creates alarm.event_log, migrates what still applies and drops both, leaving exactly one durable table with no network identifiers, token, body or query string. Retention is age-based via the sweeper.
2026-10-19 02:26:46 +01:00

57 lines
2.5 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
\c dcntrlized;
-- 008_log_events.sql — durable, anonymised service log.
--
-- User request (2026-10-18): the service should keep its operational log in the
-- database, not only on stdout, and it must not keep raw IPs or user agents.
-- Explicit columns end in `_hash` so a future reader cannot insert a raw value
-- by accident, and there is deliberately NO column for a token, request body or
-- query string: those must never be persisted (in Alarm they carry admin and
-- subscribe tokens).
--
-- Identifier hashing is NOT the analytics scheme:
-- * alert_hash / user_hash: SHA-256 hex of the UUID (analytics scheme; UUIDs
-- carry ~122 random bits, so a plain digest is one-way).
-- * ip_hash / ua_hash: Argon2id with a service-generated public salt and a
-- tuned work factor (internal/loghash). IPs are a 2^32 space, so a plain
-- digest is a lookup table away from being reversed; only the KDF's cost
-- protects them. The two schemes are intentionally different — do not
-- "unify" them, and do not lower the Argon2id parameters to make the logger
-- faster: that would silently un-anonymise every historical row.
--
-- All columns are nullable except id/occurred_at/level/kind/message: rows are
-- anonymised by construction, so this table can be exported for support without
-- leaking who or where. Retention is enforced by the sweeper (age + row cap),
-- see API.md; the table must never grow without bound.
--
-- Idempotent (same guard pattern as 002–007).
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = 'alarm') THEN
RETURN;
END IF;
CREATE TABLE IF NOT EXISTS alarm.log_events (
id BIGSERIAL PRIMARY KEY,
occurred_at TIMESTAMPTZ NOT NULL DEFAULT now(),
level TEXT NOT NULL,
kind TEXT NOT NULL,
message TEXT NOT NULL,
ip_hash TEXT,
ua_hash TEXT,
alert_hash TEXT,
user_hash TEXT,
CONSTRAINT log_events_level_check CHECK (level IN ('debug', 'info', 'warn', 'error'))
);
CREATE INDEX IF NOT EXISTS log_events_occurred_idx ON alarm.log_events (occurred_at);
CREATE INDEX IF NOT EXISTS log_events_kind_idx ON alarm.log_events (kind, occurred_at);
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'alarm_user') THEN
GRANT SELECT, INSERT, DELETE ON alarm.log_events TO alarm_user;
GRANT USAGE, SELECT ON SEQUENCE alarm.log_events_id_seq TO alarm_user;
END IF;
END
$$;