fix(mcp): forward explicit org/repo into lease/review anti-stomp
_verify_role_mutation_workspace dropped org/repo, so anti-stomp resolved bare remote=prgs to Timesheet and failed closed wrong_repo on Gitea-Tools. Forward explicit org/repo for merger/reviewer lease acquire, adopt, review, merge, and lease release. Add regression for merger acquire forwarding. Fixes #718 Refs #723
This commit is contained in:
+45
-7
@@ -1282,12 +1282,18 @@ def _verify_role_mutation_workspace(
|
||||
worktree_path: str | None = None,
|
||||
worktree: str | None = None,
|
||||
task: str | None = None,
|
||||
org: str | None = None,
|
||||
repo: str | None = None,
|
||||
) -> str:
|
||||
"""Bind reviewer/merger mutations to the active namespace workspace (#510).
|
||||
|
||||
#683: must NOT early-return solely because pytest/unittest is loaded.
|
||||
Production workspace binding always runs; test isolation uses explicit
|
||||
env fixtures / force-on flags, never a production short-circuit here.
|
||||
|
||||
org/repo are forwarded into anti-stomp (#604) so callers that pass explicit
|
||||
repository targets (e.g. prgs + Gitea-Tools when REMOTES defaults to
|
||||
Timesheet) do not fail closed with wrong_repo after a successful resolve.
|
||||
"""
|
||||
|
||||
# Check running runtimes to prevent stale mutations
|
||||
@@ -1350,7 +1356,13 @@ def _verify_role_mutation_workspace(
|
||||
)
|
||||
)
|
||||
resolved = assessment["mutation_workspace"]
|
||||
verify_preflight_purity(remote, worktree_path=resolved, task=task)
|
||||
verify_preflight_purity(
|
||||
remote,
|
||||
worktree_path=resolved,
|
||||
task=task,
|
||||
org=org,
|
||||
repo=repo,
|
||||
)
|
||||
return resolved
|
||||
|
||||
|
||||
@@ -4521,7 +4533,11 @@ def _evaluate_pr_review_submission(
|
||||
) -> dict:
|
||||
"""Shared gate chain for live submit and dry-run review tools."""
|
||||
_verify_role_mutation_workspace(
|
||||
remote, worktree_path=worktree_path, task="review_pr"
|
||||
remote,
|
||||
worktree_path=worktree_path,
|
||||
task="review_pr",
|
||||
org=org,
|
||||
repo=repo,
|
||||
)
|
||||
action = (action or "").strip().lower()
|
||||
workflow_blockers = _review_workflow_load_gate_reasons() if live else []
|
||||
@@ -7619,7 +7635,11 @@ def gitea_merge_pr(
|
||||
available. Never secrets.
|
||||
"""
|
||||
_verify_role_mutation_workspace(
|
||||
remote, worktree_path=worktree_path, task="merge_pr"
|
||||
remote,
|
||||
worktree_path=worktree_path,
|
||||
task="merge_pr",
|
||||
org=org,
|
||||
repo=repo,
|
||||
)
|
||||
allowed = get_profile()["allowed_operations"]
|
||||
forbidden = get_profile()["forbidden_operations"]
|
||||
@@ -10652,8 +10672,14 @@ def gitea_acquire_reviewer_pr_lease(
|
||||
|
||||
# task=acquire_reviewer_pr_lease so verify_preflight_purity runs shared #604
|
||||
# anti-stomp for the declared lease-acquire mutation inventory entry.
|
||||
# Forward explicit org/repo so anti-stomp does not default bare remote=prgs
|
||||
# to Timesheet when the local git remote is Gitea-Tools (#604 wrong_repo).
|
||||
_verify_role_mutation_workspace(
|
||||
remote, worktree=worktree, task="acquire_reviewer_pr_lease"
|
||||
remote,
|
||||
worktree=worktree,
|
||||
task="acquire_reviewer_pr_lease",
|
||||
org=org,
|
||||
repo=repo,
|
||||
)
|
||||
h, o, r = _resolve(remote, host, org, repo)
|
||||
auth = _auth(h)
|
||||
@@ -10794,7 +10820,11 @@ def gitea_acquire_merger_pr_lease(
|
||||
|
||||
try:
|
||||
_verify_role_mutation_workspace(
|
||||
remote, worktree=worktree, task="acquire_merger_pr_lease"
|
||||
remote,
|
||||
worktree=worktree,
|
||||
task="acquire_merger_pr_lease",
|
||||
org=org,
|
||||
repo=repo,
|
||||
)
|
||||
except RuntimeError as e:
|
||||
return {
|
||||
@@ -10987,7 +11017,11 @@ def gitea_adopt_merger_pr_lease(
|
||||
|
||||
# task=adopt_merger_pr_lease so verify_preflight_purity runs shared #604 anti-stomp.
|
||||
_verify_role_mutation_workspace(
|
||||
remote, worktree=worktree, task="adopt_merger_pr_lease"
|
||||
remote,
|
||||
worktree=worktree,
|
||||
task="adopt_merger_pr_lease",
|
||||
org=org,
|
||||
repo=repo,
|
||||
)
|
||||
h, o, r = _resolve(remote, host, org, repo)
|
||||
auth = _auth(h)
|
||||
@@ -11709,7 +11743,11 @@ def gitea_release_reviewer_pr_lease(
|
||||
dict reporting release status.
|
||||
"""
|
||||
_verify_role_mutation_workspace(
|
||||
remote, worktree=worktree, task="review_pr"
|
||||
remote,
|
||||
worktree=worktree,
|
||||
task="review_pr",
|
||||
org=org,
|
||||
repo=repo,
|
||||
)
|
||||
h, o, r = _resolve(remote, host, org, repo)
|
||||
auth = _auth(h)
|
||||
|
||||
@@ -145,6 +145,37 @@ class TestMergerLeaseAcquireTool(unittest.TestCase):
|
||||
self.assertIn("Branches-only mutation guard", res["reasons"][0])
|
||||
self.assertIsNone(leases._SESSION_LEASE)
|
||||
|
||||
@patch("gitea_mcp_server.api_request")
|
||||
@patch("gitea_mcp_server._auth", return_value="token pass")
|
||||
@patch("gitea_mcp_server._resolve", return_value=("gitea.prgs.cc", "Scaled-Tech-Consulting", "Gitea-Tools"))
|
||||
@patch("gitea_mcp_server.get_profile")
|
||||
@patch("gitea_mcp_server._fetch_pr_comments", return_value=[])
|
||||
@patch("gitea_mcp_server._verify_role_mutation_workspace")
|
||||
def test_acquire_merger_forwards_explicit_org_repo_to_workspace_verify(
|
||||
self, mock_verify, _comments, mock_profile, _resolve, _auth, mock_api
|
||||
):
|
||||
"""Explicit org/repo must reach anti-stomp (prgs bare default is Timesheet)."""
|
||||
mock_profile.return_value = self._merger_profile()
|
||||
mock_api.side_effect = [
|
||||
{"state": "open", "head": {"sha": LIVE_HEAD}},
|
||||
{"id": 789},
|
||||
]
|
||||
with patch.dict(os.environ, {"GITEA_MCP_PROFILE_NAME": "prgs-merger"}):
|
||||
res = mcp_server.gitea_acquire_merger_pr_lease(
|
||||
pr_number=718,
|
||||
worktree="branches/issue-718",
|
||||
session_id="merger-session",
|
||||
candidate_head=HEAD,
|
||||
remote="prgs",
|
||||
org="Scaled-Tech-Consulting",
|
||||
repo="Gitea-Tools",
|
||||
)
|
||||
self.assertTrue(res["success"], res)
|
||||
kwargs = mock_verify.call_args.kwargs
|
||||
self.assertEqual(kwargs.get("org"), "Scaled-Tech-Consulting")
|
||||
self.assertEqual(kwargs.get("repo"), "Gitea-Tools")
|
||||
self.assertEqual(kwargs.get("task"), "acquire_merger_pr_lease")
|
||||
|
||||
@patch("gitea_mcp_server.get_profile")
|
||||
def test_acquire_merger_requires_candidate_head(self, mock_profile):
|
||||
mock_profile.return_value = self._merger_profile()
|
||||
|
||||
Reference in New Issue
Block a user