Refusals
Route:
/refusals on the dashboard. Backed by: GET /api/audit/refusals (list) and GET /api/audit/refusals/{id} (detail).
status='refused' lands on this surface. The page renders a chronological feed of incidents; expanding any row reveals the full envelope detail inline (what the agent saw, what reason fired, which categories the policy intersected).
This is the surface for two distinct workflows:
- Operator triage. A user complained the agent kept saying “I don’t have access to that.” Open Refusals, find the relevant row, see exactly what category list intersected the policy. Decide whether to widen
--pii-blockor fix the question. - Recovery quality review. Read the envelopes SchemaBrain returned. Were the
error.recoverysuggestions actually useful? Did the agent self-correct on the next turn?
What each row carries
Each refusal row in the feed shows the same fields the underlyingmcp_audit row carries:
| Field | Meaning |
|---|---|
id | The audit row’s monotonic ID. |
occurred_at | ISO 8601 timestamp. |
tool_name | The MCP tool that was called (get_metric, describe_column, etc.). |
refusal_reason | One of pii_blocked, allowlist_violation, fragment_unsafe, cost_cap_exceeded, ambiguous_resolution, schema_drift. |
pii_categories | The category set the envelope returned. On pii_blocked refusals, this is the blocked set — the intersection of policy and request that fired. |
cost_class | refused for this surface. |
error.recovery payload the agent received (the suggested tool + args to recover with), and the chain_hash that anchors the row in the audit log. (On a pii_blocked refusal the recovery rides error.recovery; the sibling follow_up_hints field is null.)
The audit row’s
pii_categories records only the blocked set — the categories the policy intersected. The full attempted (propagated) set the agent’s plan would have touched lives only on the PiiBlockedError exception object and the event bus. See audit-chain — What this is not for the forensic implication and the roadmap item.What “refused” means upstream
A refusal is not an error. It is a Charter-conformant envelope withstatus='refused' and a structured error.recovery block (the suggested tool + args). The agent receives enough information to either:
- Pick a different tool path that does not touch the blocked categories.
- Ask the user to confirm the access is intended.
- Surface a clean “this is policy” message instead of a hallucinated retry.
pii_blocked is the reason the engine actually emits; the other five are reserved, schema-valid enum values whose recovery paths land as those checks ship.
How to populate this view
In normal operation, refused rows accumulate as agents use the MCP server. To verify the surface works end-to-end on a fresh install (or to seed a demo), use the bundled seeding script:pii_blocked row to your local store. Refresh the dashboard tab and the row appears at the top of the feed.
Alternatively, configure a strict --pii-block policy on serve and ask an agent for a metric that touches a blocked category:
Empty state
The page distinguishes “no refusals because the agent never tried” from “no refusals because the policy is empty.” If the feed is empty, that is a good signal — the agent has been able to do its work without crossing a blocked category. The empty-state message in the UI says exactly that. If you expect refusals and see none, double-check:- The
--pii-blockflag on the runningserveprocess — pass--pii-block ''and it explicitly blocks nothing. - That
schemabrain indexran without--no-pii-classify— without tags, the policy has nothing to act on. - That the indexed source actually has columns the classifier would flag — heuristic classification has false negatives for cryptically-named columns.
Cross-reference into the audit chain
Every refusal is also a row in the Audit Viewer. The two surfaces share IDs — opening row 42 on the Refusals feed and row 42 on the Audit Viewer drawer renders the same underlying row, different facets:- Refusals highlights the envelope the agent received.
- Audit Viewer highlights the
chain_hashand tamper-evidence story.
Related
Structured recovery
The envelope shape every refused call returns.
PII taxonomy
The 12 categories and the catastrophic-leak subset.
Audit Viewer
Where the refused row lives in the chain.
schemabrain serve --pii-block
The flag that produces refusals in the first place.