The namespace health check (05fdcee) classified a false-ready namespace but
only returned an advisory blocks_merge_workflow flag; nothing in the merge
gate consumed it. Wire it in so a broken live namespace hard-blocks merge.
- review_merge_state_machine.assess_workflow_blockers: add live_namespace_broken
blocker (registered-in-FastMCP but not callable-through-namespace).
- assess_state_advancement: forward **blocker_kwargs so can_approve/can_merge/
workflow_status honor the full blocker set (also fixes latent drop of
mcp_reconnect_failed/stale_capability_state through those paths).
- gitea_assess_review_merge_state_machine tool: accept live_namespace_broken and
thread it through all state-machine calls.
- Tests: prove can_merge/workflow_status/tool block on live_namespace_broken even
with every review state + pre-merge gate satisfied; bridge classify verdict.
- Docs: enforcement section wiring blocks_merge_workflow -> live_namespace_broken.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
156 lines
6.4 KiB
Python
156 lines
6.4 KiB
Python
import unittest
|
|
|
|
import gitea_mcp_server
|
|
import mcp_namespace_health
|
|
import review_merge_state_machine as rmsm
|
|
|
|
|
|
class TestMcpNamespaceHealth(unittest.TestCase):
|
|
def test_registered_tool_but_live_eof_blocks_merge(self):
|
|
result = mcp_namespace_health.classify_namespace_probe(
|
|
"gitea-reviewer",
|
|
required_tool="gitea_whoami",
|
|
registered_tools=["gitea_whoami", "gitea_list_profiles"],
|
|
probe_result={
|
|
"success": False,
|
|
"error": "client is closing: EOF",
|
|
},
|
|
process={
|
|
"pid": 4321,
|
|
"profile": "prgs-reviewer",
|
|
"env": {
|
|
"GITEA_MCP_PROFILE": "prgs-reviewer",
|
|
"GITEA_TOKEN": "must-not-leak",
|
|
},
|
|
},
|
|
config_path="/Users/jasonwalker/.gemini/config/mcp_config.json",
|
|
)
|
|
|
|
self.assertFalse(result["healthy"])
|
|
self.assertEqual(result["error_type"], "namespace_eof")
|
|
self.assertTrue(result["required_tool_registered"])
|
|
self.assertFalse(result["required_tool_callable"])
|
|
self.assertTrue(result["blocks_merge_workflow"])
|
|
self.assertEqual(result["diagnostics"]["process_pid"], 4321)
|
|
self.assertEqual(result["diagnostics"]["profile"], "prgs-reviewer")
|
|
self.assertEqual(
|
|
result["diagnostics"]["env"]["GITEA_MCP_PROFILE"],
|
|
"prgs-reviewer",
|
|
)
|
|
self.assertNotIn("GITEA_TOKEN", result["diagnostics"]["env"])
|
|
self.assertIn("gitea-reviewer", result["reasons"][0])
|
|
self.assertIn("gitea_whoami", result["reasons"][0])
|
|
|
|
def test_successful_live_invocation_is_healthy(self):
|
|
result = mcp_namespace_health.classify_namespace_probe(
|
|
"gitea-author",
|
|
registered_tools=["gitea_whoami"],
|
|
probe_result={"success": True, "result": {"authenticated": True}},
|
|
process={"pid": 1234, "profile": "prgs-author"},
|
|
config_path="/tmp/mcp_config.json",
|
|
)
|
|
|
|
self.assertTrue(result["healthy"])
|
|
self.assertTrue(result["required_tool_registered"])
|
|
self.assertTrue(result["required_tool_callable"])
|
|
self.assertFalse(result["blocks_merge_workflow"])
|
|
self.assertIsNone(result["error_type"])
|
|
|
|
def test_registered_missing_required_tool_fails_before_probe_success(self):
|
|
result = mcp_namespace_health.classify_namespace_probe(
|
|
"gitea-tools",
|
|
registered_tools=["gitea_whoami"],
|
|
probe_result={"success": True, "result": {}},
|
|
)
|
|
|
|
self.assertFalse(result["healthy"])
|
|
self.assertEqual(result["required_tool"], "gitea_list_profiles")
|
|
self.assertFalse(result["required_tool_registered"])
|
|
self.assertEqual(result["error_type"], "tool_missing")
|
|
|
|
def test_server_tool_exposes_same_assessment(self):
|
|
result = gitea_mcp_server.gitea_assess_mcp_namespace_health(
|
|
"gitea-merger",
|
|
registered_tools=["gitea_whoami"],
|
|
probe_result={"success": False, "error": "transport closed"},
|
|
process={"pid": 9876, "profile": "prgs-merger"},
|
|
)
|
|
|
|
self.assertFalse(result["success"])
|
|
self.assertEqual(result["error_type"], "namespace_eof")
|
|
self.assertEqual(result["namespace"], "gitea-merger")
|
|
self.assertEqual(result["diagnostics"]["process_pid"], 9876)
|
|
|
|
|
|
class TestLiveNamespaceBlocksMerge(unittest.TestCase):
|
|
"""AC5: a broken live namespace must hard-block the review/merge state machine."""
|
|
|
|
def _merge_ready_completion(self):
|
|
# Completion through PRE_MERGE_RECHECK is the state where a clean merge
|
|
# is otherwise allowed (see test_merge_allowed_with_pre_merge_gates).
|
|
idx = rmsm.REVIEW_MERGE_STATES.index("PRE_MERGE_RECHECK")
|
|
return {state: True for state in rmsm.REVIEW_MERGE_STATES[: idx + 1]}
|
|
|
|
def _all_gates(self):
|
|
return {gate: True for gate in rmsm._PRE_MERGE_REQUIRED_GATES}
|
|
|
|
def test_assess_workflow_blockers_flags_live_namespace_broken(self):
|
|
clean = rmsm.assess_workflow_blockers()
|
|
self.assertFalse(clean["block"])
|
|
|
|
broken = rmsm.assess_workflow_blockers(live_namespace_broken=True)
|
|
self.assertTrue(broken["block"])
|
|
self.assertTrue(any("namespace" in r.lower() for r in broken["reasons"]))
|
|
|
|
def test_can_merge_blocks_even_when_all_gates_pass(self):
|
|
# Without the namespace blocker a fully-complete workflow can merge...
|
|
allowed = rmsm.can_merge(
|
|
self._merge_ready_completion(), pre_merge_gates=self._all_gates()
|
|
)
|
|
self.assertTrue(allowed["allowed"])
|
|
|
|
# ...but a broken live namespace overrides every satisfied gate.
|
|
blocked = rmsm.can_merge(
|
|
self._merge_ready_completion(),
|
|
pre_merge_gates=self._all_gates(),
|
|
live_namespace_broken=True,
|
|
)
|
|
self.assertTrue(blocked["block"])
|
|
self.assertFalse(blocked["allowed"])
|
|
|
|
def test_workflow_status_forwards_namespace_blocker(self):
|
|
status = rmsm.workflow_status(
|
|
self._merge_ready_completion(), live_namespace_broken=True
|
|
)
|
|
self.assertFalse(status["merge_allowed"])
|
|
self.assertFalse(status["approve_allowed"])
|
|
|
|
def test_server_tool_blocks_merge_on_live_namespace_broken(self):
|
|
result = gitea_mcp_server.gitea_assess_review_merge_state_machine(
|
|
state_completion=self._merge_ready_completion(),
|
|
pre_merge_gates=self._all_gates(),
|
|
live_namespace_broken=True,
|
|
)
|
|
self.assertTrue(result["blockers"]["block"])
|
|
self.assertFalse(result["merge"]["allowed"])
|
|
|
|
def test_classify_verdict_bridges_into_merge_block(self):
|
|
# The classify verdict is the intended feed for live_namespace_broken.
|
|
verdict = mcp_namespace_health.classify_namespace_probe(
|
|
"gitea-merger",
|
|
registered_tools=["gitea_whoami", "gitea_adopt_merger_pr_lease"],
|
|
probe_result={"success": False, "error": "client is closing: EOF"},
|
|
process={"pid": 555, "profile": "prgs-merger"},
|
|
)
|
|
self.assertTrue(verdict["blocks_merge_workflow"])
|
|
blocked = rmsm.can_merge(
|
|
self._merge_ready_completion(),
|
|
pre_merge_gates=self._all_gates(),
|
|
live_namespace_broken=verdict["blocks_merge_workflow"],
|
|
)
|
|
self.assertFalse(blocked["allowed"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|