import sys as _sys from pathlib import Path as _Path _sys.path.insert(0, str(_Path(__file__).resolve().parent)) from mutation_profile_fixture import shared_mutation_env # noqa: E402 """The renewal waiver reaches the real enforcement paths (#945 B1). ``tests/test_issue_945_owning_pr_renewal_continuation.py`` proves the pure pieces: that ``issue_lock_renewal.owning_pr_renewal_from_lock`` rebuilds a renewal waiver, that ``_owning_pr_continuation_from_lock`` resolves the two dispositions in the right precedence, and that the duplicate gate honours the resulting token. None of that proves any *production* path consumes the resolver, and review ``623`` demonstrated the gap by reverting the primary call site at ``gitea_mcp_server.py:2894`` back to the recovery-only rebuild: the whole repository stayed green, failing-test ids byte identical. This file closes that hole. Every test here starts from a real durable lock file written to a temporary lock directory and bound to this process's session pointer, then calls the authoritative production entry point — not a helper: * ``mcp_server._enforce_locked_issue_duplicate_recheck`` — the shared recheck behind ``gitea_commit_files`` and ``gitea_create_pr`` * ``mcp_server.gitea_assess_work_issue_duplicate`` — the read-only assessor * ``mcp_server._prove_author_ownership_for_pr`` — the push / PR-update ownership prover, which is also the existing-PR continuation path Only the external boundaries are mocked: Gitea HTTP reads (the duplicate context fetcher, open-PR and branch listings) and the credential header. The reconstruction and enforcement chain under test — lock load, evidence rebuild, resolver precedence, and ``issue_work_duplicate_gate`` — runs for real. ``TestRevertingThePrimaryWiringIsDetected`` is the explicit regression the review asked for: it reproduces the pre-#945 recovery-only call site and asserts the enforcement path then refuses, so the wiring cannot be removed silently. Everything is written under ``tempfile.TemporaryDirectory``. No branch, worktree, PR, comment, lease, or lock outside that directory is created, and no production Gitea or control-plane state is touched (#945 AC18). """ import os import subprocess import sys import tempfile import unittest from datetime import datetime, timedelta, timezone from pathlib import Path from unittest.mock import patch sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) import issue_lock_provenance # noqa: E402 import issue_lock_recovery # noqa: E402 import issue_lock_renewal # noqa: E402 import issue_lock_store # noqa: E402 import mcp_server # noqa: E402 from issue_work_duplicate_gate import ( # noqa: E402 PHASE_COMMIT, PHASE_CREATE_PR, PHASE_LOCK, PHASE_PUSH, ) ISSUE = 4948 OWNING_PR = 4949 OTHER_PR = 4950 OTHER_ISSUE = 4951 BRANCH = f"fix/issue-{ISSUE}-renewal-wiring" OTHER_BRANCH = f"fix/issue-{ISSUE}-competing" HEAD = "e" * 40 OTHER_HEAD = "f" * 40 IDENTITY = "example-user" PROFILE = "test-author-prgs" ORG = "Scaled-Tech-Consulting" REPO = "Gitea-Tools" HOST = "gitea.prgs.cc" def dead_pid() -> int: """A PID that has certainly exited (spawned, then reaped).""" proc = subprocess.Popen([sys.executable, "-c", "pass"]) proc.wait() return proc.pid def shifted_ts(hours: int = 4) -> str: return ( (datetime.now(timezone.utc) + timedelta(hours=hours)) .isoformat() .replace("+00:00", "Z") ) def owning_pr(number=OWNING_PR, ref=BRANCH, sha=HEAD, issue=ISSUE): return { "number": number, "title": f"fix: something (Closes #{issue})", "body": f"Closes #{issue}.", "head": {"ref": ref, "sha": sha}, } def renewal_block( *, pr_number=OWNING_PR, branch=BRANCH, head=HEAD, identity=IDENTITY, profile=PROFILE, ): """The ``lease_renewal`` block ``build_renewal_record`` writes on success.""" return { "renewed": True, "renewed_at": shifted_ts(-1), "prior_pid": 4242, "prior_pid_alive": True, "prior_expires_at": shifted_ts(-1), "replacement_pid": os.getpid(), "new_expires_at": shifted_ts(), "identity": identity, "profile": profile, "branch_name": branch, "worktree_path": os.path.realpath(os.getcwd()), "head_sha": head, "remote_head_sha": head, "pr_head_sha": head, "pr_number": pr_number, "reason": "expired lease renewed by its exact recorded owner", "proof": [], } def recovery_block(*, pr_number=OWNING_PR, branch=BRANCH, head=HEAD): """The ``dead_session_recovery`` block ``build_recovery_record`` writes.""" return { "recovered": True, "reason": "owning MCP session exited; durable ownership evidence matched", "recovered_at": shifted_ts(-1), "prior_session_pid": 4242, "replacement_session_pid": os.getpid(), "prior_pid_alive": False, "branch_name": branch, "pr_number": pr_number, "pr_head": head, "recorded_head": head, "accepted_head": head, "head_relation": issue_lock_recovery.HEAD_RELATION_EQUAL, "identity": IDENTITY, "profile": PROFILE, "proof": [], } class EnforcementPathBase(unittest.TestCase): """Drives production enforcement entry points against a real durable lock. The lock lives in a throwaway directory and is bound to this process's session pointer exactly as ``gitea_lock_issue`` binds it, so ``_load_existing_issue_lock()`` resolves it through the ordinary ``read_session_issue_lock()`` path rather than a test shortcut. """ def setUp(self): self.lock_dir = tempfile.TemporaryDirectory() self.addCleanup(self.lock_dir.cleanup) self.worktree = os.path.realpath(os.getcwd()) self.remotes = patch.dict( mcp_server.REMOTES, {"prgs": {"host": HOST, "org": ORG, "repo": REPO}}, ) self.remotes.start() self.addCleanup(patch.stopall) mcp_server._IDENTITY_CACHE.clear() # ── fixtures ──────────────────────────────────────────────────────────── def build_lock( self, *, issue_number=ISSUE, branch=BRANCH, renewal=None, recovery=None, claimant=None, pid=None, live=True, ): pid = os.getpid() if pid is None else pid claimant = claimant or {"username": IDENTITY, "profile": PROFILE} expires = shifted_ts() if live else shifted_ts(-1) data = { "issue_number": issue_number, "branch_name": branch, "remote": "prgs", "org": ORG, "repo": REPO, "worktree_path": self.worktree, "session_pid": pid, "pid": pid, "claimant": dict(claimant), "work_lease": { "operation_type": issue_lock_store.AUTHOR_ISSUE_WORK_LEASE, "issue_number": issue_number, "branch": branch, "worktree_path": self.worktree, "claimant": dict(claimant), "created_at": shifted_ts(-1), "last_heartbeat_at": shifted_ts(0) if live else shifted_ts(-1), "expires_at": expires, }, "lock_provenance": issue_lock_provenance.build_sanctioned_lock_provenance( tool="gitea_lock_issue", claimant=dict(claimant), ), } if renewal is not None: data["lease_renewal"] = renewal if recovery is not None: data["dead_session_recovery"] = recovery return data def bind(self, data): """Persist the lock and bind it to this process, as the server does.""" issue_lock_store.bind_session_lock(data, self.lock_dir.name) return data def env(self): return shared_mutation_env( PROFILE, include_example_repo=True, GITEA_ISSUE_LOCK_DIR=self.lock_dir.name, ) def gitea_reads(self, *, open_prs, branch_names=None): """Patch only the external Gitea read boundary.""" branch_names = [BRANCH] if branch_names is None else branch_names return ( patch("mcp_server.get_auth_header", return_value="token x"), patch( "mcp_server.issue_duplicate_context_fetcher", side_effect=lambda h, o, r, auth, issue_number: ( list(open_prs), list(branch_names), {"status": "not_claimed"} ), ), patch("mcp_server._list_open_pulls", return_value=list(open_prs)), patch( "mcp_server.api_get_all", return_value=[ {"name": n, "commit": {"id": HEAD}} for n in branch_names ], ), ) # ── production entry points ───────────────────────────────────────────── def run_duplicate_recheck(self, *, phase, open_prs, branch_names=None): """The real shared recheck behind gitea_commit_files / gitea_create_pr.""" patches = self.gitea_reads(open_prs=open_prs, branch_names=branch_names) with patches[0], patches[1], patches[2], patches[3]: with patch.dict(os.environ, self.env(), clear=True): os.environ["GITEA_ISSUE_LOCK_DIR"] = self.lock_dir.name return mcp_server._enforce_locked_issue_duplicate_recheck( "prgs", phase, host=HOST, org=ORG, repo=REPO ) def run_readonly_assessor( self, *, open_prs, issue_number=ISSUE, branch=BRANCH, branch_names=None ): """The real read-only duplicate assessor MCP tool.""" patches = self.gitea_reads(open_prs=open_prs, branch_names=branch_names) with patches[0], patches[1], patches[2], patches[3]: with patch.dict(os.environ, self.env(), clear=True): os.environ["GITEA_ISSUE_LOCK_DIR"] = self.lock_dir.name return mcp_server.gitea_assess_work_issue_duplicate( issue_number=issue_number, branch_name=branch, phase=PHASE_COMMIT, remote="prgs", host=HOST, org=ORG, repo=REPO, ) def run_ownership_prover( self, *, pr_number=OWNING_PR, branch=BRANCH, issue_number=ISSUE ): """The real push / PR-update ownership prover (existing-PR continuation).""" with patch("mcp_server.get_auth_header", return_value="token x"): with patch.dict(os.environ, self.env(), clear=True): os.environ["GITEA_ISSUE_LOCK_DIR"] = self.lock_dir.name return mcp_server._prove_author_ownership_for_pr( pr_number=pr_number, pr_title=f"fix: something (Closes #{issue_number})", pr_body=f"Closes #{issue_number}.", source_branch=branch, remote="prgs", host=HOST, org=ORG, repo=REPO, worktree_path=self.worktree, ) # ─────────────── B1: renewal evidence reaches every enforcement path ─────────── class TestRenewalReachesEnforcementPaths(EnforcementPathBase): """A renewal-only lock must exempt its owning PR at the real call sites. Each of these fails if its call site is reverted to the recovery-only rebuild, because the lock deliberately carries no ``dead_session_recovery`` block at all. """ def setUp(self): super().setUp() self.bind(self.build_lock(renewal=renewal_block())) def test_commit_duplicate_recheck_permits_the_owning_pr(self): blocked = self.run_duplicate_recheck( phase=PHASE_COMMIT, open_prs=[owning_pr()] ) self.assertIsNone( blocked, "commit recheck refused the PR the renewal already proved it owns; " "the resolver is not wired into gitea_mcp_server:2894", ) def test_create_pr_duplicate_recheck_permits_the_owning_pr(self): blocked = self.run_duplicate_recheck( phase=PHASE_CREATE_PR, open_prs=[owning_pr()] ) self.assertIsNone(blocked) def test_read_only_assessor_reports_the_same_exemption(self): result = self.run_readonly_assessor(open_prs=[owning_pr()]) self.assertTrue(result["success"]) self.assertFalse(result["block"]) self.assertTrue(result["owning_pr_recovery_exempted"]) self.assertEqual(result["linked_open_pr"], OWNING_PR) def test_push_ownership_prover_carries_the_renewal_evidence(self): ownership = self.run_ownership_prover() self.assertTrue(ownership["proven"], ownership["reasons"]) token = ownership["recovered_owning_pr"] self.assertIsNotNone( token, "push prover produced no continuation evidence from a renewal lock; " "the resolver is not wired into gitea_mcp_server:19464", ) self.assertEqual(token["pr_number"], OWNING_PR) self.assertEqual(token["branch_name"], BRANCH) self.assertEqual(token["head_sha"], HEAD) def test_all_enforcement_paths_decide_alike_from_one_lock(self): """AC: commit, create-PR, assessor and prover agree on one lock.""" for phase in (PHASE_COMMIT, PHASE_CREATE_PR, PHASE_PUSH, PHASE_LOCK): with self.subTest(phase=phase): self.assertIsNone( self.run_duplicate_recheck(phase=phase, open_prs=[owning_pr()]) ) assessor = self.run_readonly_assessor(open_prs=[owning_pr()]) prover = self.run_ownership_prover() self.assertTrue(assessor["owning_pr_recovery_exempted"]) self.assertEqual( assessor["linked_open_pr"], prover["recovered_owning_pr"]["pr_number"] ) class TestDeadSessionRecoveryStillReachesEnforcementPaths(EnforcementPathBase): """#755/#768 recovery must be unchanged by the #945 resolver.""" def setUp(self): super().setUp() self.bind(self.build_lock(recovery=recovery_block())) def test_commit_recheck_still_permits_a_recovered_owning_pr(self): self.assertIsNone( self.run_duplicate_recheck(phase=PHASE_COMMIT, open_prs=[owning_pr()]) ) def test_assessor_still_reports_the_recovery_exemption(self): result = self.run_readonly_assessor(open_prs=[owning_pr()]) self.assertTrue(result["owning_pr_recovery_exempted"]) def test_prover_still_carries_recovery_evidence(self): token = self.run_ownership_prover()["recovered_owning_pr"] self.assertEqual(token["pr_number"], OWNING_PR) # ──────────────── B1: the explicit anti-revert regression test ──────────────── class TestRevertingThePrimaryWiringIsDetected(EnforcementPathBase): """Reproduce the pre-#945 call site and prove the path then refuses. Review ``623`` reverted ``gitea_mcp_server.py:2894`` from ``_owning_pr_continuation_from_lock`` to ``issue_lock_recovery.recovered_owning_pr_from_lock`` and found the entire repository still green. Substituting exactly that pre-fix behaviour here makes the enforcement path block, so the causal link between the resolver and the gate's answer is asserted, not assumed. """ def setUp(self): super().setUp() self.bind(self.build_lock(renewal=renewal_block())) def test_recovery_only_rebuild_reintroduces_the_945_refusal(self): with patch.object( mcp_server, "_owning_pr_continuation_from_lock", side_effect=issue_lock_recovery.recovered_owning_pr_from_lock, ): blocked = self.run_duplicate_recheck( phase=PHASE_COMMIT, open_prs=[owning_pr()] ) self.assertIsNotNone( blocked, "the pre-#945 recovery-only rebuild must lose the renewal waiver; " "if this passes, the enforcement path is not consuming the resolver", ) self.assertTrue(blocked["block"]) self.assertFalse(blocked["owning_pr_recovery_exempted"]) def test_restoring_the_resolver_restores_continuation(self): self.assertIsNone( self.run_duplicate_recheck(phase=PHASE_COMMIT, open_prs=[owning_pr()]) ) def test_read_only_assessor_is_wired_to_the_same_resolver(self): with patch.object( mcp_server, "_owning_pr_continuation_from_lock", side_effect=issue_lock_recovery.recovered_owning_pr_from_lock, ): result = self.run_readonly_assessor(open_prs=[owning_pr()]) self.assertTrue(result["block"]) self.assertFalse(result["owning_pr_recovery_exempted"]) def test_push_prover_is_wired_to_the_same_resolver(self): with patch.object( mcp_server, "_owning_pr_continuation_from_lock", side_effect=issue_lock_recovery.recovered_owning_pr_from_lock, ): ownership = self.run_ownership_prover() self.assertIsNone(ownership["recovered_owning_pr"]) # ───────────────── B1: the exemption is not widened at the call sites ───────── class TestEnforcementPathsStillFailClosed(EnforcementPathBase): def assert_blocked(self, result): self.assertIsNotNone(result, "expected a fail-closed refusal") self.assertTrue(result["block"]) return result def test_open_pr_alone_grants_no_exemption(self): """No renewal and no recovery block: the open PR still blocks.""" self.bind(self.build_lock()) blocked = self.assert_blocked( self.run_duplicate_recheck(phase=PHASE_COMMIT, open_prs=[owning_pr()]) ) self.assertFalse(blocked["owning_pr_recovery_exempted"]) def test_second_pr_is_refused(self): self.bind(self.build_lock(renewal=renewal_block())) self.assert_blocked( self.run_duplicate_recheck( phase=PHASE_COMMIT, open_prs=[owning_pr(), owning_pr(number=OTHER_PR, ref=OTHER_BRANCH)], ) ) def test_evidence_naming_another_pr_is_refused(self): self.bind(self.build_lock(renewal=renewal_block(pr_number=OTHER_PR))) self.assert_blocked( self.run_duplicate_recheck(phase=PHASE_COMMIT, open_prs=[owning_pr()]) ) def test_unrelated_branch_is_refused(self): self.bind(self.build_lock(renewal=renewal_block(branch=OTHER_BRANCH))) self.assert_blocked( self.run_duplicate_recheck(phase=PHASE_COMMIT, open_prs=[owning_pr()]) ) def test_live_head_divergence_is_refused(self): """Force-push or unrelated remote movement: live PR head no longer matches.""" self.bind(self.build_lock(renewal=renewal_block())) self.assert_blocked( self.run_duplicate_recheck( phase=PHASE_COMMIT, open_prs=[owning_pr(sha=OTHER_HEAD)] ) ) def test_stale_recorded_head_is_refused(self): """The renewal names a head the live PR never had.""" self.bind(self.build_lock(renewal=renewal_block(head=OTHER_HEAD))) self.assert_blocked( self.run_duplicate_recheck(phase=PHASE_COMMIT, open_prs=[owning_pr()]) ) def test_local_remote_head_divergence_is_refused(self): record = renewal_block() record["remote_head_sha"] = OTHER_HEAD self.bind(self.build_lock(renewal=record)) self.assert_blocked( self.run_duplicate_recheck(phase=PHASE_COMMIT, open_prs=[owning_pr()]) ) def test_identity_mismatch_with_the_lock_claimant_is_refused(self): self.bind(self.build_lock(renewal=renewal_block(identity="someone-else"))) self.assert_blocked( self.run_duplicate_recheck(phase=PHASE_COMMIT, open_prs=[owning_pr()]) ) def test_profile_mismatch_with_the_lock_claimant_is_refused(self): self.bind(self.build_lock(renewal=renewal_block(profile="test-reviewer-prgs"))) self.assert_blocked( self.run_duplicate_recheck(phase=PHASE_COMMIT, open_prs=[owning_pr()]) ) def test_ungranted_renewal_block_is_refused(self): record = renewal_block() record["renewed"] = False self.bind(self.build_lock(renewal=record)) self.assert_blocked( self.run_duplicate_recheck(phase=PHASE_COMMIT, open_prs=[owning_pr()]) ) def test_malformed_renewal_block_is_refused(self): record = renewal_block() record["pr_number"] = "not-a-number" self.bind(self.build_lock(renewal=record)) self.assert_blocked( self.run_duplicate_recheck(phase=PHASE_COMMIT, open_prs=[owning_pr()]) ) def test_wrong_issue_evidence_cannot_be_copied_onto_another_lock(self): """A renewal block copied onto a lock for a different issue proves nothing. The rebuilt token takes its ``issue_number`` from the lock it is found on, not from the record, so a block lifted onto another issue's lock claims that issue while still naming the original PR. That copied evidence must not waive the genuine duplicate the other issue has. """ self.bind( self.build_lock(issue_number=OTHER_ISSUE, renewal=renewal_block()) ) blocked = self.assert_blocked( self.run_duplicate_recheck( phase=PHASE_COMMIT, # The real open PR for OTHER_ISSUE is a different PR entirely. open_prs=[ owning_pr(number=OTHER_PR, ref=OTHER_BRANCH, issue=OTHER_ISSUE) ], branch_names=[OTHER_BRANCH], ) ) self.assertFalse(blocked["owning_pr_recovery_exempted"]) def test_refusal_carries_complete_structured_fields(self): self.bind(self.build_lock()) blocked = self.assert_blocked( self.run_duplicate_recheck(phase=PHASE_COMMIT, open_prs=[owning_pr()]) ) for field in ( "block", "outcome", "reasons", "owning_pr_recovery_exempted", "owning_pr_recovery_notes", "linked_open_pr", "linked_open_pr_count", ): with self.subTest(field=field): self.assertIn(field, blocked) self.assertTrue(blocked["reasons"]) class TestSequentialTasksStayIsolated(EnforcementPathBase): """One long-lived daemon serves many tasks; a waiver must not leak forward.""" def test_a_later_lock_without_evidence_does_not_inherit_the_earlier_waiver(self): self.bind(self.build_lock(renewal=renewal_block())) self.assertIsNone( self.run_duplicate_recheck(phase=PHASE_COMMIT, open_prs=[owning_pr()]) ) # Second task in the same process: a fresh lock, no renewal evidence. self.bind( self.build_lock(issue_number=OTHER_ISSUE, branch=OTHER_BRANCH) ) blocked = self.run_duplicate_recheck( phase=PHASE_COMMIT, open_prs=[owning_pr(number=OTHER_PR, ref=OTHER_BRANCH, issue=OTHER_ISSUE)], branch_names=[OTHER_BRANCH], ) self.assertIsNotNone(blocked) self.assertFalse(blocked["owning_pr_recovery_exempted"]) # ───────────── F2: what the caller binding actually is, and is not ──────────── class TestCallerBindingIsStructuralNotFieldComparison(unittest.TestCase): """Document, in executable form, the binding this patch really provides. Review ``623`` found that the claimant check in ``owning_pr_renewal_from_lock`` compares two fields of one server-written lock file and is therefore not bound to the authenticated caller. That is correct, and these tests assert the true guarantee rather than the overstated one: lock *selection* is process-scoped, and the claimant check is an internal-consistency check. No PID-derived, cached, or process-lifetime session authority is invented here — the process scoping asserted below is pre-existing behaviour of ``issue_lock_store``, not something this patch adds. """ def test_lock_selection_is_keyed_to_the_operating_system_process(self): with tempfile.TemporaryDirectory() as root: pointer = issue_lock_store.session_pointer_path(root) self.assertEqual( os.path.basename(pointer), f"session-{os.getpid()}.json" ) def test_a_lock_bound_by_another_process_is_not_reachable(self): """The structural protection: a foreign session pointer is not read.""" with tempfile.TemporaryDirectory() as root: foreign_pointer = os.path.join(root, f"session-{os.getpid() + 1}.json") issue_lock_store.save_lock_file( foreign_pointer, {"lock_file_path": "/nonexistent/foreign.json"} ) self.assertIsNone(issue_lock_store.read_session_issue_lock(root)) def test_claimant_check_does_not_consult_the_live_authenticated_caller(self): """The honest limit: agreement is internal to the lock document. A renewal block whose identity/profile agree with the claimant recorded on the same lock rebuilds successfully, regardless of who is authenticated. Live identity and profile are enforced by the separate mutation-authority and profile gates, not by this rebuild. """ lock = { "issue_number": ISSUE, "branch_name": BRANCH, "claimant": {"username": "unrelated-recorded-user", "profile": PROFILE}, "lease_renewal": renewal_block(identity="unrelated-recorded-user"), } token = issue_lock_renewal.owning_pr_renewal_from_lock(lock) self.assertIsNotNone(token) self.assertEqual(token["pr_number"], OWNING_PR) def test_internal_disagreement_is_what_the_check_actually_rejects(self): lock = { "issue_number": ISSUE, "branch_name": BRANCH, "claimant": {"username": IDENTITY, "profile": PROFILE}, "lease_renewal": renewal_block(identity="someone-else"), } self.assertIsNone(issue_lock_renewal.owning_pr_renewal_from_lock(lock)) class TestNoDurableArtifacts(EnforcementPathBase): def test_enforcement_runs_leave_nothing_outside_the_temp_lock_dir(self): before = sorted(os.listdir(self.lock_dir.name)) self.bind(self.build_lock(renewal=renewal_block())) self.run_duplicate_recheck(phase=PHASE_COMMIT, open_prs=[owning_pr()]) self.run_ownership_prover() after = sorted(os.listdir(self.lock_dir.name)) self.assertNotEqual(before, after, "the test must have written its lock") self.assertTrue( all( os.path.realpath(os.path.join(self.lock_dir.name, name)).startswith( os.path.realpath(self.lock_dir.name) ) for name in after ) ) if __name__ == "__main__": unittest.main()