playwright.config.js collects `**/*.@(spec|test).js`, so a module named
test-<something>.js runs only when its role's playwright.spec.js requires it.
Three roles wrote theirs as standalone specs with a top-level test() instead:
the MCP guest guards of n8n and wordpress, and the OIDC login of hermes. All
three have been dead files since they were added, so every MCP surface was
believed to be probed by a hostile guest and hermes' oauth2-proxy round trip
to be covered while none of those assertions had ever executed.
Each moves to the exports.register shape its siblings use and is appended to
its aggregator. Nothing else changes: `git diff -w` on this commit is the
test.use line becoming the register wrapper plus one require per aggregator,
and no assertion is touched. The module-level
test.use({ignoreHTTPSErrors: true}) goes away because all three aggregators
already set it for the tests they register, and a test.use inside register()
would not apply anyway. registerMcpDisabledState moves inside register() so
the switched-off case comes with it, which the addon specs do not cover since
they skip when the addon is off.
Registering them also subjects them to test.beforeEach(shared.beforeEach).
That is safe here: wordpress sets a viewport and asserts env presence, n8n
does the same and clears cookies, hermes is a no-op. None authenticates, so
the guest guards stay guests.
wordpress is the only one of the three with a green job to lose, and its
status assertion is already proven there: addons/mcp-adapter.spec.js posts to
the same endpoint with maxRedirects 0 and asserts >= 400, it matches the
collector glob, and WordPress#1 passes. The permission callback runs at the
transport before the JSON-RPC method is parsed, so initialize is refused
exactly like tools/list.
A fourth dead module, openwebui's test-ollama-chat.js, gates on a service no
role renders and follows separately.
Render every discovered server into the agent's own config, filtered to
the auth schemes a client can actually present, so no entry carries a
credential the server would reject.
Render hermes' config after the stack exists and before the handlers
flush; the other order fired compose up while the config was still
missing.
Own the config file as the container user. Rendered as root it stays
unreadable to the agent, which then falls back to its built-in defaults
silently and reports only that the server is unknown, pointing the reader
at the wrong layer.
Assert what these clients expose: neither ships an admin surface listing
its configured servers, so the specs hold the API to refusing an
unauthenticated caller, which is what keeps the per-server bearers out of
reach.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The first agent workload of requirement 032. Its compose service pins
runtime: to the fact svc-virt-kata publishes, so model-generated code
runs behind a kernel or syscall boundary rather than plain runc. The pin
goes through compose_only because docker stack deploy discards the key;
swarm relies on the node-default runtime and a placement constraint
instead.
Hermes is not a dashboard. The container starts its gateway and serves an
OpenAI-compatible API on 8642, which shapes three decisions: the env
block configures API_SERVER_*, inference points OLLAMA_BASE_URL at the
LiteLLM gateway (Hermes' Ollama client speaks the same wire format), and
the vhost declares status_codes [200, 404] because the API server maps
only /health and /v1/*, so the bare root answers 404 by design and the
webserver health unit would otherwise fail the host.
SSO is off deliberately: an oauth2 proxy in front of a bearer-token API
breaks OpenAI-compatible clients. Authentication is API_SERVER_KEY, and
the guest spec asserts that /v1/models without it is rejected while the
administrator spec asserts the key unlocks the listing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>