Skip to content
Audit log

One log. Every action. Every actor. Every module.

Every user-performable operation in Odexy is a typed action. Every action writes one row to audit_log with the actor + input hash + result. UI, AI agent, MCP server, and CLI all use the same action registry — one write path means one log, no matter how the write got in.

One write path

No matter how the write got in, one row lands.

Every entry point — the web UI a human clicks, the AI agent via MCP, the developer's cURL against the API, the operator's CLI script, an inbound webhook, a scheduled cron — reaches the same action handler through the action registry. The handler is the only place `audit_log` gets written. Which means every write is:

  • Actor-attributed. The row carries the original user + the tool that invoked (`web` / `mcp` / `api` / `cli` / `webhook` / `cron`) + the AI token id if the AI was the tool.
  • Input-hashed. A SHA-256 of the JSON input + a truncated preview lets you find "the deal-update that changed the amount from $50k to $500k" without scanning every row.
  • Result-tagged. Success + error code (Zod validation / policy denied / not_found / etc.) stored on the row. Failed writes still log — no silent drops.
  • Event-linked. The audit row cross-references the domain event that fired (`crm.deal.won`, `sales.invoice.issued`, etc.) so you can replay the exact event chain that led to any state.
  • Idempotency-key-stamped. When the caller supplied an `Idempotency-Key`, it's on the row. Duplicate keys within the 24h window return the original row + a dedup marker rather than double-writing.
Hash chain

Retro-editing history is detectable.

Every night we compute a SHA-256 over the day's audit rows, chained onto yesterday's head. The chain-head is exposed to admins in-product + streamed to your SIEM. If someone tries to alter a row after the fact — including someone with DB access — the recomputed hash won't match, and the mismatch surfaces in the daily audit-integrity report.

What the chain looks like

day_hash = SHA256(prev_day_hash || day_rows_sorted_by_id_serialized)

# Yesterday: sha256:83b4e...cd91
# Today:     sha256:d7a2c...5f08  # includes yesterday's hash
# Tomorrow:  sha256:f102b...9e77  # includes today's hash

Verify externally: hash the exported rows offline; compare to the chain head published in-product. Mismatch = investigate.

SIEM export

Ship the log to Splunk / Datadog / Sumologic / your data lake.

  • Formats: CSV, JSONL, Parquet. Every export stream carries the chain-head + a manifest so downstream verification is trivial.
  • Delivery: signed URL (pull), scheduled S3 sync (push), or streaming HTTP (long-poll). Cadence per org: real-time, hourly, daily, weekly.
  • Filtering: per-module / per-actor / per-event- class / date range. Sensitive fields optionally redacted at export time (PII scrubbing before it leaves).
  • Replay-friendly. Every row carries the event class + payload hash, so a replay tool can reconstruct the state at any timestamp.
  • Retention. 12 months in-product on Business, 7 years on Enterprise. Beyond that, exports live in your SIEM or your S3.
Compliance mappings

What frameworks the audit log actually helps you pass.

  • SOC 2 CC7 (System Operations). Continuous monitoring evidence; per-user access logs; anomaly detection signals — all first-class.
  • HIPAA §164.312(b) (Audit Controls). Every PHI access + every PHI change auditable with actor + input + result. BAA-friendly on Enterprise.
  • GDPR Article 30 (Records of processing). The audit log IS your Article 30 record — per-operation actor + purpose + retention are queryable.
  • PCI-DSS 10 (Track and monitor access). Applies where you touch cardholder data. We route cardholder data through PCI-Level-1 gateways; the audit log tracks the surrounding operations (refund, dispute, subscription change).
  • ISO 27001 A.12.4 (Logging and monitoring). Meets the "protected log" control class — hash-chained + integrity-verified + retention-policy-attached.
AI + audit

Every AI-driven action is auditable as the delegating operator's action.

Because the AI operates through the same action registry as humans, every AI-driven write is one audit_log row. The row carries BOTH the delegating operator's user id + the AI token that invoked. Filter "everything the AI did last week" or "everything Jane did — including via AI." No parallel audit surface. No "AI wing" of your SIEM.

Sample row

{
  "id": "01H...",
  "occurredAt": "2026-08-29T14:32:11.123Z",
  "orgId": "01H...",
  "actorUserId": "01H...jane",
  "actorTool": "mcp",
  "aiTokenId": "ai_tok_01H...",
  "action": "crm.deal.update",
  "inputHash": "sha256:83b4e...",
  "inputPreview": "{\"dealId\":\"01H...\",\"stage\":\"negotiation\"}",
  "result": "ok",
  "eventClass": "crm.deal.stage_changed",
  "idempotencyKey": null
}
Audit

Wire your SIEM up.

Every action; every actor; every module; one log.

Subscribe to the Odexy changelog.

One email every ~2 weeks. Honest product notes, no marketing pitches.

We email you only when there's something honest to say. Unsubscribe in one click.