-- Operator-only rollback; refuses to discard any retirement or withdrawn publication. BEGIN; DO $$ BEGIN IF current_database() <> 'booking_test' THEN RAISE EXCEPTION 'ARR daily lifecycle rollback is allowed only in booking_test'; END IF; IF EXISTS (SELECT 1 FROM ingestion.daily_run_retirements) OR EXISTS (SELECT 1 FROM reporting.monthly_runs WHERE report_status = 'withdrawn' OR withdrawn_at IS NOT NULL) THEN RAISE EXCEPTION 'refusing rollback: daily retirement or withdrawn monthly audit exists'; END IF; END $$; DROP TRIGGER daily_run_retirements_immutable ON ingestion.daily_run_retirements; DROP FUNCTION ingestion.protect_daily_run_retirement(); DROP TABLE ingestion.daily_run_retirements; DROP INDEX reporting.monthly_runs_live_snapshot_unique; ALTER TABLE reporting.monthly_runs DROP CONSTRAINT monthly_runs_report_status_check, DROP CONSTRAINT monthly_runs_status_shape, DROP COLUMN withdrawn_at; ALTER TABLE reporting.monthly_runs ADD CONSTRAINT monthly_runs_report_status_check CHECK (report_status IN ('reserved', 'active', 'superseded', 'failed')), ADD CONSTRAINT monthly_runs_snapshot_unique UNIQUE (period_start, source_snapshot_sha256), ADD CONSTRAINT monthly_runs_status_shape CHECK ( ( report_status = 'reserved' AND workbook_artifact_id IS NULL AND result_artifact_id IS NULL AND semantic_sha256 IS NULL AND failure_code IS NULL AND failure_message IS NULL AND published_at IS NULL AND superseded_at IS NULL AND failed_at IS NULL ) OR ( report_status = 'active' AND workbook_artifact_id IS NOT NULL AND result_artifact_id IS NOT NULL AND semantic_sha256 IS NOT NULL AND failure_code IS NULL AND failure_message IS NULL AND published_at IS NOT NULL AND superseded_at IS NULL AND failed_at IS NULL ) OR ( report_status = 'superseded' AND workbook_artifact_id IS NOT NULL AND result_artifact_id IS NOT NULL AND semantic_sha256 IS NOT NULL AND failure_code IS NULL AND failure_message IS NULL AND published_at IS NOT NULL AND superseded_at IS NOT NULL AND failed_at IS NULL ) OR ( report_status = 'failed' AND workbook_artifact_id IS NULL AND result_artifact_id IS NULL AND semantic_sha256 IS NULL AND failure_code IS NOT NULL AND btrim(failure_code) <> '' AND published_at IS NULL AND superseded_at IS NULL AND failed_at IS NOT NULL ) ); CREATE OR REPLACE FUNCTION finance.validate_current_daily_version() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM finance.daily_versions AS version WHERE version.id = NEW.daily_version_id AND version.business_date = NEW.business_date AND version.version_status = 'active' ) THEN RAISE EXCEPTION 'current business date must reference an active daily version'; END IF; RETURN NEW; END; $$; CREATE OR REPLACE FUNCTION reporting.protect_published_monthly_child() RETURNS trigger LANGUAGE plpgsql AS $$ DECLARE parent_id bigint; BEGIN parent_id := COALESCE(OLD.report_id, NEW.report_id); IF EXISTS ( SELECT 1 FROM reporting.monthly_runs AS run WHERE run.id = parent_id AND run.report_status IN ('active', 'superseded') ) THEN RAISE EXCEPTION 'published monthly report lineage and manifest are immutable'; END IF; RETURN COALESCE(NEW, OLD); END; $$; COMMIT;