-- +migrate Up CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE TYPE finding_status AS ENUM ('open', 'resolved', 'ignored'); CREATE TYPE risk_level AS ENUM ('low', 'medium', 'high', 'critical'); CREATE TABLE security_profiles ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), name VARCHAR(255) NOT NULL UNIQUE, risk_level risk_level NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE policies ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), name VARCHAR(255) NOT NULL UNIQUE, description TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE controls ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), policy_id UUID NOT NULL REFERENCES policies(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, description TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE findings ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), control_id UUID NOT NULL REFERENCES controls(id) ON DELETE CASCADE, resource_id VARCHAR(255) NOT NULL, status finding_status NOT NULL, details JSONB, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE audit_logs ( id BIGSERIAL PRIMARY KEY, timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(), actor_id VARCHAR(255) NOT NULL, action VARCHAR(255) NOT NULL, resource_type VARCHAR(255), resource_id VARCHAR(255), details JSONB ); -- Make audit_logs append-only CREATE FUNCTION make_audit_log_immutable() RETURNS TRIGGER AS $$ BEGIN RAISE EXCEPTION 'Audit log records cannot be modified or deleted'; END; $$ LANGUAGE plpgsql; CREATE TRIGGER audit_log_immutable_trigger BEFORE UPDATE OR DELETE ON audit_logs FOR EACH ROW EXECUTE PROCEDURE make_audit_log_immutable(); -- Indexes CREATE INDEX ON controls (policy_id); CREATE INDEX ON findings (control_id); CREATE INDEX ON audit_logs (actor_id, timestamp DESC); CREATE INDEX ON audit_logs (resource_type, resource_id); -- +migrate Down DROP TRIGGER IF EXISTS audit_log_immutable_trigger ON audit_logs; DROP FUNCTION IF EXISTS make_audit_log_immutable(); DROP TABLE IF EXISTS audit_logs; DROP TABLE IF EXISTS findings; DROP TABLE IF EXISTS controls; DROP TABLE IF EXISTS policies; DROP TABLE IF EXISTS security_profiles; DROP TYPE IF EXISTS finding_status; DROP TYPE IF EXISTS risk_level;