Governance
Memory audit trails, compliance scanning, and executive dashboards.
Endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /v1/governance/memory/audit | API Key | Memory change history |
| GET | /v1/governance/memory/stats | API Key | Operation counts by type |
| GET | /v1/governance/memory/compliance | API Key | PII and policy scan |
| GET | /v1/governance/sleep-time/report | API Key | Sleep-time refinement history |
| GET | /v1/governance/summary | API Key | Executive dashboard |
| POST | /v1/governance/policy/dry-run | API Key | Policy dry-run evaluation |
| GET | /v1/governance/lineage/{request_id} | API Key | The WHY answer for one request |
| GET | /v1/governance/lineage | API Key | Trace/request reconstruction |
| GET | /v1/governance/audit/chain/verify | API Key | Hash-chain + provenance verify |
Policy dry-run
Evaluate a policy context against the tenant's current ruleset without enforcement. Useful for testing policies before activating them.
curl -X POST "https://api.brainstormrouter.com/v1/governance/policy/dry-run" \
-H "Authorization: Bearer br_live_..." \
-H "Content-Type: application/json" \
-d '{"action": "POST:/v1/chat/completions", "agentId": "agent-007", "modelName": "gpt-4"}'
Request body
| Field | Type | Required | Description |
|---|---|---|---|
action | string | Yes | Action string |
actor | object | No | { type, id } actor identity |
target | string | No | Target resource path |
category | string | No | Category (e.g., routing) |
agentId | string | No | Agent identifier |
toolName | string | No | Tool name |
modelName | string | No | Model name |
Response
{
"decision": "deny",
"matchedRule": {
"id": "deny-gpt4",
"description": "Block GPT-4 usage",
"action": "deny"
},
"ruleCount": 3,
"mode": "active"
}
Returns decision: "allow" with matchedRule: null when no rules match or no ruleset is configured.
Memory audit
Query the full change history for memory operations.
curl "https://api.brainstormrouter.com/v1/governance/memory/audit?limit=50&operation=append" \
-H "Authorization: Bearer br_live_..."
Query parameters
| Param | Type | Default | Description |
|---|---|---|---|
memory_key | string | tenant ID | Memory scope |
limit | number | 100 (max 500) | Results to return |
since | number | — | Unix timestamp (ms) |
operation | string | — | Filter: append, replace, delete, pin, evict |
Response
{
"entries": [
{
"id": "audit-uuid",
"timestamp": "2025-02-20T10:30:00.000Z",
"operation": "append",
"block": "human",
"entry_id": "entry-uuid",
"fact_after": "User prefers dark mode",
"actor": "agent",
"reason": "Extracted from conversation"
}
],
"count": 1
}
Compliance scan
curl https://api.brainstormrouter.com/v1/governance/memory/compliance \
-H "Authorization: Bearer br_live_..."
{
"total_entries": 42,
"violations_found": 1,
"compliant": false,
"violations": [
{
"entry_id": "entry-uuid",
"block": "general",
"fact_preview": "Contact email is john@example...",
"issues": ["possible_email"]
}
]
}
Executive summary
curl https://api.brainstormrouter.com/v1/governance/summary \
-H "Authorization: Bearer br_live_..."
{
"memory": {
"total_entries": 42,
"pinned_entries": 5,
"blocks": [
{ "block": "human", "entries": 12, "limit": 50, "utilization": 24 },
{ "block": "project", "entries": 18, "limit": 50, "utilization": 36 }
],
"pii_violations": 1,
"compliant": false
},
"activity_24h": { "append": 8, "replace": 2, "delete": 1 }
}
Sleep-time report
curl "https://api.brainstormrouter.com/v1/governance/sleep-time/report?since=1708387200000" \
-H "Authorization: Bearer br_live_..."
Decision lineage
Durable, tamper-evident decision lineage bound to the audit chain. See Committed Lineage for the model and Decision Provenance — Chain Payload v2 for the normative commitment + test vectors. All three routes require governance.read.
The WHY answer
GET /v1/governance/lineage/{request_id} returns the committed audit row's verified block (chain hashes + a provenance recompute), the principal block (who acted and who is accountable — see _Owner binding_ below), the synchronous policy_context deny reason, the Inc 3 grant_context pointer, the durable lineage row + its causal neighbors, the coverage enum, and any anomalies. The response echoes the request's traceparent and x-br-request-id headers.
curl "https://api.brainstormrouter.com/v1/governance/lineage/req_01HXXXXXX" \
-H "Authorization: Bearer br_live_..."
{
"request_id": "req_01HXXXXXX",
"coverage": "captured",
"verified": {
"outcome": "denied",
"event_hash": "…",
"chain_schema_version": 2,
"provenance_check": "ok",
"provenance_digest": "b96591a0…",
"trace_id": "0af7651916cd43dd8448eb211c80319c",
"edge_provenance": "caller_asserted",
"trace_inbound_trusted": false
},
"principal": {
"id": "agent-jwt:sales-assistant",
"auth_method": "jwt",
"spiffe_id": "spiffe://brainstorm.internal/agent/…/sales-assistant",
"owner_id": "alice@example.com"
},
"policy_context": { "reason_code": "scope_denied", "deny_rule": "no-gpt4" },
"grant_context": null,
"lineage": { "binding": "bound", "edge_label": "verified", "...": "..." },
"causal_neighbors": [],
"anomalies": ["authority_without_causation"]
}
coverage ∈ captured | sampled_out | dropped | aggregated | pre_rollout | expired_by_retention. provenance_check ∈ ok | redacted | missing_lineage | expired_by_retention | sampled_out | failed.
Owner binding — accountability on the governance artifact
GET /v1/governance/artifacts/{request_id} projects the accountable human behind the decision into a signed accountability block, and maps it to EU AI Act Art. 14 (human oversight). The block is omitted entirely — never null — when no owner is known, so artifacts minted before owner binding re-derive to byte-identical signed bodies.
"accountability": {
"agentId": "sales-assistant",
"ownerId": "alice@example.com",
"ownerSource": "agent_profile",
"evidence": "committed"
}
evidence says how well the claim is evidenced: committed means the owner sits inside the bound lineage row's decision (covered by lineage_digest → provenanceDigest → the chain, so a post-hoc edit is chain-detectable); recorded means it comes only from the non-hashed audit column. ownerSource ∈ agent_profile | api_key_user: an agent caller's owner is its profile owner_id; an API-key caller's owner is the key's user. The same resolver stamps br_principal.owner_id into the signed trust envelope, so the envelope, the audit row, and the artifact never disagree.
Agent inventory — the shadow-agent view
GET /v1/governance/agents/inventory?period=day|week|month (governance.read) classifies every caller BrainstormRouter routed for in the window against the two identity registries. No scanning of cloud accounts or repos: this is the traffic the router already observes, joined to the agent profiles and mesh identities it already keeps.
| Class | Meaning |
|---|---|
registered | the agent id has a profile; ownerless: true when no accountable owner is bound |
unregistered | an agent id seen in traffic with no profile (deleted, never enrolled, or forged) |
| untagged key | API-key traffic carrying no agent identity at all — where scripts and un-enrolled agents hide |
asserted | an X-BR-Agent-Id header the policy plane accepted for scoping, with agent_registered |
summary.shadow_ratio is (unregistered + untagged) requests / total requests — the number that falls as callers are enrolled — and is null, never 0, for an empty window. Idle registered agents are still listed (idle: true). Header assertions are verified against the profile registry on every request and stamped on the response as X-BR-Agent-Assertion: registered | unregistered; the answer is chain-committed inside the lineage row's decision.accountability (asserted_agent_id, agent_registered), which is the source the inventory reads. The asserted block is null (and notes says why) when the lineage store cannot be read — never silently empty.
{
"window": { "period": "week", "since": "…", "until": "…" },
"summary": {
"total_requests": 1240,
"registered_agents": 4,
"ownerless_agents": 1,
"unregistered_agents": 1,
"untagged_keys": 2,
"mesh_identities": 3,
"shadow_ratio": 0.18,
"shadow_requests": 223
},
"agents": [
{ "agent_id": "ghost-7", "classification": "unregistered", "request_count": 91, "…": "…" }
],
"untagged_keys": [
{
"api_key_id": "…",
"request_count": 132,
"hint": "Traffic on this key carries no agent identity. …"
}
],
"asserted": [{ "asserted_agent_id": "ghost-7", "agent_registered": false, "request_count": 91 }],
"mesh": [
{ "spiffe_id": "spiffe://brainstorm.internal/agent/…", "hostname": "reviewer", "online": true }
],
"notes": []
}
Also available as the MCP tool br_agent_inventory and via governance.agentInventory() (TypeScript) / governance.agent_inventory() (Python).
Human approval — one queue for every require_approval
A policy-pack rule (or an agent-manifest tool policy) whose action is require_approval now means the same thing on every enforcement point — the MCP governor, the tool-call firewall, and the streaming tool-call interceptor all use the same durable queue that GET /v1/approvals/pending reads. Before this, the firewall silently mapped it to a block and the interceptor severed the stream with nothing for a human to review.
For a completion tool call the client executes, the semantics are:
- The call is withheld and a
tool_callreview record is queued. The response is
403 approval_required with the approval_id, a recovery block, and an X-BR-Approval-Id header (on streams the header is set and the denial chunk names the id). Retrying before approval dedupes onto the same record.
- A reviewer with
approvals.writecallsPOST /v1/approvals/{id}/approve. The record
becomes a one-shot allowance. scope: "exact" (default) means the retry must carry the same argument hash the reviewer saw; scope: "tool" widens it to any arguments for that tool, once. The widening is recorded on the record.
- The caller retries the same request. The enforcement point finds the allowance,
consumes it (consumedAt, consumedByRequestId), and the call passes. An allowance lapses 15 minutes after approval and can never be spent twice.
Reviewers see the tool name, the enforcement surface, the requesting agent and its accountable owner, and the argument keys — never the argument values. High-risk tools (shell, SQL, exec) require a review note. The EU AI Act policy pack's Art. 14 rules use this path, so "human oversight" on a completion is now a real review, not a denial.
{
"error": {
"type": "approval_required",
"approval_id": "2f1c…",
"tool_name": "execute_sql",
"surface": "firewall",
"rule": "euaia-oversight-restricted-tool"
},
"recovery": {
"action": "escalate",
"endpoint": "POST /v1/approvals/2f1c…/approve",
"message": "A reviewer with approvals.write must approve this request … then retry the same tool call."
}
}
Trace / request reconstruction
GET /v1/governance/lineage?trace_id=… (or ?request_id=…) reconstructs a causal episode ordered by span parentage. Every record carries an edge_label (verified | claimed | unverified) derived from the row's committed fields — one wire shape in all modes. Add ?live=true to read the recent in-memory ring buffer through the same mapper.
curl "https://api.brainstormrouter.com/v1/governance/lineage?trace_id=0af7651916cd43dd8448eb211c80319c" \
-H "Authorization: Bearer br_live_..."
Chain + provenance verify
GET /v1/governance/audit/chain/verify verifies the hash chain and adds per-row provenanceChecks[] recomputing provenanceDigest from the stored columns + the bound lineage bytes. The provenance recompute is best-effort and never flips the chain result.