Pinbox24 HR request gate — operations
Service: hr-request-gate (n8n workflow on bms-4)
Type: Synchronous webhook, deterministic date rule — no LLM, not an AI system
Repo source of truth: n8n-workflows/hr-request-gate.json
Plan / design: docs/plans/plan-7228-n8n-prompt-response-endpoint.md (Option A), issue #7228 / #7318
Related: docs/n8n-operations.md §Active Workflows, docs/w3-w4-stack-operations.md
1. What it does
The HR requirement is a deterministic calendar rule: block one employee’s leave requests between
day 1 and day 10 of each month. There is one input (the date) and one boolean output, with no
natural-language judgement — so this is a plain 1 <= day_of_month <= 10 comparison, not an LLM
call. Choosing the deterministic rule avoids cost, latency, an open LLM attack surface, and — because
the output would gate an HR/employment decision — an EU AI Act Annex III high-risk
classification (plan-7228 §7). A calendar comparison is not an AI system, so there is no
dev_r_ai_systems entry for it.
The Pinbox24 “AI request” task is used purely as the integration carrier — the mechanism the W3/W4 stack already has for calling an external HTTP endpoint — not as an actual AI call.
2. Endpoint
POST https://n8n.bms-4.infra.zintegrowana.online/webhook/hr-request-gate
Header: X-API-Key: <HR_GATE_ENDPOINT_KEY>
Body: {"date": "2026-09-23"} # ISO YYYY-MM-DD, passed EXPLICITLY — never inferred server-side
- Static, non-guessable path (
hr-request-gate, notai) so the endpoint is not trivially discoverable; the API key is the actual access control. responseMode: responseNode(“Respond to Webhook”) makes the call synchronous — the caller’scurlblocks on the single request until the workflow reaches the Respond node. No polling.- Response is typically <100 ms (a pure in-memory comparison; no external calls on the happy path).
2.1 Request / response contract
Request body (JSON):
| Field | Type | Required | Notes |
|---|---|---|---|
date | string | yes | ISO YYYY-MM-DD. The rule is evaluated on this value, never on the server clock. |
Responses:
| HTTP | Body | Meaning |
|---|---|---|
200 | {"allowed": false, "reason": "requests are blocked between day 1 and day 10 of the month", "day": 3} | Rule evaluated. allowed=false ⇒ blocked (day 1–10). |
200 | {"allowed": true, "reason": "requests are allowed after day 10 of the month", "day": 15} | Rule evaluated. allowed=true ⇒ permitted (day 11+). |
400 | {"error": "..."} | Missing/invalid date, impossible calendar date, or body over 4 KB. |
401 | {"error": "unauthorized"} | Missing or wrong X-API-Key. No rule is evaluated. |
500 | {"error": "internal error evaluating request gate"} | Unexpected node failure (e.g. key not injected into n8n). Also fires the error notification (§5). |
A non-2xx status lets the caller distinguish “endpoint down / misconfigured” from “rule says
blocked” (which is a normal 200 with allowed=false).
3. Authorization — HR_GATE_ENDPOINT_KEY
- A dedicated, narrow key — not the n8n management key
BMS4_N8N_API_KEY(that would over-grant: its holder can rewrite every workflow on the instance). Least-privilege requires a separate key. - The workflow’s
Guard & Evaluatenode reads the expected value from$env.HR_GATE_ENDPOINT_KEY(SOPS→env injection, the same runtime-env pattern asbrand-linkedin-publish’s$env.BRAND_WEBHOOK_SECRET). The caller sends the same value as theX-API-Keyheader. - Key name only appears anywhere in this repo — the value lives in SOPS. Creating/populating the
SOPS file (
secrets/pinbox24-hr-gate.env.sops, minimal recipient set) and distributing the value to (a) the n8n instance env and (b) the Pinbox24 task’s secret store is asecret-managertask — see §7 delegation. dev-coder never writes or decrypts SOPS. - Optional defence-in-depth (deferred, not a blocker): the caller is a fixed set of Pinbox24 backend hosts, so a Traefik/Cloudflare IP allowlist would narrow exposure further.
4. Security & privacy
- Auth on every call — 401 on missing/wrong key; no
authentication: none. - Request-size limit — bodies over 4 KB are rejected with 400 (
Guard & Evaluatenode). - No sensitive-data logging — the workflow sets
saveDataSuccessExecution: none, so successful execution payloads (which would contain the request body) are not persisted to n8n’s Postgres (plan-7228 §4.3). Error executions are saved (saveDataErrorExecution: all) for debugging, and the error notification (§5) deliberately omits payload/prompt contents. - Idempotent — the endpoint is a pure read (no writes); retries are safe.
5. Error handling & notification
Two layers, both wired in the workflow JSON:
- Explicit error branch —
Guard & EvaluateusesonError: continueErrorOutput. A thrown error (e.g.HR_GATE_ENDPOINT_KEYnot injected) routes to aNotify Discord (error)HTTP node that POSTs a symptom-only embed to$env.P24_DISCORD_INFRA_SCRIPTS_ERRORS_WEBHOOK_URL(no payload contents), then returns a well-formed500to the caller. - Central error workflow —
settings.errorWorkflowis set to the repo-standarderror_flow(gG3SsUPtcqgtsbje), which also fires on any error the explicit branch does not catch (Discord + Supabase row). This is the same central handler the rest of the n8n stack uses.
Known gap (follow-up): the repo Error Notification Standard also asks for a
bug-labelled GH issue on failure. Creating a GH issue from inside n8n requires a GitHub credential/token in the instance, which is out of scope for this dev-coder deliverable (no new token surface added). The Discord + Supabase paths above cover alerting today; a GH-issue-on-repeated-failure step can be added as a hardening follow-up (would need a scoped GH token delivered bysecret-manager).
6. Deploy / import (out of dev-coder scope — sys-admin / infra-task)
The repo JSON does not auto-deploy. To go live on bms-4:
- Ensure
HR_GATE_ENDPOINT_KEY(and the always-presentP24_DISCORD_INFRA_SCRIPTS_ERRORS_WEBHOOK_URL) are in the n8n container env (/opt/p24-infra/bms-4/.env, delivered bysecrets-sync.ymlfrom SOPS). - Import via the n8n REST API (see
docs/n8n-operations.md§Import a workflow):POST /api/v1/workflowswith headerX-N8N-API-KEY: ${BMS4_N8N_API_KEY}, body = this JSON. - Activate the workflow (
active: true) — it shipsactive: falsein the repo so import never silently activates an endpoint before the key is in place. - Smoke-test with the §8 checklist.
7. Restore
Workflows are backed up nightly to Wasabi (n8n-backup.yml). To restore hr-request-gate:
- Re-import
n8n-workflows/hr-request-gate.jsonvia the REST API (§6 step 2) — the repo copy is canonical. - Confirm
HR_GATE_ENDPOINT_KEYandP24_DISCORD_INFRA_SCRIPTS_ERRORS_WEBHOOK_URLare present in the instance env; re-enter any credential-store values by hand if a fresh instance lost them. - Re-activate and re-run the §8 checklist.
8. Manual test checklist (run after deploy)
-
POSTwith validX-API-Keyand an in-range date (e.g.2026-09-03) →200 {allowed:false} -
POSTwith an out-of-range date (e.g.2026-09-20) →200 {allowed:true} -
POSTwith no / wrongX-API-Key→401, no execution recorded - Oversized (
>4 KB) body →400 - Missing / malformed
date→400 - Forced node failure (e.g. temporarily unset the key) → Discord embed fires, caller gets
500 - Successful execution shows no persisted payload in n8n’s execution list (privacy, §4)
- Pinbox24 task receives and parses the synchronous response within its timeout
9. Delegation map (from plan-7228 §8)
| Work item | Owner role | Status |
|---|---|---|
Workflow JSON + ops doc + dev_r_services row | dev-coder | Done (this PR, #7318) |
secrets/pinbox24-hr-gate.env.sops + HR_GATE_ENDPOINT_KEY value + recipients + distribution | secret-manager | Follow-up (separate issue) |
| Import + activate on live n8n (bms-4) | sys-admin / infra-task | Follow-up (separate issue) |
| Wire the endpoint into the confirmed Pinbox24 W3/W4 task + end-to-end test | sys-admin / infra-task (W3/W4 owner) | Follow-up (separate issue) |
| HR policy: fail-open vs fail-closed on caller timeout (plan Q2) | HR process owner | Open decision |