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_path: str | None = None,
|
||||||
worktree: str | None = None,
|
worktree: str | None = None,
|
||||||
task: str | None = None,
|
task: str | None = None,
|
||||||
|
org: str | None = None,
|
||||||
|
repo: str | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Bind reviewer/merger mutations to the active namespace workspace (#510).
|
"""Bind reviewer/merger mutations to the active namespace workspace (#510).
|
||||||
|
|
||||||
#683: must NOT early-return solely because pytest/unittest is loaded.
|
#683: must NOT early-return solely because pytest/unittest is loaded.
|
||||||
Production workspace binding always runs; test isolation uses explicit
|
Production workspace binding always runs; test isolation uses explicit
|
||||||
env fixtures / force-on flags, never a production short-circuit here.
|
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
|
# Check running runtimes to prevent stale mutations
|
||||||
@@ -1350,7 +1356,13 @@ def _verify_role_mutation_workspace(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
resolved = assessment["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
|
return resolved
|
||||||
|
|
||||||
|
|
||||||
@@ -4521,7 +4533,11 @@ def _evaluate_pr_review_submission(
|
|||||||
) -> dict:
|
) -> dict:
|
||||||
"""Shared gate chain for live submit and dry-run review tools."""
|
"""Shared gate chain for live submit and dry-run review tools."""
|
||||||
_verify_role_mutation_workspace(
|
_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()
|
action = (action or "").strip().lower()
|
||||||
workflow_blockers = _review_workflow_load_gate_reasons() if live else []
|
workflow_blockers = _review_workflow_load_gate_reasons() if live else []
|
||||||
@@ -7619,7 +7635,11 @@ def gitea_merge_pr(
|
|||||||
available. Never secrets.
|
available. Never secrets.
|
||||||
"""
|
"""
|
||||||
_verify_role_mutation_workspace(
|
_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"]
|
allowed = get_profile()["allowed_operations"]
|
||||||
forbidden = get_profile()["forbidden_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
|
# task=acquire_reviewer_pr_lease so verify_preflight_purity runs shared #604
|
||||||
# anti-stomp for the declared lease-acquire mutation inventory entry.
|
# 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(
|
_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)
|
h, o, r = _resolve(remote, host, org, repo)
|
||||||
auth = _auth(h)
|
auth = _auth(h)
|
||||||
@@ -10794,7 +10820,11 @@ def gitea_acquire_merger_pr_lease(
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
_verify_role_mutation_workspace(
|
_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:
|
except RuntimeError as e:
|
||||||
return {
|
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.
|
# task=adopt_merger_pr_lease so verify_preflight_purity runs shared #604 anti-stomp.
|
||||||
_verify_role_mutation_workspace(
|
_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)
|
h, o, r = _resolve(remote, host, org, repo)
|
||||||
auth = _auth(h)
|
auth = _auth(h)
|
||||||
@@ -11709,7 +11743,11 @@ def gitea_release_reviewer_pr_lease(
|
|||||||
dict reporting release status.
|
dict reporting release status.
|
||||||
"""
|
"""
|
||||||
_verify_role_mutation_workspace(
|
_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)
|
h, o, r = _resolve(remote, host, org, repo)
|
||||||
auth = _auth(h)
|
auth = _auth(h)
|
||||||
|
|||||||
@@ -145,6 +145,37 @@ class TestMergerLeaseAcquireTool(unittest.TestCase):
|
|||||||
self.assertIn("Branches-only mutation guard", res["reasons"][0])
|
self.assertIn("Branches-only mutation guard", res["reasons"][0])
|
||||||
self.assertIsNone(leases._SESSION_LEASE)
|
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")
|
@patch("gitea_mcp_server.get_profile")
|
||||||
def test_acquire_merger_requires_candidate_head(self, mock_profile):
|
def test_acquire_merger_requires_candidate_head(self, mock_profile):
|
||||||
mock_profile.return_value = self._merger_profile()
|
mock_profile.return_value = self._merger_profile()
|
||||||
|
|||||||
Reference in New Issue
Block a user