#!/usr/bin/env python3 """Exercise historical business imports in PostgreSQL and roll back every case.""" import argparse import json from pathlib import Path from apply_weknora_business import build_plan, target_snapshot, PRESERVED_TABLES from apply_weknora_identities import sql_run from verify_asset_etl import unchanged_check from verify_identity_etl import rejected_case from weknora_business_etl import LEDGER, TARGET_DATABASE, TARGET_TENANT, business_sql from weknora_category_etl import literal, save_json from weknora_etl import consistent_snapshot def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--sqlite', type=Path, required=True) parser.add_argument('--kb-mapping', type=Path, required=True) parser.add_argument('--artifact-root', type=Path, required=True) parser.add_argument('--out', type=Path, required=True) args = parser.parse_args() if args.out.exists(): parser.error('Report exists') with consistent_snapshot(args.sqlite) as (db, info): plan = build_plan(db, args.kb_mapping, args.artifact_root) plan['source_snapshot_sha256'] = info['sha256'] query = business_sql(plan) first = plan['resources'][0]['target'] rid = literal(first['id']) owner = literal(next(uid for uid in plan['identity_mapping'].values() if uid != first['owner_id'])) scope = f"target_id={rid} AND target_tenant_id={TARGET_TENANT}" cases = [ ('wrong_resource_tenant_rejected', f'UPDATE workbench_resources SET tenant_id={TARGET_TENANT + 1} WHERE id={rid};', 'Mapped business resource removed'), ('wrong_resource_owner_rejected', f'UPDATE workbench_resources SET owner_id={owner} WHERE id={rid};', 'Mapped business resource removed'), ('deleted_resource_not_recreated', f'DELETE FROM workbench_resources WHERE id={rid};', 'Mapped business resource removed'), ('ledger_target_conflict_rejected', f"UPDATE {LEDGER} SET target_id='rollback-only' WHERE {scope};", 'Business durable mapping conflict'), ('ledger_source_conflict_rejected', f"UPDATE {LEDGER} SET source_snapshot='{{}}' WHERE {scope};", 'Business durable mapping conflict'), ('ledger_projection_conflict_rejected', f"UPDATE {LEDGER} SET native_snapshot='{{}}' WHERE {scope};", 'Business durable mapping conflict'), ('id_without_provenance_rejected', f'DELETE FROM {LEDGER} WHERE {scope};', 'Native business ID or owner/name collision'), ('inactive_tenant_rejected', f"UPDATE tenants SET status='suspended' WHERE id={TARGET_TENANT};", 'Business migration requires active target tenant'), ] before = target_snapshot() ledger_before = sql_run(f"SELECT to_regclass('public.{LEDGER}') IS NOT NULL;") sql = "BEGIN ISOLATION LEVEL SERIALIZABLE; SET LOCAL statement_timeout='45s'; SET LOCAL lock_timeout='5s';\n" preserved = [(table, f'_preserved_{i}') for i, table in enumerate(PRESERVED_TABLES)] for table, name in preserved: sql += f'CREATE TEMP TABLE {name} ON COMMIT DROP AS SELECT * FROM {table};\n' sql += query pairs = [('workbench_resources', '_resource_repeat'), (LEDGER, '_ledger_repeat')] for table, name in pairs: sql += f'CREATE TEMP TABLE {name} ON COMMIT DROP AS SELECT * FROM {table};\n' sql += query + unchanged_check('exact_repeat_noop', pairs + preserved) for name, mutation, expected in cases: sql += rejected_case(name, mutation, expected, query) sql += f"UPDATE workbench_resources SET name='Rollback native edit',status='paused',version=99,data=jsonb_set(data,'{{description}}','\"Local edit\"'::jsonb) WHERE id={rid};\n" sql += 'CREATE TEMP TABLE _native_edit ON COMMIT DROP AS SELECT * FROM workbench_resources;\n' sql += query + unchanged_check('native_edits_preserved', [('workbench_resources', '_native_edit'), pairs[1], *preserved]) sql += 'ROLLBACK;' checks = [json.loads(line) for line in sql_run(sql).splitlines() if line.startswith('{')] checks.extend([ {'check': 'all_cases_executed', 'passed': len(checks) == len(cases) + 2}, {'check': 'all_regressions_rolled_back', 'passed': before == target_snapshot() and ledger_before == sql_run(f"SELECT to_regclass('public.{LEDGER}') IS NOT NULL;")}, {'check': 'source_unchanged', 'passed': not info['source_mutated']}, ]) result = {'database': TARGET_DATABASE, 'committed': False, 'passed': all(c['passed'] for c in checks), 'checks': checks} save_json(args.out, result) print(json.dumps(result)) return 0 if result['passed'] else 1 if __name__ == '__main__': raise SystemExit(main())