"""Tests for post-restart MCP reconciliation and completion proof (#662).""" from __future__ import annotations import json import os import unittest from datetime import datetime, timezone import post_restart_reconcile as prr NOW = datetime(2026, 7, 24, 12, 0, 0, tzinfo=timezone.utc) def _base_inventory(**overrides): inv = { "inventory_complete": True, "incomplete_reasons": [], "service_health": {"healthy": True}, "clients": [{"session_id": "c1", "connected": True}], "sessions": [ { "session_id": "s-live", "status": "active", "pid": os.getpid(), "role": "author", } ], "leases": [], "checkpoints_available": False, "worktree_bindings": [{"path": "/tmp/wt", "exists": True}], "pending_mutations": [], "capabilities": {"stale": False}, "boot_head_sha": "a" * 40, "current_head_sha": "a" * 40, "queue_state": {"safe_to_resume": True}, } inv.update(overrides) return inv class IncompleteInventoryTest(unittest.TestCase): def test_incomplete_inventory_fails_closed(self) -> None: proof = prr.reconcile_after_restart( { "inventory_complete": False, "incomplete_reasons": ["control-plane DB unavailable"], }, now=NOW, mode=prr.MODE_ENFORCE, reconcile_id="test-incomplete", ) self.assertEqual(proof.overall_status, prr.STATUS_FAILED) self.assertTrue(proof.mutation_hold) self.assertFalse(proof.inventory_complete) self.assertTrue(proof.proposed_follow_ups) self.assertIn("control-plane DB unavailable", proof.incomplete_reasons) class HappyPathTest(unittest.TestCase): def test_clean_restart_is_complete_without_mutation_hold(self) -> None: proof = prr.reconcile_after_restart( _base_inventory(), now=NOW, mode=prr.MODE_ENFORCE, reconcile_id="test-clean", ) self.assertEqual(proof.overall_status, prr.STATUS_COMPLETE) self.assertFalse(proof.mutation_hold) self.assertEqual(proof.unresolved_count, 0) cp = next(i for i in proof.items if i.dimension == prr.DIM_CHECKPOINTS) self.assertEqual(cp.status, prr.ITEM_SKIPPED) links = proof.as_dict()["links"] self.assertEqual(links["umbrella"], 655) self.assertEqual(links["issue"], 662) self.assertEqual(links["vision"], 652) self.assertEqual(links["roadmap"], 653) class InterruptedMutationTest(unittest.TestCase): def test_mutating_lease_with_dead_owner_is_unresolved(self) -> None: proof = prr.reconcile_after_restart( _base_inventory( leases=[ { "lease_id": "lease-mut", "session_id": "s-dead", "phase": "implementing", "work_kind": "issue", "work_number": 662, "worktree_path": "/tmp/wt-662", "freshness": {"freshness": "stale_dead_process"}, } ] ), now=NOW, mode=prr.MODE_ENFORCE, ) mut = next(i for i in proof.items if i.dimension == prr.DIM_MUTATIONS) self.assertEqual(mut.status, prr.ITEM_UNRESOLVED) self.assertTrue(mut.follow_up_required) interrupted = mut.details["interrupted"] self.assertEqual(len(interrupted), 1) self.assertFalse(interrupted[0]["resume_allowed"]) self.assertTrue(proof.mutation_hold) self.assertTrue( any(f.dimension == prr.DIM_MUTATIONS for f in proof.proposed_follow_ups) ) def test_explicit_pending_mutation_inventory(self) -> None: proof = prr.reconcile_after_restart( _base_inventory( pending_mutations=[ { "session_id": "s1", "phase": "publishing", "work_kind": "pr", "work_number": 856, "reason": "push interrupted mid-flight", } ] ), now=NOW, mode=prr.MODE_LOG_ONLY, ) mut = next(i for i in proof.items if i.dimension == prr.DIM_MUTATIONS) self.assertEqual(mut.status, prr.ITEM_UNRESOLVED) # log_only never holds mutations even when unresolved self.assertFalse(proof.mutation_hold) self.assertEqual(proof.overall_status, prr.STATUS_DEGRADED) class DuplicateClaimsTest(unittest.TestCase): def test_duplicate_live_claims_flagged(self) -> None: proof = prr.reconcile_after_restart( _base_inventory( leases=[ { "lease_id": "l1", "session_id": "s1", "phase": "allocated", "work_kind": "issue", "work_number": 100, "freshness": {"freshness": "active"}, }, { "lease_id": "l2", "session_id": "s2", "phase": "allocated", "work_kind": "issue", "work_number": 100, "freshness": {"freshness": "active"}, }, ] ), now=NOW, mode=prr.MODE_ENFORCE, ) dups = next(i for i in proof.items if i.dimension == prr.DIM_DUPLICATES) self.assertEqual(dups.status, prr.ITEM_UNRESOLVED) self.assertEqual(dups.details["duplicates"][0]["claim_count"], 2) self.assertTrue(proof.mutation_hold) class OrphanSessionTest(unittest.TestCase): def test_active_session_dead_pid_is_unresolved(self) -> None: proof = prr.reconcile_after_restart( _base_inventory( sessions=[ { "session_id": "ghost", "status": "active", "pid": 2_000_000_000, "role": "author", } ] ), now=NOW, mode=prr.MODE_ENFORCE, ) sess = next(i for i in proof.items if i.dimension == prr.DIM_SESSIONS) self.assertEqual(sess.status, prr.ITEM_UNRESOLVED) self.assertIn("ghost", sess.details["orphan_session_ids"]) class CapabilityStaleTest(unittest.TestCase): def test_stale_runtime_unresolved(self) -> None: proof = prr.reconcile_after_restart( _base_inventory(capabilities={"stale": True, "startup_head": "aaa"}), now=NOW, mode=prr.MODE_ENFORCE, ) caps = next(i for i in proof.items if i.dimension == prr.DIM_CAPABILITIES) self.assertEqual(caps.status, prr.ITEM_UNRESOLVED) self.assertTrue(proof.mutation_hold) class MutationsAllowedHelperTest(unittest.TestCase): def test_mutations_allowed_respects_hold(self) -> None: held = prr.reconcile_after_restart( _base_inventory( pending_mutations=[{"phase": "merging", "session_id": "x"}] ), now=NOW, mode=prr.MODE_ENFORCE, ) self.assertFalse(prr.mutations_allowed(held)) self.assertFalse(prr.mutations_allowed(held.as_dict())) clean = prr.reconcile_after_restart( _base_inventory(), now=NOW, mode=prr.MODE_ENFORCE ) self.assertTrue(prr.mutations_allowed(clean)) class CheckpointSoftDependencyTest(unittest.TestCase): def test_checkpoints_when_schema_present(self) -> None: proof = prr.reconcile_after_restart( _base_inventory( checkpoints_available=True, checkpoints=[{"session_id": "s1", "stale": False}], ), now=NOW, ) cp = next(i for i in proof.items if i.dimension == prr.DIM_CHECKPOINTS) self.assertEqual(cp.status, prr.ITEM_RESOLVED) def test_stale_checkpoints_unresolved(self) -> None: proof = prr.reconcile_after_restart( _base_inventory( checkpoints_available=True, checkpoints=[{"session_id": "s1", "stale": True}], ), now=NOW, mode=prr.MODE_ENFORCE, ) cp = next(i for i in proof.items if i.dimension == prr.DIM_CHECKPOINTS) self.assertEqual(cp.status, prr.ITEM_UNRESOLVED) class ProofSerializationTest(unittest.TestCase): def test_as_dict_is_json_friendly(self) -> None: proof = prr.reconcile_after_restart(_base_inventory(), now=NOW) blob = json.dumps(proof.as_dict()) self.assertIn("reconcile_id", blob) self.assertIn("proposed_follow_ups", blob) if __name__ == "__main__": unittest.main()