diff --git a/tests/integrations/pg-mdm/evidence/v0.49-logical-restore/conflict-resolution-clean-clone.json b/tests/integrations/pg-mdm/evidence/v0.49-logical-restore/conflict-resolution-clean-clone.json new file mode 100644 index 0000000..33df973 --- /dev/null +++ b/tests/integrations/pg-mdm/evidence/v0.49-logical-restore/conflict-resolution-clean-clone.json @@ -0,0 +1,13 @@ +{ + "command": "python3 tests/integrations/pg-mdm/time/audit.py pgreact:pr30-newer-producer-probe OUTPUT --clean-clone", + "react_source": "094fa659ce7eb571e179a765564af2fb916379ba", + "audit_sha256": "c6633a114510b2fea463838052d39d2649a00310fbe4b8b4c360189314d50633", + "image": "sha256:a0855cbd661820d0243236d97b1764417f3b6cbf25eccb1db3ed08171d9db2fc", + "mdm_source": "49c00e965aa7e1ade37cd89e9b1795e16ef4b3ee", + "pg_trickle_image": "pgtrickle-1250:pr1258-47e088a", + "pg_trickle_library_sha256": "c0db2909d3eba45dcf757b915932dca4dd115f6fb4d1538da44ce1dbf2856392", + "exit": 0, + "result": "Clean physical clone remained fenced before activation and bounded resumption passed.", + "scope": "Local amd64-emulated diagnostic with non-pinned producer; not merged-artifact or release qualification.", + "archive_sha256": "e1987277fe07251055aa8400344e367fc2dbdc188fb322b0d37549b8baf31b98" +} diff --git a/tests/integrations/pg-mdm/evidence/v0.49-logical-restore/conflict-resolution-clean-clone.tar.gz b/tests/integrations/pg-mdm/evidence/v0.49-logical-restore/conflict-resolution-clean-clone.tar.gz new file mode 100644 index 0000000..2358b00 Binary files /dev/null and b/tests/integrations/pg-mdm/evidence/v0.49-logical-restore/conflict-resolution-clean-clone.tar.gz differ diff --git a/tests/integrations/pg-mdm/evidence/v0.49-logical-restore/conflict-resolution-local-setup-failure.json b/tests/integrations/pg-mdm/evidence/v0.49-logical-restore/conflict-resolution-local-setup-failure.json new file mode 100644 index 0000000..2cb01cd --- /dev/null +++ b/tests/integrations/pg-mdm/evidence/v0.49-logical-restore/conflict-resolution-local-setup-failure.json @@ -0,0 +1,26 @@ +{ + "react_base": "f19397a6a96ea563dd0b63ac556031a98aca47c7", + "react_pre_merge_head": "9d5d046d579e86f2acd6ffebac3febbdba426004", + "scope": "Dirty conflict-resolution source; local amd64-emulated image with a non-pinned producer. Not qualification.", + "image": "sha256:0f68e5112d2c734db10303658010086ebfa071c00ef61db3b23c7e040102e530", + "mdm_source": "49c00e965aa7e1ade37cd89e9b1795e16ef4b3ee", + "pg_trickle_source": "d8bb8eb5d45c5c9982e173e88914037433a1ee05", + "pg_trickle_archive_sha256": "b8d4c59ca3770c305659255e09adde303b4d220228392c415ae363aa7be0a2da", + "runs": [ + { + "command": "python3 tests/integrations/pg-mdm/time/audit.py pgreact:pr29-labelled-local OUTPUT", + "exit": 130, + "result": "Wrong mode invoked and interrupted before test output" + }, + { + "command": "python3 tests/integrations/pg-mdm/time/audit.py pgreact:pr29-labelled-local OUTPUT --logical-restore", + "exit": 1, + "result": "Fixture refresh failed with permission denied for pgtrickle.pgt_dependencies before dump; logical regression not reached" + } + ], + "archive_sha256": "bc0872762d1109be42b40608f9537647cbd337ef35c3bcd8f90aff31e36076b1", + "pinned_image_build": { + "exit": 130, + "result": "Cancelled during toolchain compilation after the conflict regression was verified on a separately identified diagnostic image; exact pinned image not built." + } +} diff --git a/tests/integrations/pg-mdm/evidence/v0.49-logical-restore/conflict-resolution-local-setup-failure.tar.gz b/tests/integrations/pg-mdm/evidence/v0.49-logical-restore/conflict-resolution-local-setup-failure.tar.gz new file mode 100644 index 0000000..3d4d72e Binary files /dev/null and b/tests/integrations/pg-mdm/evidence/v0.49-logical-restore/conflict-resolution-local-setup-failure.tar.gz differ diff --git a/tests/integrations/pg-mdm/evidence/v0.49-logical-restore/conflict-resolution-logical-regression.json b/tests/integrations/pg-mdm/evidence/v0.49-logical-restore/conflict-resolution-logical-regression.json new file mode 100644 index 0000000..2a92beb --- /dev/null +++ b/tests/integrations/pg-mdm/evidence/v0.49-logical-restore/conflict-resolution-logical-regression.json @@ -0,0 +1,13 @@ +{ + "command": "python3 tests/integrations/pg-mdm/time/audit.py pgreact:pr30-newer-producer-probe OUTPUT --logical-restore", + "react_source": "094fa659ce7eb571e179a765564af2fb916379ba", + "audit_sha256": "c6633a114510b2fea463838052d39d2649a00310fbe4b8b4c360189314d50633", + "image": "sha256:a0855cbd661820d0243236d97b1764417f3b6cbf25eccb1db3ed08171d9db2fc", + "mdm_source": "49c00e965aa7e1ade37cd89e9b1795e16ef4b3ee", + "pg_trickle_image": "pgtrickle-1250:pr1258-47e088a", + "pg_trickle_library_sha256": "c0db2909d3eba45dcf757b915932dca4dd115f6fb4d1538da44ce1dbf2856392", + "exit": 1, + "result": "Dump and restore completed; regression failed at complete state equality as intended, with missing work, rules, events, attempts, activations and rule versions.", + "scope": "Local amd64-emulated diagnostic with non-pinned producer; not merged-artifact or release qualification.", + "archive_sha256": "9d18e0832d1f44174405421f73052ea5050731f7aafc1fed57763b2a301a5fd2" +} diff --git a/tests/integrations/pg-mdm/evidence/v0.49-logical-restore/conflict-resolution-logical-regression.tar.gz b/tests/integrations/pg-mdm/evidence/v0.49-logical-restore/conflict-resolution-logical-regression.tar.gz new file mode 100644 index 0000000..2342295 Binary files /dev/null and b/tests/integrations/pg-mdm/evidence/v0.49-logical-restore/conflict-resolution-logical-regression.tar.gz differ diff --git a/tests/integrations/pg-mdm/evidence/v0.49-logical-restore/full-vectors.tar.gz b/tests/integrations/pg-mdm/evidence/v0.49-logical-restore/full-vectors.tar.gz new file mode 100644 index 0000000..d705957 Binary files /dev/null and b/tests/integrations/pg-mdm/evidence/v0.49-logical-restore/full-vectors.tar.gz differ diff --git a/tests/integrations/pg-mdm/evidence/v0.49-logical-restore/manifest.json b/tests/integrations/pg-mdm/evidence/v0.49-logical-restore/manifest.json new file mode 100644 index 0000000..2e61969 --- /dev/null +++ b/tests/integrations/pg-mdm/evidence/v0.49-logical-restore/manifest.json @@ -0,0 +1,36 @@ +{ + "react_source": "96e07090be0da636445b5a27fd8d5e39cc5476f1", + "mdm_source": "061705d17dae52697f6e7371b7840722a6ad4bde", + "pg_trickle_source": "c7e257e551d6f6bacb84ecfe6f5daf7637c53673", + "pg_trickle_source_archive_sha256": "d6233d49cee80d33ac30626e0af7932a9698279233b8fceef3447275192abc27", + "image": "sha256:90559bc08fca7b5215a4d6830388c123ca626d5e43de8b2b81274a960c245c6e", + "architecture": "amd64 emulated on aarch64; not performance evidence", + "regression_sha256": "9894ff77ca875c1f622c2a7d629da7ac91c408ed71d9bd97ffe9a5c2b34d4970", + "raw_sha256": "9a8fd12182624e11708f56f353bd3663d81531c09a3f1f17643f9f547d2412e9", + "command": "python3 tests/integrations/pg-mdm/time/audit.py pgreact:merged-stack-clone OUTPUT --logical-restore", + "runs": [ + { + "path": "pgreact-logical-restore-baseline-20261027", + "exit": 1, + "result": "pg_dump and pg_restore succeeded; complete durable vectors differed because React work and attempts were absent" + }, + { + "path": "pgreact-logical-isolated-baseline-20261027", + "exit": 1, + "result": "pg_dump and pg_restore succeeded; complete durable vectors differed because React work and attempts were absent" + } + ], + "release_qualified": false, + "full_vectors": { + "exit": 1, + "result": "The source pg_react extension has no registered dump configuration; exact rule, rule-version, activation, event, work and attempt vectors are missing after successful restore.", + "raw_sha256": "cfbe6cf049c19e7123df71f7bc1676f51766a45a920c73b48ae6408a8d229713", + "regression_sha256": "3891c96042c006686c71de9b3378e8332b4f972830e9ebb2d80b00646e89142d" + }, + "mapped_vectors": { + "exit": 1, + "result": "Restore lost rules, versions, activations, events, work and attempts; role and relation identities are compared by resolved names, not copied OIDs.", + "regression_sha256": "425238d7a2fa7acbb1aeebb509372ba38ffcf89953447970933de5cd19c3ef54", + "raw_sha256": "daab1454f4394ec99c4d8ab8395ae9a167c281a13205de7f5600160be5ee6b81" + } +} diff --git a/tests/integrations/pg-mdm/evidence/v0.49-logical-restore/mapped-vectors.tar.gz b/tests/integrations/pg-mdm/evidence/v0.49-logical-restore/mapped-vectors.tar.gz new file mode 100644 index 0000000..ea63371 Binary files /dev/null and b/tests/integrations/pg-mdm/evidence/v0.49-logical-restore/mapped-vectors.tar.gz differ diff --git a/tests/integrations/pg-mdm/evidence/v0.49-logical-restore/raw-runs.tar.gz b/tests/integrations/pg-mdm/evidence/v0.49-logical-restore/raw-runs.tar.gz new file mode 100644 index 0000000..f88963b Binary files /dev/null and b/tests/integrations/pg-mdm/evidence/v0.49-logical-restore/raw-runs.tar.gz differ diff --git a/tests/integrations/pg-mdm/time/audit.py b/tests/integrations/pg-mdm/time/audit.py index c4e903d..0213038 100644 --- a/tests/integrations/pg-mdm/time/audit.py +++ b/tests/integrations/pg-mdm/time/audit.py @@ -18,11 +18,12 @@ def main(): outage_mode = sys.argv[3:] == ["--outage"] multi_outage = sys.argv[3:] == ["--multi-outage"] pending_recovery = sys.argv[3:] == ["--pending-recovery"] + logical_restore = sys.argv[3:] == ["--logical-restore"] clean_clone = sys.argv[3:] == ["--clean-clone"] direct_clone = sys.argv[3:] == ["--clean-clone-direct"] autonomous_recurrence = sys.argv[3:] == ["--autonomous-recurrence"] claimed_work = sys.argv[3:] == ["--claimed-work"] - if sys.argv[3:] and not (maintenance or compatibility_only or outage_mode or multi_outage or pending_recovery or clean_clone or direct_clone or autonomous_recurrence or claimed_work): + if sys.argv[3:] and not (maintenance or compatibility_only or outage_mode or multi_outage or pending_recovery or logical_restore or clean_clone or direct_clone or autonomous_recurrence or claimed_work): raise ValueError("unsupported audit mode after IMAGE OUTPUT") output = Path(output).resolve() output.mkdir(parents=True, exist_ok=False) @@ -376,15 +377,52 @@ def qualify_pending_recovery(): "'attempts',(SELECT COALESCE(jsonb_agg(to_jsonb(a) ORDER BY execution_id),'[]') FROM pgreact.attempts a WHERE name='s1-future-idle')," "'business',(SELECT jsonb_agg(to_jsonb(s) ORDER BY id) FROM public.policy_qualification_source s)," "'technical',(SELECT jsonb_agg(to_jsonb(s) ORDER BY case_key) FROM s1_audit.source s))") + if logical_restore: + state_query = state_query[:-1] + ( + ",'rules',(SELECT COALESCE(jsonb_agg((to_jsonb(r)-'owner_oid') || " + "jsonb_build_object('owner_name',pg_get_userbyid(r.owner_oid)) ORDER BY rule_id),'[]') FROM pgreact_internal.rules r)," + "'rule_versions',(SELECT COALESCE(jsonb_agg((to_jsonb(v)-ARRAY['owner_oid','source_view_oid','match_relid','consequence_oid','dispatcher_oid']) || " + "jsonb_build_object('owner_name',pg_get_userbyid(v.owner_oid),'source_identity',v.source_view_oid::regclass::text," + "'match_identity',v.match_relid::regclass::text,'consequence_actual',v.consequence_oid::regprocedure::text," + "'dispatcher_actual',v.dispatcher_oid::regprocedure::text) ORDER BY rule_version_id),'[]') FROM pgreact_internal.rule_versions v)," + "'activations',(SELECT COALESCE(jsonb_agg(to_jsonb(a) ORDER BY rule_version_id,activation_id),'[]') FROM pgreact_internal.activation_state a)," + "'events',(SELECT COALESCE(jsonb_agg(to_jsonb(e) ORDER BY event_id),'[]') FROM pgreact_internal.lifecycle_events e))") before = json.loads(sql(state_query)) observations["pending_setup"] = {"cycle": result, "state": before} assert sorted(w["state"] for w in before["work"]) == ["COMPLETED", "PENDING"], observations["pending_setup"] + if logical_restore: + qualify_logical_restore(state_query, before) + return if clean_clone or direct_clone: qualify_clean_clone(state_query, before) return qualify_recovery(state_query, pending=True) observations["entry_decision"] = "FOCUSED_PENDING_RECOVERY_QUALIFIED" + def qualify_logical_restore(state_query, before): + config_query = ("SELECT COALESCE(jsonb_agg(c.oid::regclass::text ORDER BY c.oid::regclass::text),'[]') " + "FROM pg_extension e CROSS JOIN LATERAL unnest(e.extconfig) object_oid " + "JOIN pg_class c ON c.oid=object_oid WHERE e.extname='pg_react'") + observations["react_dump_config"] = json.loads(sql(config_query)) + command("docker", "exec", container, "pg_dump", "-Fc", "-U", "postgres", "-d", "s1_idle", + "-f", "/tmp/s1-logical.dump") + command("docker", "exec", container, "createdb", "-U", "postgres", "s1_logical") + try: + result = command("docker", "exec", container, "pg_restore", "--exit-on-error", "-U", "postgres", + "-d", "s1_logical", "/tmp/s1-logical.dump") + except RuntimeError as error: + (output / "logical-restore.log").write_text(str(error)) + observations["checks"].append({"name": "logical-restore", "result": "failed"}) + raise + (output / "logical-restore.log").write_text(result) + after = json.loads(sql(state_query, "s1_logical")) + observations["logical_restore"] = {"before": before, "after": after, + "source_database_oid": sql("SELECT oid FROM pg_database WHERE datname=current_database()"), + "restored_database_oid": sql("SELECT oid FROM pg_database WHERE datname=current_database()", "s1_logical")} + assert after == before, "logical restore did not preserve the complete recovery state" + assert sql("SELECT NOT EXISTS (SELECT 1 FROM pgreact_mdm.authorized_policy_cases_v1)", "s1_logical") == "t" + observations["checks"].append({"name": "logical-restore-preactivation", "result": "passed"}) + def qualify_clean_clone(state_query, before): nonlocal container, clone_volume original = container @@ -1003,7 +1041,7 @@ def qualify_two_case_outage(before, source, temporal_source): if compatibility_only: observations["entry_decision"] = "ADMISSION_ONLY" return - if clean_clone or direct_clone: + if logical_restore or clean_clone or direct_clone: observations["fixture_sha256"] = command("docker", "exec", container, "sha256sum", "/usr/lib/postgresql/18/lib/pg_react.so", "/usr/lib/postgresql/18/lib/pg_mdm.so", "/usr/lib/postgresql/18/lib/pg_trickle.so").splitlines()