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

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) 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."