1
0
simple-ai-gateway-tool/scenes/11-audit-trail.md
Nilton Constantino d48172539a
first commit
2026-04-22 14:55:58 +01:00

153 lines
5.3 KiB
Markdown

# 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
```mermaid
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"
```bash
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"
```bash
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"
```bash
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"
```bash
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"
```bash
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):
```json
{
"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) and `fields` (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 like `arguments` are 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."