feat(diagnostics): end-to-end tunnel check, stale state detection, MCP extensions

- diagnostics.py: TunnelCheckResult with SSH process liveness, port
  probe, and optional API health check; check_tunnel / check_all_tunnels
- cli.py: bridge status shows LIVE column and [STALE] marker when state
  says connected but PID is dead; bridge check wired to diagnostics
- state.py: read_raw_pid helper; _pid_alive exported for reuse
- capabilities.py: capabilities registry stubs
- mcp_server/server.py: expose check_tunnel and tunnel capabilities
  over MCP
- SCOPE.md: rapid orientation document
- workplans/OPS-WP-0001-diagnostics.md: workplan backing this feature
- tests: 207 passing (test_cli, test_mcp, test_diagnostics)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-21 15:07:47 +01:00
parent bebd542a2e
commit a55c685f89
10 changed files with 773 additions and 8 deletions

View File

@@ -469,6 +469,80 @@ class TestMcpCatalogShowBridge:
assert "error" in data
# ---------------------------------------------------------------------------
# bridge_check
# ---------------------------------------------------------------------------
class TestMcpBridgeCheck:
@pytest.mark.capability("bridge_check")
@pytest.mark.access_mode("mcp")
async def test_bridge_check_tool(self, env_simple):
"""bridge_check returns a list of dicts with 'ok' key."""
from bridge.diagnostics import TunnelCheckResult
mock_result = TunnelCheckResult(
tunnel="test-tunnel",
ssh_process="ok",
pid=12345,
remote_port="listening",
local_api=None,
latency_ms=None,
stale_state=False,
)
with patch("bridge.mcp_server.server.check_all_tunnels", return_value=[mock_result]):
from fastmcp import Client
async with Client(mcp) as c:
result = await c.call_tool("bridge_check", {})
data = _data(result)
assert isinstance(data, list)
assert len(data) == 1
row = data[0]
assert "ok" in row
assert row["ok"] is True
assert row["tunnel"] == "test-tunnel"
assert row["ssh_process"] == "ok"
assert row["remote_port"] == "listening"
async def test_bridge_check_specific_tunnel(self, env_simple):
"""bridge_check with tunnel arg calls check_tunnel for that tunnel."""
from bridge.diagnostics import TunnelCheckResult
mock_result = TunnelCheckResult(
tunnel="test-tunnel",
ssh_process="dead",
pid=None,
remote_port="closed",
local_api=None,
latency_ms=None,
stale_state=True,
)
with patch("bridge.mcp_server.server.check_tunnel", return_value=mock_result):
from fastmcp import Client
async with Client(mcp) as c:
result = await c.call_tool("bridge_check", {"tunnel": "test-tunnel"})
data = _data(result)
assert isinstance(data, list)
assert data[0]["ok"] is False
assert data[0]["stale_state"] is True
async def test_bridge_check_unknown_tunnel(self, env_simple):
"""bridge_check with unknown tunnel returns error dict."""
from fastmcp import Client
async with Client(mcp) as c:
result = await c.call_tool("bridge_check", {"tunnel": "nonexistent"})
data = _data(result)
assert isinstance(data, list)
assert "error" in data[0]
async def test_bridge_check_bad_config(self, tmp_path, monkeypatch):
"""bridge_check with bad config returns error dict."""
monkeypatch.setenv("BRIDGE_CONFIG", str(tmp_path / "nonexistent.yaml"))
from fastmcp import Client
async with Client(mcp) as c:
result = await c.call_tool("bridge_check", {})
data = _data(result)
assert isinstance(data, list)
assert "error" in data[0]
# ---------------------------------------------------------------------------
# Resources
# ---------------------------------------------------------------------------