# Cron and n8n advisory classifier contract Status: dry-run specification and integration examples Scope: cron and n8n alert/event classification through the OpenVINO advisory gateway Gateway: `http://172.19.0.1:18830` from `n8n-agent` and host-local cron on the current bridge-bound service. Override `NPU_ADVISORY_GATEWAY_URL=http://127.0.0.1:18830` only if a localhost-bound instance is explicitly running. ## Authority boundary This contract is advisory only. It may recommend one of `suppress`, `log`, `summarize`, or `escalate`, but it must not perform the action itself. Every integration must preserve these authority flags: ```json { "may_route": false, "may_write_memory": false, "may_send_external": false, "may_process_private_dirs": false, "may_execute_tools": false, "may_restart_services": false } ``` Allowed side effects in dry-run mode: - read an explicit cron/n8n event payload; - call the advisory gateway classifier/generator; - write compact local stdout or n8n execution logs; - store metadata-only advisory counters if an existing log sink already does so. Forbidden without separate explicit approval: - outbound sends/pages/Discord/Telegram/email; - service restarts, command execution, or tool calls; - Hermes/Atlas routing changes; - memory writes; - broad private-directory processing; - vector database mutation or reindexing. ## Input event envelope Cron and n8n producers should normalize events before classification. Keep this input small and avoid raw private payloads. ```json { "schema": "cron_n8n_event_v1", "trace_id": "cron:service-health:2026-06-05T14:30:00Z", "source": "cron", "workflow": "npu-service-health", "event_kind": "health_check", "severity": "warning", "subject": "openvino-reranker health check repeated warning", "summary": "Two consecutive health probes reported timeout, no restart attempted.", "dedupe_key": "service:openvino-reranker:timeout", "observed_at": "2026-06-05T14:30:00Z", "stale_after_s": 900, "action_requested": false, "dry_run": true } ``` Field rules: - `source`: `cron` or `n8n`. - `workflow`: compact job/workflow name, not a private URL. - `subject` + `summary`: the only text sent to the classifier. - `dedupe_key`: stable non-secret key for duplicate detection by the caller. - `stale_after_s`: caller-side freshness gate; stale events should not page. - `action_requested`: true only when an upstream job is asking a human/Atlas to consider action. - `dry_run`: must remain true for this phase. ## Gateway classifier call The current gateway `/v1/advisory/classify` accepts explicit text and wraps the classifier response in `openvino_advisory_v1` with NPU proof and authority fields. Host cron example for the current bridge-bound service: ```bash curl -fsS http://172.19.0.1:18830/v1/advisory/classify \ -H 'Content-Type: application/json' \ -d '{ "trace_id":"cron:service-health:sample", "text":"source=cron workflow=npu-service-health severity=warning kind=health_check subject=openvino-reranker repeated timeout summary=Two consecutive health probes reported timeout; no restart attempted; dry_run=true" }' | jq '{schema, mode, trace_id, npu_ok: .npu_proof.ok, npu_delta: .npu_proof.npu_busy_delta_us, authority, labels: .result.labels}' ``` n8n Docker-bridge example: ```bash curl -fsS http://172.19.0.1:18830/v1/advisory/classify \ -H 'Content-Type: application/json' \ -d '{"trace_id":"n8n:swarm-health:sample","text":"source=n8n workflow=swarm-health-watchdog severity=critical kind=health_check subject=multiple services unhealthy summary=Health probe failed for three services; dry_run=true"}' \ | jq '{mode, npu_ok: .npu_proof.ok, npu_delta: .npu_proof.npu_busy_delta_us, may_send_external: .authority.may_send_external}' ``` NPU proof gate: an HTTP 200 is not enough. Treat the classifier as NPU-backed only when `.npu_proof.ok == true` and `.npu_proof.npu_busy_delta_us > 0` for real inference. ## Advisory decision envelope Cron/n8n wrappers should map the gateway response plus caller-side freshness/deduplication state into this compact decision envelope: ```json { "schema": "cron_n8n_advisory_decision_v1", "trace_id": "cron:service-health:2026-06-05T14:30:00Z", "source": "cron", "workflow": "npu-service-health", "dry_run": true, "recommendation": "summarize", "classification": "action_required", "confidence": 0.84, "reason_codes": ["warning_or_high_urgency", "fresh_event", "not_duplicate"], "npu_proof": {"required": true, "ok": true, "npu_busy_delta_us": 1234}, "authority": { "may_route": false, "may_write_memory": false, "may_send_external": false, "may_process_private_dirs": false, "may_execute_tools": false, "may_restart_services": false }, "next_gate": "human_or_atlas_review_required_before_any_side_effect" } ``` Decision fields: - `recommendation`: `suppress`, `log`, `summarize`, or `escalate`. - `classification`: `duplicate`, `stale`, `no_op`, or `action_required` for v1 examples. - `confidence`: use classifier urgency/category confidence when available; otherwise use a conservative wrapper score. - `reason_codes`: compact machine-readable rationale, not raw payload text. - `next_gate`: always a review/approval gate before side effects. ## Recommendation mapping This is the v1 dry-run mapping. It is intentionally conservative and caller-side; the NPU classifier advises, the wrapper chooses a recommendation, and humans/Atlas retain authority. | Caller/classifier signal | Classification | Recommendation | Dry-run behavior | |---|---|---|---| | Same `dedupe_key` observed inside caller cooldown | `duplicate` | `suppress` | Log compact duplicate count only. Do not send. | | `observed_at + stale_after_s` is older than now | `stale` | `log` | Log stale event and age. Do not summarize/page. | | Severity low/normal, no action requested, classifier urgency low/normal | `no_op` | `log` | Keep normal execution log only. | | Warning/high urgency or action requested, NPU proof ok | `action_required` | `summarize` | Draft a local summary for review; no send/restart. | | Critical severity or repeated failures and NPU proof ok | `action_required` | `escalate` | Recommend escalation to Atlas/human; wrapper still must not send/restart. | | NPU proof missing or false | `action_required` or caller-specific | `log` | Log `npu_proof_failed`; do not claim NPU-backed advice. | ## Required examples ### Duplicate -> suppress Input summary: ```json {"source":"cron","workflow":"npu-service-health","severity":"warning","dedupe_key":"service:reranker:timeout","summary":"Same timeout as prior run inside cooldown.","dry_run":true} ``` Decision: ```json {"classification":"duplicate","recommendation":"suppress","reason_codes":["dedupe_key_in_cooldown"],"next_gate":"none_in_dry_run"} ``` ### Stale -> log Input summary: ```json {"source":"n8n","workflow":"swarm-health-watchdog","severity":"warning","observed_at":"older_than_stale_after","stale_after_s":900,"summary":"Delayed webhook replay for an old probe.","dry_run":true} ``` Decision: ```json {"classification":"stale","recommendation":"log","reason_codes":["event_stale"],"next_gate":"none_in_dry_run"} ``` ### No-op -> log Input summary: ```json {"source":"cron","workflow":"backup-check","severity":"normal","action_requested":false,"summary":"Backup completed and all expected files are present.","dry_run":true} ``` Decision: ```json {"classification":"no_op","recommendation":"log","reason_codes":["normal_severity","no_action_requested"],"next_gate":"none_in_dry_run"} ``` ### Action required -> summarize/escalate Input summary: ```json {"source":"n8n","workflow":"swarm-health-watchdog","severity":"critical","action_requested":true,"summary":"RAG and embeddings health failed repeatedly; no restart attempted.","dry_run":true} ``` Decision: ```json {"classification":"action_required","recommendation":"escalate","reason_codes":["critical_severity","action_requested","fresh_event"],"next_gate":"human_or_atlas_review_required_before_any_side_effect"} ``` ## Optional local summary draft If the decision is `summarize` or `escalate`, a wrapper may request a bounded draft from `/v1/advisory/generate`: ```bash curl -fsS http://172.19.0.1:18830/v1/advisory/generate \ -H 'Content-Type: application/json' \ -d '{"trace_id":"cron:service-health:sample","job":"summary","input":"Health check warning: openvino-reranker timed out twice; no restart attempted.","max_new_tokens":48}' \ | jq '{mode, trace_id, npu_ok: .npu_proof.ok, authority, draft: .result.draft_text, final_authority: .result.final_authority}' ``` The draft remains non-authoritative. It must not be automatically sent externally or written to memory. ## n8n integration pattern Recommended node chain for dry-run workflows: ```text Schedule/Webhook/Failure Trigger -> Set normalized event envelope -> HTTP Request POST /v1/advisory/classify -> Code node maps decision envelope -> IF node on recommendation suppress/log: execution log only summarize/escalate: optional local summary draft, then execution log only ``` The IF node must not connect to outbound messaging, service restart, memory write, or Hermes routing nodes until a separate approval changes the authority boundary. See `../examples/n8n-advisory-dry-run-fragment.json` for a sanitized node fragment. ## Cron integration pattern Cron jobs should call a wrapper script that prints one compact line and exits successfully unless the wrapper itself fails. The wrapper should not page or restart. Example crontab shape: ```text */15 * * * * /home/will/lab/swarm/openvino-advisory-gateway/examples/cron-advisory-dry-run.sh npu-service-health warning health_check "openvino-reranker timeout twice" "service:openvino-reranker:timeout" >> /home/will/.local/state/npu-advisory/cron.log 2>&1 ``` See `../examples/cron-advisory-dry-run.sh`. ## Verification checklist - Gateway health is reachable on the intended interface. - Classifier response includes `schema=openvino_advisory_v1`. - `.authority.*` flags are all false for side-effect authority. - `.npu_proof.ok` is true and `npu_busy_delta_us > 0` before claiming NPU-backed advice. - Decision envelope is compact and contains only booleans/counts/paths/deltas/gates. - Duplicate/stale/no-op/action-required examples remain dry-run only. - No n8n workflow activation, outbound send, service restart, memory write, routing change, private-dir broadening, or vector DB mutation occurred.