"""Regression coverage for reviewer-lease preflight ordering (#763).""" from __future__ import annotations from datetime import datetime, timezone from unittest.mock import patch import pytest import anti_stomp_preflight import gitea_mcp_server as server import merger_lease_adoption import reviewer_pr_lease import task_capability_map def _prime_clean_reviewer_preflight(monkeypatch, resolved_task: str) -> None: """Install a clean reviewer preflight without bypassing task matching.""" monkeypatch.setenv("GITEA_TEST_PORCELAIN", "") monkeypatch.delenv("GITEA_TEST_FORCE_DIRTY", raising=False) monkeypatch.setattr(server, "_preflight_in_test_mode", lambda: False) monkeypatch.setattr(server, "_process_start_porcelain", "") monkeypatch.setattr(server, "_preflight_whoami_called", False) monkeypatch.setattr(server, "_preflight_capability_called", False) monkeypatch.setattr(server, "_preflight_whoami_violation", False) monkeypatch.setattr(server, "_preflight_capability_violation", False) monkeypatch.setattr(server, "_preflight_resolved_role", None) monkeypatch.setattr(server, "_preflight_resolved_task", None) monkeypatch.setattr(server, "_preflight_whoami_baseline_porcelain", None) monkeypatch.setattr(server, "_preflight_capability_baseline_porcelain", None) monkeypatch.setattr(server, "_preflight_whoami_violation_files", []) monkeypatch.setattr(server, "_preflight_capability_violation_files", []) monkeypatch.setattr(server, "_preflight_reviewer_violation_files", []) monkeypatch.setattr( server, "_resolve_namespace_mutation_context", lambda _worktree=None: { "workspace_path": server.PROJECT_ROOT, "canonical_repo_root": server.PROJECT_ROOT, "process_project_root": server.PROJECT_ROOT, "workspace_role_kind": "reviewer", "workspace_binding_source": "test reviewer binding", "ignored_bindings": [], }, ) monkeypatch.setattr(server, "_enforce_stable_branch_contamination_gate", lambda *_a: None) monkeypatch.setattr(server, "_enforce_canonical_repository_root", lambda *_a, **_k: None) monkeypatch.setattr(server, "_enforce_root_checkout_guard", lambda *_a: None) monkeypatch.setattr(server, "_enforce_branches_only_author_mutation", lambda *_a, **_k: None) monkeypatch.setattr(server, "_enforce_issue_scope_guard", lambda *_a, **_k: None) monkeypatch.setattr(server, "_create_issue_bootstrap_assessment", lambda *_a: None) monkeypatch.setattr(server, "_run_anti_stomp_preflight", lambda *_a, **_k: None) server.record_preflight_check("whoami") server.record_preflight_check( "capability", resolved_role="reviewer", resolved_task=resolved_task ) def test_documented_review_capability_allows_reviewer_lease_acquire(monkeypatch): """whoami -> resolve(review_pr) -> acquire reviewer lease is canonical.""" _prime_clean_reviewer_preflight(monkeypatch, "review_pr") server.verify_preflight_purity(task="acquire_reviewer_pr_lease") assert server._preflight_capability_called is False def test_exact_lease_capability_without_intervening_call_still_succeeds(monkeypatch): _prime_clean_reviewer_preflight(monkeypatch, "acquire_reviewer_pr_lease") server.verify_preflight_purity(task="acquire_reviewer_pr_lease") assert server._preflight_capability_called is False def test_missing_wrong_and_consumed_capability_fail_closed(monkeypatch): _prime_clean_reviewer_preflight(monkeypatch, "create_issue") with pytest.raises(RuntimeError, match="task mismatch"): server.verify_preflight_purity(task="acquire_reviewer_pr_lease") _prime_clean_reviewer_preflight(monkeypatch, "acquire_reviewer_pr_lease") server.verify_preflight_purity(task="acquire_reviewer_pr_lease") with pytest.raises(RuntimeError, match="has not been resolved"): server.verify_preflight_purity(task="acquire_reviewer_pr_lease") def test_documented_intervening_whoami_read_preserves_capability(monkeypatch): _prime_clean_reviewer_preflight(monkeypatch, "review_pr") with patch.object(server, "_get_workspace_porcelain", return_value=""): server.record_preflight_check("whoami") server.verify_preflight_purity(task="acquire_reviewer_pr_lease") def test_reviewer_transition_is_narrow_alias_aware_and_one_way(): assert task_capability_map.preflight_task_matches( "review_pr", "gitea_acquire_reviewer_pr_lease" ) assert task_capability_map.preflight_task_matches( "gitea_acquire_reviewer_pr_lease", "acquire_reviewer_pr_lease" ) assert not task_capability_map.preflight_task_matches( "acquire_reviewer_pr_lease", "review_pr" ) assert not task_capability_map.preflight_task_matches( "review_pr", "acquire_merger_pr_lease" ) assert not task_capability_map.preflight_task_matches( "merge_pr", "acquire_reviewer_pr_lease" ) def test_dirty_reviewer_workspace_still_fails_closed(monkeypatch): _prime_clean_reviewer_preflight(monkeypatch, "review_pr") monkeypatch.setenv("GITEA_TEST_PORCELAIN", " M gitea_mcp_server.py\n") with pytest.raises(RuntimeError, match="Reviewer role violation"): server.verify_preflight_purity(task="acquire_reviewer_pr_lease") def test_mismatched_reviewer_workspace_still_fails_closed(monkeypatch): _prime_clean_reviewer_preflight(monkeypatch, "review_pr") monkeypatch.setattr( server, "_resolve_namespace_mutation_context", lambda _worktree=None: { "workspace_path": "/outside/review-pr-762", "canonical_repo_root": "/repo", "process_project_root": "/repo", "workspace_role_kind": "reviewer", "workspace_binding_source": "test reviewer binding", "ignored_bindings": [], }, ) monkeypatch.setattr( server.author_mutation_worktree, "assess_workspace_repo_membership", lambda **_kwargs: {"block": True, "reasons": ["workspace mismatch"]}, ) monkeypatch.setattr( server.author_mutation_worktree, "format_workspace_repo_membership_error", lambda _assessment: "workspace mismatch (fail closed)", ) with pytest.raises(RuntimeError, match="workspace mismatch"): server.verify_preflight_purity(task="acquire_reviewer_pr_lease") def test_reviewer_lease_acquire_requires_workflow_load_proof(monkeypatch): sha = "a" * 40 monkeypatch.setattr(server, "_anti_stomp_in_test_mode", lambda: False) monkeypatch.setattr( server, "get_profile", lambda: { "profile_name": "prgs-reviewer", "role": "reviewer", "allowed_operations": [ "gitea.read", "gitea.pr.comment", "gitea.pr.review", ], }, ) monkeypatch.setattr(server, "_actual_profile_role", lambda: "reviewer") monkeypatch.setattr( server, "_resolve_namespace_mutation_context", lambda _worktree=None: { "workspace_path": "/repo/branches/review-pr-762", "canonical_repo_root": "/repo", "process_project_root": "/repo", }, ) monkeypatch.setattr( server.issue_lock_worktree, "read_worktree_git_state", lambda _path: { "current_branch": "master", "head_sha": sha, "porcelain_status": "", }, ) monkeypatch.setattr( server.root_checkout_guard, "resolve_remote_master_sha", lambda _path: sha, ) monkeypatch.setattr( server, "_current_master_parity", lambda: {"startup_head": sha, "current_head": sha}, ) monkeypatch.setattr(server, "_local_git_remote_url", lambda _remote: None) monkeypatch.setattr(server, "_load_stable_contamination_marker", lambda _remote: None) monkeypatch.setattr( server, "_review_workflow_load_gate_reasons", lambda: ["canonical review workflow proof missing"], ) with pytest.raises(RuntimeError, match="workflow"): server._run_anti_stomp_preflight( "acquire_reviewer_pr_lease", remote="prgs", worktree_path="/repo/branches/review-pr-762", org="Scaled-Tech-Consulting", repo="Gitea-Tools", ) def test_whoami_identity_mismatch_invalidates_preflight(monkeypatch): monkeypatch.setenv("GITEA_TEST_PORCELAIN", "") monkeypatch.setattr(server, "_process_start_porcelain", "") monkeypatch.setattr(server, "_preflight_whoami_called", False) monkeypatch.setattr(server, "_preflight_capability_called", True) monkeypatch.setattr(server, "_auth", lambda _host: "redacted") monkeypatch.setattr( server, "api_request", lambda *_args, **_kwargs: {"login": "wrong-reviewer", "id": 7}, ) monkeypatch.setattr( server, "get_profile", lambda: { "profile_name": "prgs-reviewer", "role": "reviewer", "username": "sysadmin", "allowed_operations": ["gitea.read", "gitea.pr.review"], "forbidden_operations": [], }, ) monkeypatch.setattr(server, "_seed_session_context", lambda **_kwargs: None) monkeypatch.setattr(server.session_ctx, "mutation_context_audit_fields", lambda: {}) monkeypatch.setattr(server, "_reveal_endpoints", lambda: False) result = server.gitea_whoami(remote="prgs") assert result["identity_match"] is False assert server._preflight_whoami_called is False assert server._preflight_capability_called is False def test_denied_reviewer_profile_does_not_leave_capability_proof(monkeypatch): profile = { "profile_name": "prgs-author", "role": "author", "username": "jcwalker3", "allowed_operations": [ "gitea.read", "gitea.pr.comment", "gitea.pr.review", ], "forbidden_operations": [], } monkeypatch.setenv("GITEA_TEST_PORCELAIN", "") monkeypatch.setattr(server, "_process_start_porcelain", "") monkeypatch.setattr(server, "get_profile", lambda: profile) monkeypatch.setattr( server.gitea_config, "load_config", lambda: {"profiles": {"prgs-author": profile}}, ) monkeypatch.setattr(server.gitea_config, "is_runtime_switching_enabled", lambda: False) monkeypatch.setattr(server, "_authenticated_username", lambda _host: "jcwalker3") monkeypatch.setattr(server, "_seed_session_context", lambda **_kwargs: None) monkeypatch.setattr( server.session_ctx, "assess_session_context", lambda **_kwargs: {"block": False, "reasons": []}, ) monkeypatch.setattr( server.session_ctx, "assess_identity_match", lambda **_kwargs: {"block": False, "reasons": []}, ) monkeypatch.setattr( server.session_ctx, "profile_allowed_for_remote", lambda *_args, **_kwargs: {"block": False, "reasons": []}, ) monkeypatch.setattr(server.session_ctx, "mutation_context_audit_fields", lambda: {}) monkeypatch.setattr( server.role_session_router, "assess_infra_stop", lambda _root: {"infra_stop": False, "infra_stop_reasons": []}, ) monkeypatch.setattr(server, "_check_mcp_runtimes_diagnostics", lambda *_a: []) monkeypatch.setattr( server, "_assess_stale_active_binding", lambda **_kwargs: {"classification": "unbound"}, ) monkeypatch.setattr(server, "record_mutation_authority", lambda *_args: None) monkeypatch.setattr(server, "init_review_decision_lock", lambda *_a, **_k: None) monkeypatch.setattr(server.capability_stop_terminal, "is_active", lambda: False) monkeypatch.setattr( server.capability_stop_terminal, "sync_from_capability_result", lambda _result: False, ) result = server.gitea_resolve_task_capability(task="review_pr", remote="prgs") assert result["allowed_in_current_session"] is False assert result["required_role_kind"] == "reviewer" assert server._preflight_capability_called is False def test_head_and_foreign_lease_protections_remain_enforced(): now = datetime.now(timezone.utc) head = "a" * 40 moved_head = "b" * 40 body = reviewer_pr_lease.format_lease_body( repo="Scaled-Tech-Consulting/Gitea-Tools", pr_number=762, issue_number=605, reviewer_identity="other-reviewer", profile="prgs-reviewer", session_id="foreign-session", worktree="/repo/branches/review-pr-762", phase="claimed", candidate_head=head, target_branch="master", target_branch_sha="c" * 40, last_activity=now, ) comments = [{"id": 10, "author": "other-reviewer", "body": body}] acquire = reviewer_pr_lease.assess_acquire_lease( comments, pr_number=762, reviewer_identity="sysadmin", profile="prgs-reviewer", session_id="my-session", repo="Scaled-Tech-Consulting/Gitea-Tools", issue_number=605, worktree="/repo/branches/review-pr-762-mine", candidate_head=head, target_branch="master", target_branch_sha="c" * 40, now=now, ) assert acquire["acquire_allowed"] is False reviewer_pr_lease.clear_session_lease() reviewer_pr_lease.record_session_lease( { "pr_number": 762, "session_id": "foreign-session", "candidate_head": head, "comment_id": 10, }, lease_provenance=merger_lease_adoption.build_lease_provenance( source=merger_lease_adoption.SOURCE_ACQUIRE, comment_id=10, ), ) try: gate = reviewer_pr_lease.assess_mutation_lease_gate( pr_number=762, comments=comments, reviewer_identity="other-reviewer", session_id="foreign-session", mutation="approve", live_head_sha=moved_head, pinned_head_sha=head, now=now, ) finally: reviewer_pr_lease.clear_session_lease() assert gate["block"] is True assert any("head changed" in reason for reason in gate["reasons"]) def test_reviewer_lease_role_gate_is_not_weakened(): result = anti_stomp_preflight.assess_anti_stomp_preflight( task="acquire_reviewer_pr_lease", profile_name="prgs-author", profile_role="author", required_role="reviewer", required_permission="gitea.pr.comment", allowed_operations=["gitea.read"], check_repo=False, check_root_checkout=False, check_worktree=False, check_stale_runtime=False, ) assert result["block"] is True assert result["blocker_kind"] == anti_stomp_preflight.BLOCKER_WRONG_ROLE