fix(mcp): separate not-connected from Connected-but-unattached (review 637 B2)

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]>
This commit is contained in:
2026-07-28 17:08:44 -05:00
co-authored by Claude Opus 4.8
parent 126d76ad28
commit ca5f078d8a
4 changed files with 473 additions and 39 deletions
@@ -4,11 +4,16 @@ 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
@@ -19,11 +24,14 @@ def test_assess_connected_namespace_attachment_success():
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