A required namespace absent from the connected-service inventory was never counted as missing, because missing_namespaces was populated only while walking connected_servers. The session therefore reported attachment_healthy true, discovery_status namespaces_attached and error_type None while holding no attachment proof for that namespace, and the gate text told the operator "the host reports Connected" about a service nothing had reported Connected. assess_connected_namespace_attachment now classifies the two conditions apart. missing_namespaces keeps its meaning: required, Connected at the host, absent from the session tool surface -- the actual #708 defect, still typed mcp_connected_namespaces_missing. Required namespaces absent from the connected inventory land in the new not_connected_namespaces list, typed mcp_required_namespaces_not_connected, so a caller is never pointed at an attachment recovery for a service that never connected. attachment_healthy is false whenever any required namespace lacks attachment proof under either condition, error_types reports every condition present so neither hides the other, and namespace_conditions carries the per-namespace connected/attached verdict. Evidence that disagrees with itself -- attached in the session yet absent from the connected inventory -- is reported as contradictory and fails closed rather than being read as proof. attachment_gate_from_session states only what the recorded evidence supports: the Connected wording appears solely when connected is true, the not-connected wording when it is false, and a neutral refusal when connected status was never recorded. Review and merge stay fail-closed in all three cases. _record_live_namespace_attachment consumes the per-namespace verdicts instead of pinning the session-wide error_type onto every unattached namespace and instead of deriving health from a missing list that tracked only one of the two conditions. The two existing cases in test_issue_708_mcp_namespace_attachment.py declared no required_namespaces, so they inherited a default set containing a namespace their connected list omitted. Under the corrected rule that is a false-healthy assertion, so each now declares the required set it actually means. Closes #708 Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
78 lines
3.3 KiB
Python
78 lines
3.3 KiB
Python
"""Unit regression tests for Issue #708: Connected-but-namespaces-missing detection and attachment safety."""
|
|
|
|
import mcp_namespace_health
|
|
|
|
|
|
def test_assess_connected_namespace_attachment_success():
|
|
# required_namespaces is declared explicitly: these three are the namespaces this case
|
|
# is about. Relying on the default (which also requires gitea-tools) would ask for a
|
|
# healthy verdict covering a required namespace that was never connected — exactly the
|
|
# false-healthy classification these tests now forbid.
|
|
connected = ["gitea-author", "gitea-reviewer", "gitea-merger"]
|
|
attached = ["gitea-author", "gitea-reviewer", "gitea-merger"]
|
|
res = mcp_namespace_health.assess_connected_namespace_attachment(
|
|
connected_servers=connected,
|
|
attached_session_namespaces=attached,
|
|
required_namespaces=connected,
|
|
)
|
|
assert res["success"] is True
|
|
assert res["attachment_healthy"] is True
|
|
assert res["discovery_status"] == "namespaces_attached"
|
|
assert res["error_type"] is None
|
|
assert res["missing_namespaces"] == []
|
|
assert res["exact_next_action"] == "None; session tool namespaces attached."
|
|
|
|
|
|
def test_assess_connected_namespace_attachment_missing():
|
|
# Every required namespace here *is* connected, so the only condition present is the
|
|
# #708 one: Connected at the host, absent from the session tool surface.
|
|
connected = ["gitea-author", "gitea-reviewer", "gitea-merger"]
|
|
attached = ["gitea-author"]
|
|
res = mcp_namespace_health.assess_connected_namespace_attachment(
|
|
connected_servers=connected,
|
|
attached_session_namespaces=attached,
|
|
required_namespaces=connected,
|
|
)
|
|
assert res["success"] is False
|
|
assert res["attachment_healthy"] is False
|
|
assert res["discovery_status"] == "connected_but_namespaces_missing"
|
|
assert res["error_type"] == "mcp_connected_namespaces_missing"
|
|
assert "gitea-reviewer" in res["missing_namespaces"]
|
|
assert "gitea-merger" in res["missing_namespaces"]
|
|
assert "Reconnect the IDE/client MCP session" in res["exact_next_action"]
|
|
|
|
|
|
def test_assess_connected_namespace_attachment_disconnected():
|
|
res = mcp_namespace_health.assess_connected_namespace_attachment(
|
|
connected_servers=[],
|
|
attached_session_namespaces=[],
|
|
)
|
|
assert res["success"] is False
|
|
assert res["attachment_healthy"] is False
|
|
assert res["discovery_status"] == "disconnected"
|
|
|
|
|
|
def test_proof_of_connected_vs_attached_mapping():
|
|
connected = ["gitea-author", "gitea-reviewer"]
|
|
attached = ["gitea-author"]
|
|
res = mcp_namespace_health.assess_connected_namespace_attachment(
|
|
connected_servers=connected,
|
|
attached_session_namespaces=attached,
|
|
)
|
|
proof = res["proof_of_connected_vs_attached"]
|
|
assert proof["gitea-author"] == {"connected": True, "attached": True}
|
|
assert proof["gitea-reviewer"] == {"connected": True, "attached": False}
|
|
|
|
|
|
def test_unsafe_fallback_policy_enforcement():
|
|
res = mcp_namespace_health.assess_connected_namespace_attachment(
|
|
connected_servers=["gitea-author"],
|
|
attached_session_namespaces=[],
|
|
)
|
|
policy = res["unsafe_fallback_policy"]
|
|
assert "Workflow Safety Hard Stop (#708)" in policy
|
|
assert "direct imports" in policy
|
|
assert "API mutations" in policy
|
|
assert "profile hopping" in policy
|
|
assert "session-state overrides" in policy
|