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, not ai) 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’s curl blocks 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):

FieldTypeRequiredNotes
datestringyesISO YYYY-MM-DD. The rule is evaluated on this value, never on the server clock.

Responses:

HTTPBodyMeaning
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 & Evaluate node reads the expected value from $env.HR_GATE_ENDPOINT_KEY (SOPS→env injection, the same runtime-env pattern as brand-linkedin-publish’s $env.BRAND_WEBHOOK_SECRET). The caller sends the same value as the X-API-Key header.
  • 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 a secret-manager task — 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

  1. Auth on every call — 401 on missing/wrong key; no authentication: none.
  2. Request-size limit — bodies over 4 KB are rejected with 400 (Guard & Evaluate node).
  3. 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.
  4. Idempotent — the endpoint is a pure read (no writes); retries are safe.

5. Error handling & notification

Two layers, both wired in the workflow JSON:

  1. Explicit error branch — Guard & Evaluate uses onError: continueErrorOutput. A thrown error (e.g. HR_GATE_ENDPOINT_KEY not injected) routes to a Notify 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-formed 500 to the caller.
  2. Central error workflow — settings.errorWorkflow is set to the repo-standard error_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 by secret-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:

  1. Ensure HR_GATE_ENDPOINT_KEY (and the always-present P24_DISCORD_INFRA_SCRIPTS_ERRORS_WEBHOOK_URL) are in the n8n container env (/opt/p24-infra/bms-4/.env, delivered by secrets-sync.yml from SOPS).
  2. Import via the n8n REST API (see docs/n8n-operations.md §Import a workflow): POST /api/v1/workflows with header X-N8N-API-KEY: ${BMS4_N8N_API_KEY}, body = this JSON.
  3. Activate the workflow (active: true) — it ships active: false in the repo so import never silently activates an endpoint before the key is in place.
  4. Smoke-test with the §8 checklist.

7. Restore

Workflows are backed up nightly to Wasabi (n8n-backup.yml). To restore hr-request-gate:

  1. Re-import n8n-workflows/hr-request-gate.json via the REST API (§6 step 2) — the repo copy is canonical.
  2. Confirm HR_GATE_ENDPOINT_KEY and P24_DISCORD_INFRA_SCRIPTS_ERRORS_WEBHOOK_URL are present in the instance env; re-enter any credential-store values by hand if a fresh instance lost them.
  3. Re-activate and re-run the §8 checklist.

8. Manual test checklist (run after deploy)

  • POST with valid X-API-Key and an in-range date (e.g. 2026-09-03) → 200 {allowed:false}
  • POST with an out-of-range date (e.g. 2026-09-20) → 200 {allowed:true}
  • POST with no / wrong X-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 itemOwner roleStatus
Workflow JSON + ops doc + dev_r_services rowdev-coderDone (this PR, #7318)
secrets/pinbox24-hr-gate.env.sops + HR_GATE_ENDPOINT_KEY value + recipients + distributionsecret-managerFollow-up (separate issue)
Import + activate on live n8n (bms-4)sys-admin / infra-taskFollow-up (separate issue)
Wire the endpoint into the confirmed Pinbox24 W3/W4 task + end-to-end testsys-admin / infra-task (W3/W4 owner)Follow-up (separate issue)
HR policy: fail-open vs fail-closed on caller timeout (plan Q2)HR process ownerOpen decision