catalogue-agent and approvisionnement-agent named a Deployment, a Service, an
Ingress and a Certificate in French, in an estate where nothing else is.
The peer URLs move with them, set through the existing per-environment override
so the rename takes effect without rebuilding an image; the defaults in the code
carry the same values for the next build.
The Keycloak client ids, the image names and the repository names are still
French. They are a separate, heavier move.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
assistant-agent.sttlab.local and its two siblings were long, and two of them
were French where nothing else in the estate is.
The Kubernetes object names are unchanged — they name a Deployment and a
Service, and nobody types them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nothing outside the cluster could talk to it: every conversation went through
kubectl run curl, which makes an agent that works and nobody can use.
The same authentication subrequest that guards the n8n endpoints guards this
one. The agent forwards and exchanges the caller's token, so a forged one
fails at the realm — but failing downstream is not refusing, and an
unauthenticated caller could still make it do inference.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Kafka is administered centrally. An ACL is not a team's decision to make about
itself, so the account moved to k8s-platform/kafka-topology.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An account belongs to the consumer, not the broker: this repo says what the
agent may read, so adding or removing the agent adds or removes its access.
It was in infra-kafka, which deploys a cluster and should hold no opinion about
who uses it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every webhook on this instance was answering unauthenticated requests, the
stock watchlist included — it returned references and stock levels and let
anyone clear entries. The A2A facade in front of these workflows enforces a
bearer, but the workflows stayed reachable directly, facade bypassed.
Keycloak's userinfo is the authenticator: 200 for a valid bearer in the realm,
401 otherwise, which is the auth-request contract exactly.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The reactive half of the agent. It consumes product.stock_changed from the
catalog topic and reports only a crossing of the threshold — every further
order line on an already-low reference would otherwise raise the same alarm,
and an agent that repeats itself is one a human learns to ignore. A stock-out
is treated as its own fact even when the reference was already low.
The detection lands on a watchlist readable over a webhook, because a proposal
that reached only an execution log would be lost between two nightly runs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Withdrawn: the offline_access mandate built an hour ago. An agent running on its
own account acts for itself. Nobody asked for the 06:00 run, so naming a person
as its principal was a fiction — and choosing which person was arbitrary, as the
first implementation showed by picking whichever test user was to hand.
It also cost more than it was worth. An offline token has no exp, dies only
after 30 days unused, is not rotated on use, and is revocable only by killing
that user's offline session: a permanent bearer credential standing in for a
consent nobody gave.
Now client_credentials, with the service account carrying read_only — the least
privilege the job needs. That is not a convention the workflow observes: the MCP
server maps the role to a consumer group at discovery, so tools/list under this
identity returns get-product and list-products and nothing else. create-product
is invisible rather than merely unused.
Nothing downstream changes. azp still names the agent, audiences are still
per-server, and policy still reads roles — a service account has roles like
anyone else.
The mandate client stays in the realm, unused, for the case where a person's own
authority genuinely is in play: Recouvrement's dunning, which is human-validated
already.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The standing authority the autonomous runs act under: an offline refresh token
issued by a new classic-models-mandate client for the user who authorised the
agent, stored in Vault, redeemed at trigger time.
The mandate client holds no domain audience. It cannot reach data; it can only
say who authorised whom. Direct access grants on it is a lab shortcut, noted as
such — mandating an agent is a decision a person takes, and a password grant
records no consent where the authorization code flow would.
Verified end to end: refresh gives sub=pm-user azp=classic-models-mandate with
the agent in the audience, the agent exchanges it to azp=itself and
aud=mcp-catalog, and the MCP call answers 200. sub stays the person who
mandated, azp says which agent acted.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The adapter runs in the agents namespace with the other agents, not with n8n:
what it is, is an agent, and the workflow behind it is an implementation detail
its callers never see.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The values that infra-n8n deliberately does not carry: our hostname, issuer,
Vault path and images, plus the two things that are ours alone — the lab CA the
workflows need to verify internal TLS, and the agents' Keycloak client secrets,
read from the realm's own Vault entry so there is one source per secret.
Self-managing after three applies, like the rest of the estate.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>