"""message safety v2 normative schema Revision ID: 0001_message_safety_v2 """ from alembic import op from app.db import Base revision = "0001_message_safety_v2" down_revision = None branch_labels = None depends_on = None def upgrade() -> None: op.execute("CREATE SCHEMA IF NOT EXISTS message_safety") Base.metadata.create_all(bind=op.get_bind()) op.execute( """ CREATE OR REPLACE FUNCTION message_safety.guard_config_immutable() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN IF OLD.version IS DISTINCT FROM NEW.version OR OLD.schema_version IS DISTINCT FROM NEW.schema_version OR OLD.config IS DISTINCT FROM NEW.config OR OLD.config_sha256 IS DISTINCT FROM NEW.config_sha256 THEN RAISE EXCEPTION 'immutable config fields cannot be changed'; END IF; RETURN NEW; END $$; """ ) op.execute( """ CREATE TRIGGER config_immutable BEFORE UPDATE ON message_safety.config_versions FOR EACH ROW EXECUTE FUNCTION message_safety.guard_config_immutable(); """ ) op.execute( """ CREATE OR REPLACE FUNCTION message_safety.guard_terminal_task() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN IF OLD.status IN ('allowed','denied','failed') AND ROW(OLD.*) IS DISTINCT FROM ROW(NEW.*) THEN RAISE EXCEPTION 'terminal safety task is immutable'; END IF; RETURN NEW; END $$; """ ) op.execute( """ CREATE TRIGGER task_terminal_immutable BEFORE UPDATE ON message_safety.safety_tasks FOR EACH ROW EXECUTE FUNCTION message_safety.guard_terminal_task(); """ ) def downgrade() -> None: op.execute("DROP SCHEMA message_safety CASCADE")