Governance Modes: Warn and Enforce
Why governance, safety scanning and quotas all run in warn mode on this instance, what each plane records while it does, how to read the numbers, and how to switch a plane back to enforce.
On this page
TL;DR: Three planes judge every governed request: the governance
chain, the gateway safety scanners and the quota windows. Since
2026-09-04 all three run in warn mode on this instance. Every stage
still evaluates and every finding is still recorded, but nothing is
refused. Authentication is separate and always enforced: a missing, invalid
or expired credential is still denied. Warn mode is a measurement window. Read
the numbers, fix the tunables that misfire, then return each plane to
enforce one at a time.
Why the instance is in warn mode
The chain and the scanners were switched on in enforce mode for the
enterprise rollout on 2026-08-25. Within days, false positives denied
ordinary developer traffic. jailbreak and pii_credit_card had already
come out of the scanners' block list on 2026-08-31 for that reason. Taking
categories out one at a time, each only after a broken workflow proved it
wrong, was not a way to reach a good policy. On 2026-09-04 all three planes
moved to warn so the evidence could build up without blocking work.
The three planes
| Plane | Configured in | Runs on | Switch | Records to |
|---|---|---|---|---|
| Governance chain | services/governance/config.yaml |
governed tool calls and gateway prompts | governance.mode |
governance_decisions |
| Safety scanners | services/gateway/policies.yaml |
gateway requests, and buffered responses | safety.mode |
ai_safety_findings |
| Quota windows | services/gateway/policies.yaml |
gateway requests | quota_mode |
governance_decisions (policy quota) |
Governance chain
Four stages run in order: scope check → secret scan → blocklist → rate limit. Their settings on this instance:
- Scope check. Tools prefixed
mcp__systemprompt__are admin-only. - Secret scan. A list of credential patterns owned by Dynapps: GitHub,
Anthropic, OpenAI and Google keys, PEM private keys, JWTs, database URLs
that embed a password, and structured
password/secret/client_secretfields. A high-entropy check runs alongside it, but only as an observation. - Blocklist. Tool names containing
delete,dropordestroy. - Rate limit. 300 calls per 60 seconds for each session and user.
Under warn, a stage that would have denied writes a decision = 'warn' row
with the reason it would have denied. It does not stop the chain, so one call
can be recorded against several stages. Warn is not the same as
enabled: false. A disabled stage does not run and records nothing.
mode is inherited. The top-level value applies to every policy that does
not set its own, and an unrecognised value fails boot rather than falling back
to either mode.
Safety scanners
The scanners run are heuristic, secrets and pii_extended. They produce
these categories:
| Category | Scanner | Would block under enforce |
|---|---|---|
jailbreak |
heuristic (a pinned phrase list) | no, audit-only |
pii_email |
heuristic | no, audit-only |
pii_credit_card |
heuristic | no, audit-only |
pii_ssn |
pii_extended | yes, on requests and responses |
pii_phone |
pii_extended | no, audit-only |
secret |
secrets (responses only) | yes, on responses |
The block lists, block_categories: [pii_ssn] for requests and
block_response_categories: [secret, pii_ssn] for responses, keep the
pre-warn enforcement set. Under warn they refuse nothing. They mark which
findings the report counts as would have blocked, so every finding is
written with blocked = false. Under enforce, a streamed response is still
only audited: its tokens are on the wire before the scan finishes.
Redaction ignores the mode. Core can rewrite a request finding in place,
as [REDACTED:<category>], for every category listed in
safety.redact_categories. That rewrite, and its fail-closed refusal of
content that cannot be redacted (a finding with no location, or a span inside
a signed block or protocol field), apply under warn as well as enforce.
This is why services/gateway/policies.yaml lists no redact_categories:
switching redaction on would let warn mode refuse requests. It is switched on
together with the enforce flip. Separately, each scanner's
scanner_settings.<name>.fail_mode decides whether a scanner fault or timeout
blocks; here secrets is fail_mode: open with a 2-second timeout, so a fault
is recorded as a scanner_failure finding.
Quota windows
| Subject | Window | Threshold |
|---|---|---|
| each user | 1 hour | 600 requests or $20 |
each project (project) |
1 day | $100 |
the whole instance (organization) |
1 day | $200 |
Every window still runs in full under warn. Admission reserves the
request's estimated tokens and cost in each window, completion settles the
bucket to the audited usage, and a failed request releases its reservation, so
the usage figures are the same as they would be under enforce. A breach is
recorded as a warn under policy quota, and counted in
systemprompt_quota_denials_total{mode="warn"}, rather than answered with
HTTP 429. Treat these numbers
as warning thresholds, not ceilings. The instance has no spend cap. The
windows can also be scoped to a group, role, cost_centre, practice or
api_key, or run for a calendar month, but none of those is declared today.
An API key's own budget and request ceiling (see
User & Access Management)
are quota windows too and follow the same quota_mode. Edit them live at
/admin/gateway/policies; see
Cost Management.
Reading the measurement
Open /admin/governance. It has three tabs over one time window and
scope:
- Decisions: the chain's log.
- Safety: the scanners' log.
- Hooks: the plumbing underneath.
The strip across the top shows both planes side by side: each chain stage's
deny count beside the scanners' findings and blocks.
/admin/governance/quotas shows current usage against each quota window,
per subject, and marks any bucket past its threshold as a warning that did not
refuse the request.
From the CLI:
systemprompt infra logs governance report --since 7d
systemprompt infra logs governance report --since 7d --group-by tool
systemprompt infra logs governance report --since 30d --group-by user --format csv > warnings.csv
systemprompt infra logs trace list --decision warn --since 24h
The report has two tables. The first groups chain warnings by policy
(default), tool or user. The second covers the safety plane, one row per
category and phase, with its findings and blocked counts. A category
with many findings is one warn mode is absorbing. A category with no findings
at all is a scanner that is catching nothing.
Returning a plane to enforce
Change one plane, watch it for a few days, then move to the next.
- Fix the tunables the report blames. Examples: a secret pattern that matches test fixtures, a blocklist word that hits a harmless tool name, a quota threshold set below real usage.
- Switch the one plane.
- Chain: set
mode: enforceon a single policy underservices/governance/config.yaml, keeping the others on the inheritedwarn. Set it on the top-levelgovernance.modeonly once every stage is ready. - Scanners: set
safety.mode: enforceinservices/gateway/policies.yaml. The block lists then take effect exactly as written. - Quotas: set
quota_mode: enforce. A breach then answers HTTP 429 with aRetry-Afterand a machine-readableerror.quotabody, and the thresholds become ceilings. A request whose reserved estimate alone breaches a cost ceiling is refused before the provider is called.
- Chain: set
- Deploy it. Both files are read at boot. The gateway policy table can
also be edited live at
/admin/gateway/policies, and/admin/syncshows whether the table and the file have drifted apart. - Watch
/admin/governance. Denials now appear where warnings used to.
Tests pin the current modes and block lists, so any change to them is visible in review.
Check it yourself
/admin/governance: the KPI strip shows findings with zero blocks./admin/governance/quotas: each window's usage for the current period./admin/gateway/policies: the livedefault-quotaspolicy withquota_modeandsafety.mode.systemprompt infra logs governance report --since 7d