Commit Graph
8 Commits
Author SHA1 Message Date
evangelostsak b615c5e31e feat(svc-ai-mcp-adapter): let a deployment put its hosts into meshes 2026-10-16 03:40:16 +02:00
kevinveenbirkenbachandClaude Opus 5 4ac07edaea refactor(meta): move the info-media exemption to the file it speaks about
The marker names the reason a role has no upstream homepage or demo video, and
it sat in meta/main.yml because a comment-only info.yml parses to an empty
document, which the schema lint rejected outright. So the exemption lived one
file away from the two fields it explains, and a reader looking for the video
found neither the field nor the reason it was missing.

The schema lint now accepts an empty document exactly when it carries the
marker, and that is what allows the move. The media lint reads the marker from
info.yml rather than main.yml, and its failure message names that same file, so
the fix it suggests lands where the absent field would have gone.

A hundred and two roles follow. Every occurrence was a standalone comment on
the second line of main.yml, directly under the document start, so the move is
a line delete and an insert at the same offset. Seventy-three roles already had
an info.yml and gain the line; twenty-nine get one whose whole content is the
document start and the marker. All hundred and two reasons carry over verbatim,
including the thirty-five bespoke ones.

Nothing downstream sees a change. Of the four readers of meta/info.yml, three
coerce a null parse through `or {}` behind an isinstance guard, and
utils/cache/applications.py skips a falsy parse before assignment, so
applications.<role>.info stays absent for the twenty-nine exactly as it was
while the file did not exist.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 22:26:38 +02:00
kevinveenbirkenbachandClaude Opus 5 627ff73fae fix(svc-ai-mcp-adapter): put the docker-version suppressor where the check reads it
The marker sat above image:, and the check reads it relative to the version: key, either on that line or on the one directly above. Neither matched, so the role declared a pin and still drew an Outdated warning on every run. A warning does not fail the run, so the dead suppressor stayed invisible.

Negative control against the committed file: is_suppressed_at reports same-line=False and line-above=False at HEAD, True after the move. A repo-wide scan with the same helper finds no other version key whose suppressor fails to apply; the seven that looked misplaced use the same-line form, which is equally valid.

Moved rather than bumped: the marker's own reason is that this is the base of a self-built stdlib-only image, and svc-db-qdrant pins the identical 3.13.15-slim for the identical reason. Raising one would split the two adapters.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 22:38:49 +02:00
kevinveenbirkenbachandClaude Opus 5 8d2c0cd0e3 fix(svc-ai-mcp-adapter): probe health without curl
The healthcheck used the curl flavor, which invokes the curl binary in the
container. The adapter runs on python:3.13.15-slim, a Debian slim image that
ships no curl, so the probe could never succeed and the container would sit
unhealthy indefinitely.

http_status opens /dev/tcp/localhost/<port> from bash and matches the status
line against 2xx or 3xx, needing no extra tool. keycloak, matrix and
socialhome already use it.

Same class as the mariadb healthcheck in 1a81a6d86: a probe that cannot pass
in the container it runs in.

Note http_status asserts less than curl -f, accepting any 2xx or 3xx, and it
depends on bash with /dev/tcp, which a future alpine or distroless base would
not provide.

The container was not running in any round this session, so the missing curl
is inferred from the distribution and from a sibling container of the same
stack answering `sh: curl: not found`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 13:12:58 +02:00
kevinveenbirkenbachandClaude Opus 5 700d537855 refactor(mcp): pin the adapter base by tag where the mirror looks
The adapter was the only image in the tree pinned by a hand-maintained digest,
and the pin was never recomputed by anything. It drifted twice: run 31374959721
lost seven of eight jobs to mirror/docker.io/python@sha256:6771159c... not
found, and the renewal to the newest upstream digest earlier today would have
reproduced it, because builds resolve FROM against the GHCR mirror while the
mirror is copied by tag and lags upstream by design.

The base is now FROM <image>:<version>, taken from the sidecar service in
meta/services.yml - the only file iter_role_images scans, so the tag the build
asks for is by construction the tag the mirror was told to copy. The version
moves from the moving 3.13-slim to 3.13.15-slim, which upstream stops rewriting
once published, and which the existing version drift job renews like every other
image.

With the build reading services.yml, the adapter block's digest, image and
version had no consumer left. They are removed together with the schema check
for the digest form, the warn-only drift test, and the manifest_digest helper
that existed only for it. What remains in the block describes the adapter rather
than the image it is built from.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 23:38:53 +02:00
kevinveenbirkenbach 8509ea49cb fix(mcp): opt the adapter out of the deploy matrix at the declared SPOT
The earlier attempt used meta/tests.yml, which the deploy-modes lint
records as replaced: it reached CI discovery but left get_role_skip
empty, so the roundtrip script, the swarm context and the deployment
lookup all still treated the adapter as a standalone application.

modes.<mode>.enabled is the SPOT, and the role has nothing to deploy on
its own: its vars read mcp.adapter.digest against the including
provider, which does not exist when the adapter itself is the
application, so its standalone job failed while rendering the template.
2026-08-10 13:27:41 +02:00
kevinveenbirkenbach 8933ee2e5c fix(mcp): stop deploying the adapter role as a standalone application
The role stages a build context and creates a network; it never starts a
container, because the adapter runs as a sidecar inside each provider's
stack. Invokability follows the category path alone, so the svc-ai
prefix put a library role into the deployable matrix, where its job can
only fail. It is exercised in every provider stack that carries it, and
its contract probe is what reports a drifted upstream.
2026-08-10 11:24:23 +02:00
kevinveenbirkenbachandClaude Opus 5 683de0ab9a feat(svc-ai-mcp-adapter): add the reusable per-provider MCP adapter
One audited implementation, instantiated once per provider application rather
than deployed as a single process holding every application's credential. Each
instance joins only its provider's network, carries only that provider's
least-privileged credential and serves a distinct endpoint.

Every decision lives in the policy layer, which validates the contract at
startup rather than at first call, so an instance missing a limit, an
allowlist or the schema hash never reaches a client. The server itself only
moves bytes over three JSON-RPC methods, with no stream state to get wrong.
Its upstream header is configurable because only half the providers accept an
RFC 6750 bearer.

The contract probe runs at deploy time rather than in Playwright, because it
needs the provider's real credential and a Playwright trace would capture it.
It proves, over both transports, that an unauthenticated caller is refused,
that a wrongly authenticated one is refused, that the handshake completes,
that the served tool list is exactly the contracted one, and that one
deterministic read answers without an error. Steps one and two are what
separate a guarded endpoint from one that merely happens to be unreachable.

The isolation probe counts the sidecar's network interfaces from inside it.
Reachability cannot be used here: 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.

The role is inert until a provider includes it; those inclusions follow.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 14:04:08 +02:00