5.3 KiB
Scene 11 — Audit Trail: Every Decision Is Logged
Endpoint: GET /api/audit/recent
Query filters: limit, from, to, tool, agentId, decision
Expected result: Full audit trail of all previous demo scenes
Presentation Rule
Language: All AI narration and communication during scenes must be in English.
The AI must narrate every interaction with the gateway in detail. Before calling the gateway, explain what it is about to do, which endpoint it will call, what payload it will send, and what it expects to happen. After receiving the response, explain what the gateway returned, what the decision means, and why it matters. The goal is to make the audience understand exactly what is happening between the AI and the gateway at every step.
Communication Flow
sequenceDiagram
participant AI as AI Agent
participant GW as Gateway
participant AS as Audit Store
AI->>GW: GET /api/audit/recent?limit=20
GW->>GW: Validate JWT
GW->>AS: Query audit entries (tags filter)
AS-->>GW: Matching entries [{timestamp, tags, fields}, ...]
GW-->>AI: 200 OK [{timestamp, tags, fields}, ...]
AI->>GW: GET /api/audit/recent?decision=DENY
GW->>AS: Query entries where tags.decision=DENY
AS-->>GW: Filtered entries
GW-->>AI: 200 OK [denied entries...]
AI->>GW: GET /api/audit/recent?tool=jenkins.deploy
GW->>AS: Query entries where tags.tool=jenkins.deploy
AS-->>GW: Filtered entries
GW-->>AI: 200 OK [jenkins entries...]
Demo Script
Presenter says: "We've run 10 scenarios — allows, denials, fraud alerts. Every single one was logged. The audit store exposes TSDB-style queries — time range, tool, decision. The storage implementation can evolve without changing the interface."
Action 1 — Full trail
Query the endpoint:
"Show me the audit trail from the gateway — the last 20 entries"
TOKEN=$(jq -r '.access_token' /tmp/gateway-token.json)
curl -s "http://localhost:8080/api/audit/recent?limit=20" \
-H "Authorization: Bearer $TOKEN" | jq .
Action 2 — Filter by decision (only denials)
"Show me only the denied requests"
curl -s "http://localhost:8080/api/audit/recent?decision=DENY" \
-H "Authorization: Bearer $TOKEN" | jq .
Action 3 — Filter by tool
"Show me all audit entries for jenkins.deploy"
curl -s "http://localhost:8080/api/audit/recent?tool=jenkins.deploy" \
-H "Authorization: Bearer $TOKEN" | jq .
Action 4 — Filter by time range
"Show me what happened in the last 10 minutes"
curl -s "http://localhost:8080/api/audit/recent?from=2026-03-26T14:20:00%2B01:00" \
-H "Authorization: Bearer $TOKEN" | jq .
Action 5 — Filter by policyRuleId (fraud entries)
"Show me only the fraud alerts"
curl -s "http://localhost:8080/api/audit/recent?policyRuleId=fraud:sql-injection" \
-H "Authorization: Bearer $TOKEN" | jq .
Expected Response (single entry)
Each entry follows the TSDB data model — tags (indexed, low-cardinality) and fields (payload):
{
"timestamp": "2026-03-26T14:35:00+01:00",
"tags": {
"agentId": "claude-mcp-agent",
"tool": "jira.createTicket",
"action": "create",
"decision": "DENY",
"policyRuleId": "restrict-jira-projects"
},
"fields": {
"requestId": "req-abc-123",
"reason": "Project not in allowlist",
"arguments": "{\"project\":\"FINANCE\",\"summary\":\"...\"}",
"scopes": "[\"jira.read\",\"jira.write\",\"jenkins.deploy\",\"db.read\",\"db.migrate\"]"
}
}
What This Proves
- TSDB data model — explicit separation of
tags(indexed dimensions: who, what, decision) andfields(payload: arguments, reason). Swap NDJSON for TimescaleDB = map tags → indexed columns, fields → JSONB, timestamp → hypertable time column - Query by tags only — filters work on
tool,agentId,decision,policyRuleId(indexed). Fields likeargumentsare never filtered (high-cardinality payload) - Complete observability — every request, every decision, every reason, timestamped
- Fraud alerts are filterable via
policyRuleId=fraud:* - The endpoint itself is authenticated — you need a valid JWT to read audit data
Query Capabilities
| Filter | Type | Example | Use case |
|---|---|---|---|
limit |
— | ?limit=50 |
Last N entries (default 20, max 100) |
from / to |
time | ?from=...&to=... |
Time range (ISO 8601 OffsetDateTime) |
tool |
tag | ?tool=jenkins.deploy |
Filter by tool name |
agentId |
tag | ?agentId=claude-mcp-agent |
Filter by agent |
decision |
tag | ?decision=DENY |
ALLOW, DENY, or REQUIRE_APPROVAL |
policyRuleId |
tag | ?policyRuleId=fraud:sql-injection |
Filter by rule or fraud detector |
All tag filters are combinable: ?decision=DENY&tool=db.executeScript&limit=10
Key Message
"The audit store uses the same interface whether backed by a file or a time-series database. Right now it's NDJSON — append-only and easy to inspect with jq. If you later move to a TSDB, the gateway contract stays the same."
Closing
"Authentication, authorization, fraud detection, policy engine, audit. Five layers. The AI is a requesting client — the gateway decides, and every decision is recorded."