Plan: Generic synchronous n8n prompt→response endpoint for the Pinbox24 HR AI-task

Issue: #7228 Type: Code-change-design (plan only — no implementation in this issue) Status: Draft — iteration 1 Last updated: 2026-09-23 Author: Claude Opus 4.8 (dev-coder worker session on bms-4) Related: #951 (GROQ_API_KEY human-action), docs/n8n-llm-classifier.md (existing LLM-in-n8n pattern), docs/w3-w4-stack-operations.md (Pinbox24 W3/W4 ownership + permission matrix), secrets/report-query-api.env.sops / secrets/anthropic-admin.env.sops (narrow-recipient SOPS precedent)


0. Summary — recommendation

Two questions must be answered before any code is written, and one of them may make most of this work unnecessary. Do not implement until §9 open question Q1 is resolved with the HR process owner.

The HR requirement — “block leave requests between day 1 and day 10 of each month for one employee” — is a deterministic calendar rule. It has one input (the current date), one output (allowed / blocked), and no natural-language judgement. An LLM adds cost, 5–20 s of latency, a new externally-reachable attack surface, and — because the output would gate an HR/employment decision — a potential EU AI Act Annex III high-risk classification (see §7). None of that buys anything the rule 1 <= day_of_month <= 10 does not already give deterministically and for free.

So this plan presents two build options and a clear recommendation:

  • Option A (recommended) — deterministic rule endpoint. A tiny n8n webhook that evaluates the date rule in a Code/Function node and responds synchronously. No LLM, no model provider, no AI Act exposure. The Pinbox24 “AI request” task is used purely as the integration carrier (it is the mechanism Pinbox24 already has for calling an external HTTP endpoint from a task), not as an actual AI call.
  • Option B — generic LLM prompt→response proxy. The literal ask in the issue: POST {prompt} → LLM → {response} synchronously. This is genuinely reusable infrastructure, but for this HR case it is the wrong tool. If the org wants a general LLM proxy as a separate capability, build it as its own issue with its own justification — do not couple it to the HR blocker.

Recommendation: build Option A for #7228. Track Option B, if still wanted, as a separate feature issue so its cost/security/compliance trade-offs are reviewed on their own merits rather than smuggled in behind an HR date rule.

The rest of this plan designs both paths (the shared n8n/webhook/auth/error scaffolding is identical; only the middle node differs) so that whichever the HR owner confirms can proceed without a re-design cycle.


1. Current state — facts on the ground

1.1 n8n on bms-4 (verified from docs/n8n-operations.md + repo workflow JSON)

  • n8n 2.26.3, queue mode (EXECUTIONS_MODE=queue): the n8n main container (port 5678) receives webhooks; 3 n8n-worker-* containers dequeue from Redis and execute.
  • Public URL: https://n8n.bms-4.infra.zintegrowana.online (Traefik TLS termination).
  • Compose file: bms-4/docker-compose.yml (repo, canonical) → /opt/p24-infra/bms-4/docker-compose.yml (server). Secrets injected at runtime from /opt/p24-infra/bms-4/.env, deployed by secrets-sync.yml from SOPS.
  • Webhook + Respond to Webhook (synchronous response) is a native, already-used pattern — many workflows in n8n-workflows/*.json use webhook triggers, and headerAuth webhook authentication is already used by 5 existing workflows. No new n8n capability is required.
  • LLM nodes are already credentialed in this instance: @n8n/n8n-nodes-langchain.lmChatOpenAi (22 uses; gpt-4o-mini, gpt-4.1-mini), lmChatOpenRouter (17), googleGemini (6), lmChatXAiGrok (5), plus langchain agent/openAi nodes. So Option B has a working provider path today without minting a brand-new provider key — though the auth key for the endpoint itself is a separate concern (§3).

1.2 Deployment mechanics (from docs/n8n-operations.md §Deployment / §Import)

  • Workflow JSON is stored in n8n-workflows/ in this repo and imported to the live instance via the n8n REST API (POST /api/v1/workflows, X-N8N-API-KEY: ${BMS4_N8N_API_KEY}). The repo JSON does not auto-deploy — importing/activating is a server-side action.
  • Workflows are backed up nightly to Wasabi (n8n-backup.yml); credentials are exported encrypted (values never leave the instance) and must be re-entered by hand on a fresh restore.

1.3 Pinbox24 side — NOT verified from this repo

The “AI request” task lives in the Pinbox24 backend codebase (GitLab, W3/W4 stack), which is not present in p24-infra. This plan cannot confirm which service/task issues the curl, its timeout behaviour, or whether the task framework supports a non-document (prompt-only) call shape. That is open question Q3 (§9) and must be answered by whoever owns the Pinbox24 W3/W4 stack (see docs/w3-w4-stack-operations.md).


2. Proposed architecture

Shared scaffolding (identical for Option A and Option B):

Pinbox24 W3/W4 task
   │  POST https://n8n.bms-4.infra.zintegrowana.online/webhook/hr-request-gate
   │  Header: X-API-Key: <endpoint key>
   │  Body:   {"prompt": "...", "date": "2026-09-23"}   # date passed explicitly, not inferred
   ▼
[Webhook node]  authentication = headerAuth (responseMode = responseNode)
   ▼
[Validate/Guard node]  reject if body missing / prompt too large (see §4)
   ▼
   ├─ OPTION A: [Code node]  evaluate deterministic rule → {allowed:bool, reason:string}
   └─ OPTION B: [lmChat* node → LLM]  prompt → text response
   ▼
[Respond to Webhook node]  HTTP 200 JSON {allowed | response, ...}  (synchronous — same request)
   │
   └─ [Error branch] → error-flow / Discord webhook + GH issue (Error Notification Standard, §5)

Webhook path: use a static, non-guessable path (e.g. hr-request-gate, not ai) so the endpoint is not trivially discoverable; the API key (§3) is the actual access control.

responseMode: responseNode (“Respond to Webhook”) is what makes the call synchronous — the curl from Pinbox24 blocks on the single request until the workflow reaches the Respond node. No polling, matching the issue requirement.

2.1 Response contract (proposed)

Option A:

{ "allowed": false, "reason": "requests are blocked between day 1 and 10 of the month", "day": 3 }

Option B:

{ "response": "<model text>", "model": "gpt-4o-mini" }

Pin the contract before implementation so the Pinbox24 task parser is written once.


Decision (issue point 2): mint a new dedicated endpoint API key in its own narrow SOPS file — do NOT reuse BMS4_N8N_API_KEY.

  • BMS4_N8N_API_KEY is the n8n management API key (create/import/activate workflows, read credentials-list). Handing it to a Pinbox24 task to call one webhook would over-grant massively: anyone holding it could rewrite every workflow on the instance. Least-privilege forbids reuse.
  • Follow the established narrow-file precedent (secrets/report-query-api.env.sops, secrets/anthropic-admin.env.sops): a new file secrets/pinbox24-hr-gate.env.sops holding a single key, e.g. HR_GATE_ENDPOINT_KEY, with a minimal recipient set (developer + the bms-4 worker/CI key that needs to deliver it). The n8n webhook’s headerAuth credential is set to this value; the Pinbox24 task sends it as X-API-Key.
  • This SOPS work is out of dev-coder scope. Creating/populating the SOPS file and choosing the recipient list is a secret-manager task (see §8). This plan only specifies the key name, the target file, and the recipient intent — never a value.
  • Distribution: the value must reach (a) the n8n instance as a webhook headerAuth credential (entered in n8n, or injected via env if the workflow reads $env), and (b) the Pinbox24 task’s own secret store. Both deliveries are secret-manager + sys-admin/infra-task actions.

Optional defence-in-depth: since the caller is a fixed set of Pinbox24 backend hosts, an IP allowlist at Traefik (or a Cloudflare rule) narrows exposure further. Deferred to implementation as a hardening step, not a blocker — the API key is the primary control.


4. Security — this is (in Option B) an open LLM proxy

Even in Option A the endpoint is internet-reachable, so the controls below apply to both; the LLM-specific ones (prompt-abuse, cost) apply only to Option B.

  1. Auth required on every call — headerAuth (§3); reject unauthenticated with 401. No authentication: none.
  2. Request-size limit — reject bodies / prompt fields over a hard cap (e.g. 4 KB for the HR case; a few KB for Option B). Prevents cost-blowup and payload abuse. Enforce in the Validate node.
  3. No sensitive-data logging — n8n persists execution data to PostgreSQL by default. Set the workflow to not save successful execution payloads (or save errors only) so prompt contents are not retained. Never echo the API key or full prompt into Discord/GH error notifications (Error Notification Standard requires the symptom, not the payload).
  4. Rate limiting — Option B is effectively a paid LLM behind an HTTP endpoint. Add a simple per-key/per-window guard (n8n has no native limiter; a Redis INCR+TTL check in a Code node, or a Traefik rate-limit middleware, both work). For Option A the cost is zero so this is optional.
  5. No secret values in prompt/response — same global rule as everywhere; the HR use case sends only a date, so this is naturally satisfied in Option A.

5. Timeout & error handling (issue point 3)

  • Caller side (Pinbox24 curl): set an explicit connect + read timeout. Option A responds in <100 ms; Option B in ~5–20 s depending on model/prompt. Size the read timeout to ≥30 s for Option B, ≥5 s for Option A, and have the task treat a timeout as “rule not evaluated → fail safe” (see Q2 below — fail-open vs fail-closed is an HR-policy decision).
  • n8n side: wire an error branch to the existing centralized error-flow handler (routes to Discord) and additionally satisfy the repo Error Notification Standard (CLAUDE.md): on any workflow failure, POST a Discord embed via P24_DISCORD_INFRA_SCRIPTS_ERRORS_WEBHOOK_URL and create a bug-labelled GH issue in radieu/p24-infra. The Respond node on the error branch should return a well-formed error JSON + non-2xx status so the caller can distinguish “endpoint down” from “rule says blocked”.
  • Idempotency: the endpoint is a pure read (no writes), so retries are safe.

6. Compliance (issue point 6)

New service ⇒ mandatory compliance steps (CLAUDE.md §Compliance), all performed at implementation time, documented here so nothing is missed:

  1. dev_r_services — add a row for the new endpoint/workflow (host='bms-4', compliance_workbook='yes', workbook_url → the new ops doc).
  2. Ops doc — create docs/pinbox24-hr-gate-operations.md (endpoint URL, webhook path, auth key name, request/response contract, Pinbox24 caller, error-handling, restore steps). Cross-link from docs/n8n-operations.md §Active Workflows.
  3. EU AI Act — see §7. Option A: not an AI system → no dev_r_ai_systems row. Option B: is an AI system → register in dev_r_ai_systems + update docs/eu-ai-act-compliance.md.

7. EU AI Act flag (the decisive compliance argument)

The EU AI Act (Annex III) treats AI used in employment / worker-management decisions as high-risk. An LLM whose output gates whether an employee may submit a leave request plausibly falls in that category, pulling in transparency, human-oversight, logging, and risk-management obligations (deadline 2026-08-02, already past — see CLAUDE.md).

  • Option A (deterministic rule): no AI system, no Annex III exposure. A calendar comparison is not “AI”.
  • Option B (LLM decides): high-risk exposure for a decision a >=/<= comparison makes perfectly. This is the strongest reason to prefer Option A for the HR use case.

This flag is precisely why Q1 must be answered before building.


8. Role boundaries / delegation (dev-coder scope)

This is a plan (design) written by a dev-coder session. Implementation crosses role boundaries:

Work itemOwner roleWhy
Author + import/activate the n8n workflow JSON on the live instancesys-admin / infra-task (bms-4)Requires the n8n management API / server access; dev-coder cannot operate the server. docs/w3-w4-stack-operations.md for the Pinbox24-side permission matrix.
Create secrets/pinbox24-hr-gate.env.sops + populate HR_GATE_ENDPOINT_KEY + recipient set + distributionsecret-managerdev-coder must never write/decrypt SOPS.
Wire the endpoint key into the Pinbox24 W3/W4 task + confirm which service issues the curlsys-admin / infra-task (Pinbox24 stack owner)Pinbox24 backend is GitLab/server-side, not this repo.
Workflow JSON committed to n8n-workflows/ + ops doc + dev_r_services row (and dev_r_ai_systems if Option B)dev-coderRepo code/docs/DB-row changes.

The dev-coder implementation issue can produce the workflow JSON and docs; the deploy + secret + Pinbox24-wiring steps must be delegated as noted.

9. Open questions — MUST be resolved before implementation

  • Q1 (blocking, HR process owner): Is an LLM actually required, or is this a deterministic date rule? If deterministic → Option A, and no LLM/provider/AI-Act work at all. This plan strongly recommends Option A. Nothing should be built until this is confirmed.
  • Q2 (HR policy): fail-open or fail-closed? If the endpoint is unreachable/times out, should the Pinbox24 task allow the request through or block it? This is a policy decision, not a technical one, and it changes the caller’s error handling (§5).
  • Q3 (Pinbox24 W3/W4 stack owner): which W3/W4 service/task issues the curl, and can its “AI request” task framework make a prompt-only (non-document) call? The issue notes the existing task is document-oriented. Confirm the exact service and W3-vs-W4 target (docs/w3-w4-stack-operations.md).
  • Q4 (only if Option B): which provider/model? The instance already has OpenAI (gpt-4o-mini), OpenRouter, Gemini, and Grok credentialed. gpt-4o-mini is the cheapest already-wired option for a generic proxy; confirm before minting anything new.

10. Follow-up issues to create after this plan is accepted

  1. (if Q1 = deterministic) dev-issue: implement Option A workflow JSON + ops doc + dev_r_services row; then an infra-task to import/activate on bms-4.
  2. secret-manager: create secrets/pinbox24-hr-gate.env.sops with HR_GATE_ENDPOINT_KEY and distribute to n8n + Pinbox24.
  3. infra-task: wire the endpoint into the confirmed Pinbox24 W3/W4 task + end-to-end test.
  4. (only if Q1 = LLM / Option B) separate feature issue with its own cost/security/EU-AI-Act review, plus dev_r_ai_systems registration.

11. Manual test checklist (implementation-time)

  • POST with valid X-API-Key and a day-in-range date → {allowed:false} (Option A)
  • POST with a day-out-of-range date → {allowed:true}
  • POST with no / wrong API key → 401, no execution
  • Oversized prompt body → rejected by Validate node
  • Forced node failure → Discord embed + GH bug issue created (Error Notification Standard)
  • Successful execution payload NOT persisted with prompt contents (privacy, §4.3)
  • Pinbox24 task receives and parses the synchronous response within its timeout

Auto-generated by dev-coder worker agent for #7228. Plan only — no code shipped in this PR beyond this document. Awaiting /review-plan and resolution of §9 Q1 before implementation.