feat: gate reviewer mutations on canonical workflow load proof (Closes #389)
Add gitea_load_review_workflow and in-process session proof required before gitea_submit_pr_review, gitea_mark_final_review_decision, and gitea_merge_pr. Capability and runtime context report workflow-load status; stale or missing proof fails closed with recovery handoff text that forbids approve/merge replay.
This commit is contained in:
@@ -55,6 +55,12 @@ _NO_BLOCKER_FEEDBACK = {
|
||||
}
|
||||
|
||||
|
||||
def _init_reviewer_session(remote="prgs"):
|
||||
"""Seed review decision lock and required workflow-load proof (#389)."""
|
||||
init_review_decision_lock(remote, "review_pr")
|
||||
mcp_server.gitea_load_review_workflow()
|
||||
|
||||
|
||||
def _mark_request_changes_ready(pr_number=8, **kwargs):
|
||||
"""Mark a request_changes decision ready with the #332 duplicate-
|
||||
suppression feedback fetch stubbed to 'no existing blocker'."""
|
||||
@@ -539,6 +545,9 @@ class TestViewPR(unittest.TestCase):
|
||||
class TestMergePR(unittest.TestCase):
|
||||
"""Gated merge workflow (#16). gitea_merge_pr is the only merge path."""
|
||||
|
||||
def setUp(self):
|
||||
mcp_server.gitea_load_review_workflow()
|
||||
|
||||
def _pr(self, author, state="open", sha="abc123", mergeable=True):
|
||||
return {
|
||||
"user": {"login": author},
|
||||
@@ -1011,8 +1020,7 @@ class TestReviewPR(unittest.TestCase):
|
||||
{"login": "jcwalker3"}, # /api/v1/user (submit eligibility)
|
||||
{"user": {"login": "jcwalker3"}, "state": "open", "head": {"sha": "abc1234"}, "mergeable": True}, # /pulls/1
|
||||
]
|
||||
from mcp_server import init_review_decision_lock
|
||||
init_review_decision_lock("prgs", "review_pr")
|
||||
_init_reviewer_session("prgs")
|
||||
gitea_mark_final_review_decision(1, "approve", remote="prgs")
|
||||
result = gitea_review_pr(
|
||||
pr_number=1,
|
||||
@@ -1680,7 +1688,7 @@ class TestReviewDecisionValidationGate(unittest.TestCase):
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
init_review_decision_lock("prgs", "review_pr")
|
||||
_init_reviewer_session("prgs")
|
||||
|
||||
def _env(self):
|
||||
return patch.dict(os.environ, {
|
||||
@@ -1775,7 +1783,7 @@ class TestSubmitPrReview(unittest.TestCase):
|
||||
"""Gated review-mutation tool (#15)."""
|
||||
|
||||
def setUp(self):
|
||||
init_review_decision_lock("prgs", "review_pr")
|
||||
_init_reviewer_session("prgs")
|
||||
gitea_mark_final_review_decision(8, "approve", remote="prgs")
|
||||
|
||||
def _pr(self, author, state="open", sha="abc123", mergeable=True):
|
||||
@@ -2168,7 +2176,7 @@ class TestSubmitPrReview(unittest.TestCase):
|
||||
os.remove(spoof_path)
|
||||
|
||||
def test_mark_final_decision_rejects_remote_mismatch(self):
|
||||
init_review_decision_lock("prgs", "review_pr")
|
||||
_init_reviewer_session("prgs")
|
||||
r = gitea_mark_final_review_decision(8, "approve", remote="dadeschools")
|
||||
self.assertFalse(r["marked_ready"])
|
||||
self.assertTrue(any("does not match locked remote" in x for x in r["reasons"]))
|
||||
@@ -2250,6 +2258,7 @@ if __name__ == "__main__":
|
||||
class TestTrackerHygieneCleanup(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
mcp_server.gitea_load_review_workflow()
|
||||
self.mock_api = patch("mcp_server.api_request").start()
|
||||
self.mock_auth = patch("mcp_server.get_auth_header", return_value=FAKE_AUTH).start()
|
||||
patch("gitea_audit.audit_enabled", return_value=True).start()
|
||||
|
||||
Reference in New Issue
Block a user