LAYER 4 · RUNTIME ENFORCEMENT
Enforcement reference
The enforcement YAML schema, the approvals API, the approval record, alert event types, and the server-side configuration knobs.
Enforcement YAML schema
Everything lives under the top-level enforcement: key of the policy file, next to the layer-2 tool rules. Omitting the section entirely means every call observes.
| Key | Values | Meaning |
|---|---|---|
enforcement.<env> | observe | enforce | require_approval | Mode for every call from agents in that environment. An unlisted environment is observe. |
enforcement.rules.<tool> | same three modes | Per-tool override — wins over the environment mode for that tool, in every environment. |
approval.defaults.window_seconds | number > 0 (default 60) | How long a held call waits for a decision before timing out (timeout = deny). |
approval.defaults.blast_radius_threshold | 0–1 (default 0.7) | In an enforcing mode, a permitted call whose declared blast radius meets this is held for approval. |
approval.<env> | {window_seconds, blast_radius_threshold, channel} | Per-environment override of the defaults; channel routes that environment's approval alerts to a Slack channel. |
fail_mode.<env> | open | closed (default open) | What a policy-engine error does in that environment: open = pass + warn, closed = 403 + fail_closed alert. |
Approvals API
| Endpoint | Min role | Behavior |
|---|---|---|
GET /approvals?status=&limit= | viewer | Org-scoped list, newest first. status filters on pending / approved / denied / timeout; limit defaults 100, max 1000. |
POST /approvals/{id}/approve | security | Approves a pending approval and releases the held call. Body: {approver?, note?}. A session user always decides as themselves — the body approver is ignored; an org API key must supply approver (422 otherwise). |
POST /approvals/{id}/deny | security | Same body and approver semantics; the held call returns 403 with the denial and approver in the reason. |
| Errors (both decisions) | — | 404 unknown (or malformed) id or another org's approval; 409 approval already <status> when it was already decided or timed out — decisions are claimed atomically, so exactly one wins. |
The approval record
Every hold writes one row to mcp_approvals (migration 004_r4_enforcement.sql) — the tamper-evident record of who decided what. Fields as returned by GET /approvals:
| Field | Type | Meaning |
|---|---|---|
id | uuid | The approval id — appears in the alert and the decide URLs. |
agent_id / agent_name / environment | text | Which agent's call is held, and where it runs. |
mcp_server / tool_name / params | text / json | Exactly what the agent was about to do, arguments included. |
blast_radius | number 0–1 | null | The tool's declared blast-radius score, if the policy declares one. |
reason | text | Why approval was required — the explicit rule, or the blast-threshold comparison. |
status | pending | approved | denied | timeout | The lifecycle of the hold. Only pending rows can be decided. |
requested_at / window_seconds | timestamp / number | When the hold started and how long it will wait — together, the countdown. |
decided_at / approver / decision_note | timestamp / text / text | Who decided, when, and their note. The approver is always a named human identity (or the name an API-key caller was required to supply). |
Alert event types
| Type | Fired when |
|---|---|
block | An enforcing mode blocked a policy-denied call before the upstream. |
approval_request | A call was held — carries the approval id and the environment's channel override. |
approval_decided | A held call was denied or timed out (an approved call simply proceeds; the decision lives in the call log and the approval row). |
fail_closed | The policy engine errored in an environment with fail_mode: closed, and the call was blocked. |
Server configuration knobs
All optional; with no channel configured, alerts stay local (zero egress).
| Variable | Default | Meaning |
|---|---|---|
SNOPS_SLACK_WEBHOOK_URL | unset | Slack incoming-webhook URL; unset disables the Slack channel. |
SNOPS_SMTP_HOST / SNOPS_SMTP_PORT | unset / 587 | SMTP relay for email alerts (STARTTLS); unset host disables email. |
SNOPS_SMTP_USER / SNOPS_SMTP_PASSWORD | unset | SMTP credentials, if your relay requires login. |
SNOPS_ALERT_EMAIL_TO / SNOPS_ALERT_EMAIL_FROM | unset / sentnelops-alerts@localhost | Recipient and sender for email alerts; email needs both a host and a recipient. |
SNOPS_ENFORCEMENT_FAIL_MODE_DEFAULT | open | Last-resort fail mode when the policy store itself cannot be read — the per-env fail_mode lives in the YAML that failed to load. |