#!/usr/bin/env python3 """Validate migration 011 and two rebuilds using temp tables in the test DB.""" from __future__ import annotations import json import os import sys from pathlib import Path from sqlalchemy import text ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) os.chdir(ROOT) from app.blif_flow_v2_projection_service import rebuild_blif_flow_v2_projection from app.db import engine NAMED_IDS = { "instalbeira": "5c33db95-fab8-477a-bddd-0b9cc8f91302", "engexicon": "61f1c955-a372-4ea7-b9b0-b8528d74a141", "construrecup": "e3b23ac5-84db-4763-8a31-a684e873032c", "panoramic": "fd79b9a1-07e6-4f61-95e8-09eab89c155e", "x_mat_canonical": "dc89a466-db24-401b-bfe9-d47644b2d0c8", "x_mat_reconstructed": "1816a06e-9a69-4a9b-9279-1263156892d3", "rzsolar_reconstructed": "fd221608-e007-4043-a23d-07e0c119a345", "rzsolar_synthetic": "434124fb-ac19-4d78-909a-55761d7e8daa", } def main() -> None: conn = engine.connect().execution_options(isolation_level="AUTOCOMMIT") try: identity = conn.execute(text( "SELECT current_database(), current_user, current_setting('transaction_read_only')" )).one() if tuple(identity[:2]) != ("clientflow_codex_test", "clientflow_codex_test"): raise RuntimeError(f"refusing persistence validation on {identity!r}") conn.exec_driver_sql("BEGIN READ WRITE") try: stage_fingerprint_before = conn.execute(text(""" SELECT md5(string_agg(id::text || ':' || COALESCE(stage,''), ',' ORDER BY id)) FROM public.opportunities """)).scalar_one() conn.exec_driver_sql("SET LOCAL search_path TO pg_temp, public") conn.exec_driver_sql("CREATE TEMP TABLE opportunities (id UUID PRIMARY KEY)") conn.exec_driver_sql("INSERT INTO opportunities SELECT id FROM public.opportunities") conn.exec_driver_sql("CREATE TEMP TABLE opportunity_events (LIKE public.opportunity_events INCLUDING DEFAULTS INCLUDING CONSTRAINTS)") conn.exec_driver_sql("ALTER TABLE opportunity_events ADD PRIMARY KEY (id)") conn.exec_driver_sql("CREATE TEMP TABLE tasks (LIKE public.tasks INCLUDING DEFAULTS INCLUDING CONSTRAINTS)") conn.exec_driver_sql("ALTER TABLE tasks ADD PRIMARY KEY (id)") conn.exec_driver_sql(Path("migrations/011_blif_flow_v2_persistence.sql").read_text()) first = rebuild_blif_flow_v2_projection( mode="shadow", target_schema="pg_temp", connection=conn, ) transition_count_first = conn.execute(text( "SELECT count(*) FROM pg_temp.opportunity_flow_transitions" )).scalar_one() first_fingerprints = dict(conn.execute(text(""" SELECT opportunity_id::text, source_fingerprint FROM pg_temp.opportunity_flow_state_v2 """)).all()) second = rebuild_blif_flow_v2_projection( mode="shadow", target_schema="pg_temp", connection=conn, ) transition_count_second = conn.execute(text( "SELECT count(*) FROM pg_temp.opportunity_flow_transitions" )).scalar_one() second_fingerprints = dict(conn.execute(text(""" SELECT opportunity_id::text, source_fingerprint FROM pg_temp.opportunity_flow_state_v2 """)).all()) named = {} for name, oid in NAMED_IDS.items(): row = conn.execute(text(""" SELECT opportunity_id::text, material_process_key, canonical_opportunity_id::text, is_duplicate_representation, business_state, business_next_action, diagnostic_status, confidence FROM pg_temp.opportunity_flow_state_v2 WHERE opportunity_id=CAST(:oid AS UUID) """), {"oid": oid}).mappings().one() named[name] = dict(row) stage_fingerprint_after = conn.execute(text(""" SELECT md5(string_agg(id::text || ':' || COALESCE(stage,''), ',' ORDER BY id)) FROM public.opportunities """)).scalar_one() result = { "database": {"name": identity[0], "user": identity[1]}, "migration_scope": "transaction-scoped pg_temp (public tasks is postgres-owned)", "first_rebuild": first, "second_rebuild": second, "transition_count_first": transition_count_first, "transition_count_second": transition_count_second, "idempotent": transition_count_first == transition_count_second and first_fingerprints == second_fingerprints, "opportunity_stage_unchanged": stage_fingerprint_before == stage_fingerprint_after, "named": named, } conn.exec_driver_sql(Path("migrations/011_blif_flow_v2_persistence_down.sql").read_text()) remaining_task_columns = conn.execute(text(""" SELECT count(*) FROM information_schema.columns WHERE table_schema LIKE 'pg_temp_%' AND table_name='tasks' AND column_name IN ('resolution_code','resolved_at','resolved_by_event_id','superseded_by_task_id') """)).scalar_one() result["down_migration_reversible"] = ( conn.execute(text("SELECT to_regclass('pg_temp.opportunity_flow_state_v2')")).scalar_one() is None and conn.execute(text("SELECT to_regclass('pg_temp.opportunity_flow_transitions')")).scalar_one() is None and remaining_task_columns == 0 ) print(json.dumps(result, ensure_ascii=False, indent=2, sort_keys=True)) finally: conn.exec_driver_sql("ROLLBACK") finally: conn.close() if __name__ == "__main__": main()