-- Admin audit log: append-only record of admin-side actions (who/what/when/ -- where/outcome). Envelope + key-data enrichment (resource_name, details); no -- request bodies or field diffs, so there is no secret-bearing data here. -- Retention is driven from the config file (audit.retention-days), not -- app_settings. CREATE TABLE IF NOT EXISTS audit_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, created_at INTEGER NOT NULL, actor_type TEXT NOT NULL DEFAULT '', -- 'user' | 'principal' | '' actor_id TEXT NOT NULL DEFAULT '', -- public id snapshot actor_name TEXT NOT NULL DEFAULT '', -- username / principal name snapshot actor_is_admin INTEGER NOT NULL DEFAULT 0, auth_method TEXT NOT NULL DEFAULT '', -- 'session' | 'principal' category TEXT NOT NULL DEFAULT 'admin', -- 'admin' | 'self_service' (v2) method TEXT NOT NULL DEFAULT '', path TEXT NOT NULL DEFAULT '', -- concrete request path route_pattern TEXT NOT NULL DEFAULT '', -- matched template, e.g. /api/admin/pki/certs/:id/revoke resource_type TEXT NOT NULL DEFAULT '', -- derived from the pattern, e.g. pki/certs resource_id TEXT NOT NULL DEFAULT '', -- :id path param, if any resource_name TEXT NOT NULL DEFAULT '', -- human name of the target (handler-annotated) details TEXT NOT NULL DEFAULT '', -- small REDACTED JSON object; never secret values status INTEGER NOT NULL DEFAULT 0, -- HTTP status outcome TEXT NOT NULL DEFAULT '', -- 'success' | 'denied' | 'error' remote_ip TEXT NOT NULL DEFAULT '', user_agent TEXT NOT NULL DEFAULT '', session_id TEXT NOT NULL DEFAULT '' ); CREATE INDEX IF NOT EXISTS idx_audit_log_created ON audit_log(created_at); CREATE INDEX IF NOT EXISTS idx_audit_log_actor ON audit_log(actor_id, created_at); CREATE INDEX IF NOT EXISTS idx_audit_log_resource ON audit_log(resource_type, resource_id);