The periodic half of Approvisionnement: a 06:00 schedule that reads the catalog
and proposes purchase quantities for anything below the reorder threshold.
Verified on live data — 110 of 110 references scanned, S24_2000 at 15 on hand,
135 proposed.
Three things it deliberately does not do, each recorded rather than worked
around:
It halts without a mandate. No user session exists at 06:00, and the
offline_access mandate the design calls for does not exist yet, so the first
node stops before any token exchange and before reading anything. An agent
without an identity does nothing rather than inventing an authority.
It writes nothing. The schema has no purchase order and no supplier entity, and
the catalog's only write tool is create-product, which belongs to another agent.
So catalog:write was not granted, contrary to the design's scope list: a scope
with no reachable target is an authorisation waiting for a use.
It scans one page and says so. complete: false escalates, because a proposal set
computed from part of the catalog looks exactly like one computed from all of
it.
Also: check_availability now states its basis. Deducting quantities committed on
open orders would need the sales API to be filterable by product code — it is
not, so the honest alternative was paging 2996 rows per question.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
MCP does not report a tool failure as a JSON-RPC error. It reports it as a
successful result carrying isError: true, with the reason as text. The node
only checked `error`, so it read the failure as data and went on to JSON.parse
the sentence "HTTP call failed with status 404" as a product. The throw is what
made the webhook answer nothing at all.
Now checks result.isError, distinguishes a missing product from any other tool
failure, and guards every parse of a payload it did not construct. Through the
facade, escalate: true becomes a task in state failed rather than a completed
one.
Also corrects this file: the earlier note said the run completed in success
with every node executing. It did not — that was the wrong execution. n8n's
executions list did not return them newest-first as assumed, and the conclusion
drawn from it sent the search to the wrong place.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two things were wrong, and the second hid the first.
n8n does not re-register an active workflow's definition on PATCH: the running
webhook kept executing the version it was activated with. So the corrected
subject_token expression was never running, and the Keycloak V1 NullPointer it
kept producing was the old empty-token failure — including through the attempt
to capture the request with an echo service, which the old definition never
called. Deactivate, apply, reactivate; written down in workflows/README.md
because it costs an afternoon otherwise.
The token reference itself: $json is the previous node's output, not the
request, so the subject token has to come from $('Webhook').
The regex is now loose after the underscore, so a reference that looks valid but
does not exist reaches the catalog instead of being answered with 'you named no
product', which was false.
Working for the main path and for a question naming no product. A nonexistent
code still returns an empty body — recorded as a known gap, not as acceptable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The adapter is written once and parameterised, so both n8n agents get the same
A2A surface and differ only in the workflow behind the webhook. It owns the
protocol and nothing else: no Keycloak client, no credentials, and the end
user's token forwarded in a header so a workflow that logs its input does not
log the token with it. escalate: true fails the task rather than completing it,
because an autonomous agent meeting a rule that needs approval must hand it
back, not decide.
Serving this from the gateway was the repo's stated preference and does not
reach: A2A returns a Task with a lifecycle, and a proxy cannot synthesise that
from a webhook's single reply.
The check_availability workflow does the token exchange itself, using the
agent's client secret from the pod environment so the committed JSON holds no
secret, then talks MCP in two calls — initialize to obtain the session id from
a response header, then tools/call. It reads the product code with a regex
rather than a model: this agent's critical path is rules, and a question naming
no product escalates instead of being guessed at.
MCP rather than the REST route, which would have been one HTTP call instead of
two: the per-tool ACL by consumer group lives on the MCP route only. Going
through REST would give an autonomous agent broader access than a conversational
one, which is exactly backwards.
Status: the adapter is tested (13 tests) and the identity chain is verified by
hand, but the workflow fails at its token exchange and is not yet working —
see the note in workflows/.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>