- Distinguish client_namespace vs offline_spawn probe sources; only IDE client probes prove namespace health and feed mutation gates. - Record assessments in session; gate gitea_submit_pr_review and gitea_merge_pr when client-namespace health is unhealthy/non-proven. - Mark test_mcp_conn.py offline-only; align recovery docs to client reconnect (no PID-kill/config-touch as canonical recovery). - Rebase onto current master so #590 ledger isolation keeps TestMergePR.test_unknown_profile_blocks green.
This commit is contained in:
@@ -66,6 +66,7 @@ def _reset_mutation_authority(monkeypatch):
|
||||
monkeypatch.setattr(mcp_server, "_MUTATION_AUTHORITY", None)
|
||||
monkeypatch.setattr(mcp_server, "_IDENTITY_CACHE", {})
|
||||
monkeypatch.setattr(mcp_server, "_REVIEW_DECISION_LOCK", None)
|
||||
monkeypatch.setattr(mcp_server, "_LIVE_NAMESPACE_HEALTH", {})
|
||||
monkeypatch.setattr(mcp_server, "_preflight_whoami_called", False)
|
||||
monkeypatch.setattr(mcp_server, "_preflight_capability_called", False)
|
||||
monkeypatch.setattr(mcp_server, "_preflight_resolved_role", None)
|
||||
|
||||
@@ -6,6 +6,9 @@ import review_merge_state_machine as rmsm
|
||||
|
||||
|
||||
class TestMcpNamespaceHealth(unittest.TestCase):
|
||||
def setUp(self):
|
||||
gitea_mcp_server._LIVE_NAMESPACE_HEALTH.clear()
|
||||
|
||||
def test_registered_tool_but_live_eof_blocks_merge(self):
|
||||
result = mcp_namespace_health.classify_namespace_probe(
|
||||
"gitea-reviewer",
|
||||
@@ -24,6 +27,7 @@ class TestMcpNamespaceHealth(unittest.TestCase):
|
||||
},
|
||||
},
|
||||
config_path="/Users/jasonwalker/.gemini/config/mcp_config.json",
|
||||
probe_source="client_namespace",
|
||||
)
|
||||
|
||||
self.assertFalse(result["healthy"])
|
||||
@@ -31,6 +35,8 @@ class TestMcpNamespaceHealth(unittest.TestCase):
|
||||
self.assertTrue(result["required_tool_registered"])
|
||||
self.assertFalse(result["required_tool_callable"])
|
||||
self.assertTrue(result["blocks_merge_workflow"])
|
||||
self.assertFalse(result["ide_namespace_proven"])
|
||||
self.assertEqual(result["probe_source"], "client_namespace")
|
||||
self.assertEqual(result["diagnostics"]["process_pid"], 4321)
|
||||
self.assertEqual(result["diagnostics"]["profile"], "prgs-reviewer")
|
||||
self.assertEqual(
|
||||
@@ -41,26 +47,44 @@ class TestMcpNamespaceHealth(unittest.TestCase):
|
||||
self.assertIn("gitea-reviewer", result["reasons"][0])
|
||||
self.assertIn("gitea_whoami", result["reasons"][0])
|
||||
|
||||
def test_successful_live_invocation_is_healthy(self):
|
||||
def test_successful_client_namespace_invocation_is_ide_proven(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",
|
||||
probe_source="client_namespace",
|
||||
)
|
||||
|
||||
self.assertTrue(result["healthy"])
|
||||
self.assertTrue(result["required_tool_registered"])
|
||||
self.assertTrue(result["required_tool_callable"])
|
||||
self.assertTrue(result["ide_namespace_proven"])
|
||||
self.assertFalse(result["blocks_merge_workflow"])
|
||||
self.assertIsNone(result["error_type"])
|
||||
|
||||
def test_offline_spawn_success_is_not_ide_proof(self):
|
||||
result = mcp_namespace_health.classify_namespace_probe(
|
||||
"gitea-merger",
|
||||
registered_tools=["gitea_whoami"],
|
||||
probe_result={"success": True, "result": {}},
|
||||
process={"pid": 99, "profile": "prgs-merger"},
|
||||
probe_source="offline_spawn",
|
||||
)
|
||||
self.assertTrue(result["healthy"])
|
||||
self.assertFalse(result["ide_namespace_proven"])
|
||||
self.assertFalse(result["blocks_merge_workflow"])
|
||||
self.assertTrue(
|
||||
any("offline_spawn" in r for r in result["reasons"])
|
||||
)
|
||||
|
||||
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": {}},
|
||||
probe_source="client_namespace",
|
||||
)
|
||||
|
||||
self.assertFalse(result["healthy"])
|
||||
@@ -68,23 +92,31 @@ class TestMcpNamespaceHealth(unittest.TestCase):
|
||||
self.assertFalse(result["required_tool_registered"])
|
||||
self.assertEqual(result["error_type"], "tool_missing")
|
||||
|
||||
def test_server_tool_exposes_same_assessment(self):
|
||||
def test_server_tool_exposes_same_assessment_and_records_session(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"},
|
||||
probe_source="client_namespace",
|
||||
)
|
||||
|
||||
self.assertFalse(result["success"])
|
||||
self.assertEqual(result["error_type"], "namespace_eof")
|
||||
self.assertEqual(result["namespace"], "gitea-merger")
|
||||
self.assertEqual(result["diagnostics"]["process_pid"], 9876)
|
||||
self.assertIn("gitea-merger", gitea_mcp_server._LIVE_NAMESPACE_HEALTH)
|
||||
gate = gitea_mcp_server._live_namespace_health_gate("merge_pr")
|
||||
self.assertTrue(gate)
|
||||
self.assertTrue(any("gitea-merger" in r for r in gate))
|
||||
|
||||
|
||||
class TestLiveNamespaceBlocksMerge(unittest.TestCase):
|
||||
"""AC5: a broken live namespace must hard-block the review/merge state machine."""
|
||||
|
||||
def setUp(self):
|
||||
gitea_mcp_server._LIVE_NAMESPACE_HEALTH.clear()
|
||||
|
||||
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).
|
||||
@@ -141,6 +173,7 @@ class TestLiveNamespaceBlocksMerge(unittest.TestCase):
|
||||
registered_tools=["gitea_whoami", "gitea_adopt_merger_pr_lease"],
|
||||
probe_result={"success": False, "error": "client is closing: EOF"},
|
||||
process={"pid": 555, "profile": "prgs-merger"},
|
||||
probe_source="client_namespace",
|
||||
)
|
||||
self.assertTrue(verdict["blocks_merge_workflow"])
|
||||
blocked = rmsm.can_merge(
|
||||
@@ -150,6 +183,26 @@ class TestLiveNamespaceBlocksMerge(unittest.TestCase):
|
||||
)
|
||||
self.assertFalse(blocked["allowed"])
|
||||
|
||||
def test_offline_spawn_does_not_authorize_mutation_gate(self):
|
||||
gitea_mcp_server.gitea_assess_mcp_namespace_health(
|
||||
"gitea-merger",
|
||||
registered_tools=["gitea_whoami"],
|
||||
probe_result={"success": True, "result": {}},
|
||||
probe_source="offline_spawn",
|
||||
)
|
||||
gate = gitea_mcp_server._live_namespace_health_gate("merge_pr")
|
||||
self.assertTrue(gate)
|
||||
self.assertTrue(any("offline_spawn" in r or "client_namespace" in r for r in gate))
|
||||
|
||||
def test_client_namespace_healthy_clears_mutation_gate(self):
|
||||
gitea_mcp_server.gitea_assess_mcp_namespace_health(
|
||||
"gitea-merger",
|
||||
registered_tools=["gitea_whoami"],
|
||||
probe_result={"success": True, "result": {}},
|
||||
probe_source="client_namespace",
|
||||
)
|
||||
self.assertEqual(gitea_mcp_server._live_namespace_health_gate("merge_pr"), [])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user