"""Persist Message Safety v2 evidence and recovery locations. Revision ID: 0012_safety_v2_checkpoint Revises: 0011_module07_contract Create Date: 2026-08-06 """ from collections.abc import Sequence from alembic import op revision: str = "0012_safety_v2_checkpoint" down_revision: str | None = "0011_module07_contract" branch_labels: str | Sequence[str] | None = None depends_on: str | Sequence[str] | None = None def upgrade() -> None: op.execute( """ ALTER TABLE han_app.messages ADD COLUMN IF NOT EXISTS safety_processing_mode varchar(16), ADD COLUMN IF NOT EXISTS safety_config_version bigint, ADD COLUMN IF NOT EXISTS safety_rules_version varchar(128); ALTER TABLE han_app.message_attachments ADD COLUMN IF NOT EXISTS quarantine_version_id varchar(1024), ADD COLUMN IF NOT EXISTS quarantine_etag varchar(1024); ALTER TABLE han_app.message_attachments DROP CONSTRAINT IF EXISTS message_attachments_scan_status_check; ALTER TABLE han_app.message_attachments ADD CONSTRAINT message_attachments_scan_status_check CHECK (scan_status IN ('pending','clean','bypassed','infected','failed')) NOT VALID; ALTER TABLE han_app.message_attachments VALIDATE CONSTRAINT message_attachments_scan_status_check; ALTER TABLE han_app.safety_tasks ADD COLUMN IF NOT EXISTS poll_location varchar(1024), ADD COLUMN IF NOT EXISTS processing_mode varchar(16), ADD COLUMN IF NOT EXISTS config_version bigint, ADD COLUMN IF NOT EXISTS rules_version varchar(128), ADD COLUMN IF NOT EXISTS expires_at timestamptz; UPDATE han_app.safety_tasks SET poll_location = '/internal/safety/v2/messages/tasks/' || task_id, expires_at = deadline_at WHERE poll_location IS NULL OR expires_at IS NULL; ALTER TABLE han_app.safety_tasks ALTER COLUMN poll_location SET NOT NULL, ALTER COLUMN expires_at SET NOT NULL; """ ) # Notification uploads use the same safety contract. op.execute( """ ALTER TABLE han_app.client_upload_drafts ADD COLUMN IF NOT EXISTS quarantine_version_id varchar(1024), ADD COLUMN IF NOT EXISTS quarantine_etag varchar(1024), ADD COLUMN IF NOT EXISTS safety_processing_mode varchar(16), ADD COLUMN IF NOT EXISTS safety_config_version bigint, ADD COLUMN IF NOT EXISTS safety_rules_version varchar(128); ALTER TABLE han_app.client_upload_drafts DROP CONSTRAINT IF EXISTS client_upload_drafts_scan_status_check; ALTER TABLE han_app.client_upload_drafts ADD CONSTRAINT client_upload_drafts_scan_status_check CHECK (scan_status IN ('pending','clean','bypassed','infected','failed')) NOT VALID; ALTER TABLE han_app.client_upload_drafts VALIDATE CONSTRAINT client_upload_drafts_scan_status_check; """ ) def downgrade() -> None: raise RuntimeError("Safety v2 checkpoint migration is forward-only")