fix(workflow): reach the catalog, and answer
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>
This commit is contained in:
+44
-29
@@ -2,47 +2,62 @@
|
||||
|
||||
Exported as JSON and committed. The n8n instance is not the source of truth.
|
||||
|
||||
## Importing
|
||||
## Deploying a change
|
||||
|
||||
n8n's REST API, with an owner session cookie. The activation endpoint is
|
||||
separate from the update one and requires the current `versionId`:
|
||||
**n8n does not re-register an active workflow when you PATCH it.** The running
|
||||
webhook keeps executing the definition it was activated with, so an edit appears
|
||||
to have no effect — and the failure it keeps producing is the *old* one, which
|
||||
is a very good way to spend an afternoon debugging something already fixed.
|
||||
|
||||
Deactivate, apply, reactivate, in that order:
|
||||
|
||||
```
|
||||
POST /rest/workflows # import
|
||||
POST /rest/workflows/<id>/activate # {"versionId": "<current>"}
|
||||
POST /rest/workflows/<id>/deactivate {"versionId": "<current>"}
|
||||
PATCH /rest/workflows/<id> {"nodes":…, "connections":…, "versionId": "<current>"}
|
||||
POST /rest/workflows/<id>/activate {"versionId": "<current>"}
|
||||
```
|
||||
|
||||
`PATCH /rest/workflows/<id>` with `{"active": true}` returns 200 and leaves the
|
||||
workflow inactive, silently. Use the activate endpoint.
|
||||
`versionId` changes at every step, so re-read it each time. And note
|
||||
`PATCH {"active": true}` returns 200 and leaves the workflow inactive, silently:
|
||||
activation has its own endpoint.
|
||||
|
||||
## approvisionnement-check-availability
|
||||
|
||||
**Not working yet.** The `Exchange token` node fails, and what it fails with is
|
||||
worth recording:
|
||||
Working. `POST /webhook/check-availability` with the end user's token in
|
||||
`X-Subject-Token`:
|
||||
|
||||
```
|
||||
Keycloak 500, an HTML login page as the body
|
||||
NullPointerException: Cannot invoke UserSessionModel.getAuthenticatedClientSessionByClient
|
||||
because "userSession" is null
|
||||
at V1TokenExchangeProvider.exchangeClientToOIDCClient
|
||||
"Peut-on promettre S10_1949 ?"
|
||||
→ {"answer":"Yes — 1952 Alpine Renault 1300 (S10_1949) can be promised:
|
||||
7305 in stock, above the reorder threshold of 50.","escalate":false}
|
||||
|
||||
"Peut-on promettre la Vespa ?"
|
||||
→ {"answer":"No product reference in the question…","escalate":true}
|
||||
```
|
||||
|
||||
Two things stand out. Keycloak answers a malformed token exchange with a **500
|
||||
and a rendered login page**, which tells a client nothing. And it took the **V1**
|
||||
exchange path, not the Standard one every other agent in this estate uses
|
||||
successfully.
|
||||
It exchanges the token 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`. No `notifications/initialized` is needed.
|
||||
|
||||
What has been eliminated, each by hand against the same running Keycloak:
|
||||
MCP rather than the REST route, which would have been one 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.
|
||||
|
||||
- the client and its secret — Keycloak logs
|
||||
`kc.clientId: classic-models-agent-approvisionnement`, so it authenticated,
|
||||
which also proves `$env` reaches the node
|
||||
- the subject token — the *same* token replayed with curl exchanges fine (200)
|
||||
- omitting `subject_token_type` — still 200
|
||||
- omitting `openid` from the scope — still 200
|
||||
- using `audience=` instead of a scope — a clean 400, not this
|
||||
The product code is read with a regex, not a model. This agent's critical path
|
||||
is rules; a question naming no product escalates rather than being guessed at.
|
||||
The regex is loose after the underscore on purpose — a reference that looks
|
||||
valid but does not exist must reach the catalog and come back as "no such
|
||||
product", because answering "you named no product" would be false.
|
||||
|
||||
So n8n is sending something the reproduction attempts are not. The next step is
|
||||
to capture the request as the node actually emits it: point the node at an
|
||||
in-cluster echo service and read the body. That was set up and the run did not
|
||||
reach it, which is itself unexplained and where this stopped.
|
||||
### Known gap
|
||||
|
||||
A product code that does not exist returns an **empty body** rather than that
|
||||
message. The run completes, every node executes including the one that composes
|
||||
the answer, and the response is still empty — so the fault is between the
|
||||
composing node and the webhook response, not in the decision logic.
|
||||
`alwaysOutputData` on the read node did not change it.
|
||||
|
||||
For an autonomous agent, silence is the worst possible answer. Not shipped as
|
||||
acceptable — recorded as unfinished.
|
||||
|
||||
Reference in New Issue
Block a user