feat: add root checkout guard for contaminated control checkout (Closes #475)

Introduce root_checkout_guard helper and enforce it in verify_preflight_purity
so author, reviewer, and merger mutations fail closed when the stable control
checkout is on the wrong branch, detached, dirty, or diverged from prgs/master.
Isolated branches/... worktrees and reconciler paths remain allowed.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
2026-07-08 02:23:27 -04:00
co-authored by Claude Opus 4.8
parent 7af73e1539
commit be592d6d4d
6 changed files with 382 additions and 4 deletions
+12 -2
View File
@@ -38,8 +38,18 @@ class TestCreateIssueWorkspaceGuard(unittest.TestCase):
@patch("gitea_mcp_server.role_session_router.check_author_mutation_after_reviewer_stop", return_value=(True, []))
@patch("gitea_mcp_server.api_request")
@patch("gitea_mcp_server.api_get_all", return_value=[])
@patch("gitea_mcp_server.issue_lock_worktree.read_worktree_git_state", return_value={"current_branch": "feat/issue-1"})
def test_create_issue_stable_checkout_rejected(self, _git, _get_all, mock_api, _role, _ns, _prof, _auth):
@patch("gitea_mcp_server.root_checkout_guard.resolve_remote_master_sha", return_value="a" * 40)
@patch(
"gitea_mcp_server.issue_lock_worktree.read_worktree_git_state",
return_value={
"current_branch": "master",
"head_sha": "a" * 40,
"porcelain_status": "",
},
)
def test_create_issue_stable_checkout_rejected(
self, _git, _remote_sha, _get_all, mock_api, _role, _ns, _prof, _auth,
):
# Without worktree_path/env hints, workspace resolves to PROJECT_ROOT. When that
# path is the stable control checkout (not under branches/), mutation must fail.
with patch.object(srv, "PROJECT_ROOT", CONTROL_CHECKOUT_ROOT):