-- Admin audit log. Written from inside the same transaction as the action -- it records, so a failed COMMIT also rolls back the audit row — the log -- never claims an action happened that didn't. -- -- `actor_user_id` is ON DELETE SET NULL so audit rows outlive a deleted -- admin (the answer to "who promoted Bob to admin?" survives even after -- Alice's account is removed). `target_id` is intentionally not a FK -- because future audit kinds may target non-user rows (manga, source, -- etc.) and a single typed FK can't express that. CREATE TABLE admin_audit ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), actor_user_id uuid REFERENCES users(id) ON DELETE SET NULL, action text NOT NULL, target_kind text NOT NULL, target_id uuid, payload jsonb NOT NULL DEFAULT '{}'::jsonb, at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX admin_audit_at_idx ON admin_audit (at DESC);