mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 02:54:26 +00:00
The contract check ran as an Ansible task in the middle of every provider deploy, and the two client checks ran the same way. A test that is part of the deploy cannot be run without deploying, cannot be re-run on its own, and reports its verdict as a deploy failure. Twenty-seven task files lose 491 lines to this.
It is now what it always was. Each provider ships files/test/test.sh, templates/test.env.j2 and meta/tests.yml, and test-e2e-cli stages one shared harness beside it: run.sh drives contract.sh in a throwaway container on the provider's own network, then isolation.sh for adapter-fronted providers. The read probe each provider offers moved from the calling task into meta/mcp.yml next to the allowlist it must satisfy, and the schema now rejects a read probe naming a tool outside that allowlist.
The bearer the test presents is the one a client resolves through mcp.credential, via the new mcp_client_bearer lookup over resolve_credential, which moved to utils.roles.applications.mcp so it is the same function mcp_servers renders every client connection with. The probe presented whatever the calling role passed, which for the fifteen adapter-fronted providers is a different secret than any client holds.
Sidecar isolation is read from the engine rather than measured inside the container, so it no longer needs a delegation to the node holding the task: attachment is compared by network name, which drops the gwbridge special case, and a swarm worker says it cannot measure instead of skipping silently.
Retrying is bounded by wall clock against MCP_DEADLINE_SECONDS, which scales through the same lookup('timeout') as the harness budget it has to stay under. A flag that is neither true nor false aborts rather than reading as off.
mcp-provider-probe becomes mcp-provider-cli-test: an enabled provider must ship the test rather than include the task. mcp-probe-bearer becomes mcp-adapter-bearer, tying ADAPTER_BEARER to the declared credential key now that no probe include carries it. A new integration test renders the shared env template for every provider through Ansible's own templar, so a later change to it breaks visibly instead of poisoning twenty deploys quietly.
Hermes and OpenClaw keep their client-side proof as CLI tests of their own.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
63 lines
2.6 KiB
Bash
63 lines
2.6 KiB
Bash
#!/usr/bin/env bash
|
|
# Prove one role's MCP adapter sidecar is attached to exactly the networks it may.
|
|
#
|
|
# The attachment set is read from the engine, never by probing a peer's name: an
|
|
# unknown name does not fail to resolve in this environment, it falls through to
|
|
# a wildcard resolver that answers every name with one reachable address, so a
|
|
# name-based probe reports isolation broken for every provider.
|
|
#
|
|
# Swarm service introspection needs a manager. On a worker this reports that it
|
|
# cannot measure and returns success, because the manager in the same round does
|
|
# measure; a silent skip here would be indistinguishable from a pass.
|
|
#
|
|
# Env (rendered into test.env from test-e2e-cli/templates/mcp/test.env.j2):
|
|
# MCP_SIDECAR_SERVICE swarm service name, or compose container name
|
|
# MCP_EXPECTED_NETWORKS comma-separated networks the sidecar may hold
|
|
# MCP_DEPLOYMENT_MODE compose | swarm
|
|
set -euo pipefail
|
|
|
|
: "${MCP_SIDECAR_SERVICE:?}"
|
|
: "${MCP_EXPECTED_NETWORKS:?}"
|
|
: "${MCP_DEPLOYMENT_MODE:?}"
|
|
|
|
attached_swarm() {
|
|
local id
|
|
for id in $(container service inspect "${MCP_SIDECAR_SERVICE}" \
|
|
--format '{{range .Endpoint.VirtualIPs}}{{.NetworkID}} {{end}}'); do
|
|
container network inspect "${id}" --format '{{.Name}}'
|
|
done
|
|
}
|
|
|
|
attached_compose() {
|
|
container inspect --type container "${MCP_SIDECAR_SERVICE}" --format '{{json .NetworkSettings.Networks}}' |
|
|
python3 -c 'import json,sys; print("\n".join(json.load(sys.stdin)))'
|
|
}
|
|
|
|
if [[ "${MCP_DEPLOYMENT_MODE}" == "swarm" ]]; then
|
|
if ! container node ls >/dev/null 2>&1; then
|
|
echo "SKIP: not a swarm manager, the manager of this round measures instead"
|
|
exit 0
|
|
fi
|
|
attached="$(attached_swarm)"
|
|
else
|
|
attached="$(attached_compose)"
|
|
fi
|
|
|
|
attached="$(printf '%s\n' "${attached}" | sed '/^$/d' | sort -u)"
|
|
expected="$(printf '%s' "${MCP_EXPECTED_NETWORKS}" | tr ',' '\n' | sed '/^$/d' | sort -u)"
|
|
|
|
echo "attached: $(printf '%s' "${attached}" | tr '\n' ' ')"
|
|
|
|
unexpected="$(comm -23 <(printf '%s\n' "${attached}") <(printf '%s\n' "${expected}"))"
|
|
absent="$(comm -13 <(printf '%s\n' "${attached}") <(printf '%s\n' "${expected}"))"
|
|
|
|
if [[ -n "${unexpected}" ]]; then
|
|
echo "REJECTED the adapter is attached beyond its declared networks ($(printf '%s' "${unexpected}" | tr '\n' ' ')); a sidecar holding its provider's credential reaches that provider and its admitted clients only" >&2
|
|
fi
|
|
|
|
if [[ -n "${absent}" ]]; then
|
|
echo "REJECTED the adapter is missing a declared attachment ($(printf '%s' "${absent}" | tr '\n' ' ')); an admitted client cannot reach it" >&2
|
|
fi
|
|
|
|
[[ -z "${unexpected}${absent}" ]] || exit 1
|