Skip to content

Audit Log

In a Continuous History system, the audit trail was always there — it’s not a separate feature, it’s inherent. Ironflow captures every state mutation as an immutable recorded fact: run creation, status change, step start, step completion, step failure, history correction (hot-patch), and saga compensation. This is history inspection — querying what your system recorded.

Key Concepts

ConceptDescription
Audit EventAn immutable record of a single state mutation in a workflow run.
RecordingPer-function toggle that controls whether audit events are captured.
Recording RetentionPer-function retention period for audit events (7d, 30d, 90d, forever).
Audit TrailThe ordered sequence of audit events for a run, queryable by type and time range.

Event Types

Every audit event has a event_type that identifies what happened:

Event TypeWhenKey Payload Fields
run.createdA new run is createdrunId, functionId, input, eventId
run.status_changedRun status transitionsrunId, oldStatus, newStatus, reason
step.startedA step begins executionstepId, stepName, stepType
step.completedA step finishes successfullystepId, output, durationMs
step.failedA step failsstepId, error, attempt
step.patchedA step output is hot-patchedstepId, originalOutput, patchedOutput, patchedBy
saga.compensation.startedA compensation step beginsstepId, compensationTargetStep
saga.compensation.completedA compensation step succeedsstepId, result
saga.compensation.failedA compensation step failsstepId, error, attempt
step.injectedStep output injected during scoped injectionstepId, previousOutput, newOutput, reason
run.pausedRun paused at step boundaryrunId, oldStatus, newStatus, reason
run.resumedRun resumed after pauserunId, oldStatus, newStatus, reason
debounce.cancelledDebounce window closed without invocationeventId, functionId, debounceKey
environment.createdEnvironment createdenv_id, env_name, project_id, org_id, actor_id, change_type
environment.deletedEnvironment deletedenv_id, env_name, project_id, org_id, actor_id, change_type

Enabling Recording

Recording is configured per-function. When recording is off, no audit events are captured. Existing events are preserved.

import { createFunction } from "@ironflow/node";
const processOrder = createFunction(
{
id: "process-order",
recording: true,
recordingRetention: "90d",
triggers: [{ event: "order.placed" }],
},
async ({ event, step }) => {
const validated = await step.run("validate", async () => {
return validateOrder(event.data);
});
// Every step is recorded in the audit trail
return validated;
},
);

Retention Options

ValueDescription
7dKeep audit events for 7 days
30dKeep audit events for 30 days (default) — retention value is stored but automatic purging is not yet implemented
90dKeep audit events for 90 days
foreverNever automatically delete audit events

Querying the Audit Trail

Dashboard

Navigate to Audit Log in the left sidebar to browse audit events across all runs. You can filter by:

  • Function — select a specific function from the dropdown
  • Event type — filter by step.completed, run.created, etc.
  • Run ID — search for events from a specific run

From any run detail page, click the Audit Log button to jump directly to that run’s audit events.

CLI

Terminal window
# View audit trail for a run
ironflow audit trail <run-id>
# Filter by event type
ironflow audit trail <run-id> --type step.completed
# Time range filtering
ironflow audit trail <run-id> --from 2025-01-01T00:00:00Z --to 2025-01-02T00:00:00Z
# Limit results
ironflow audit trail <run-id> --limit 100
# JSON output (for piping to jq, etc.)
ironflow audit trail <run-id> --json

Example output:

TIMESTAMP EVENT TYPE STEP ID PAYLOAD
10:23:01.123 run.created {"runId":"run-abc","functionId":"process-order"...}
10:23:01.145 step.started step-1 {"stepId":"step-1","stepName":"validate","stepType":"invoke"}
10:23:01.312 step.completed step-1 {"stepId":"step-1","output":{"valid":true},"durationMs":167}
10:23:01.315 step.started step-2 {"stepId":"step-2","stepName":"charge","stepType":"invoke"}
10:23:01.520 step.completed step-2 {"stepId":"step-2","output":{"charged":true},"durationMs":205}
10:23:01.522 run.status_changed {"runId":"run-abc","oldStatus":"running","newStatus":"completed"}
Total: 6 events

REST API

Terminal window
# Get audit events for a specific run
curl http://localhost:9123/api/v1/runs/<run-id>/audit
# List audit events across all runs (with optional filters)
curl "http://localhost:9123/api/v1/audit?function_id=process-order&event_type=step.failed&limit=50"
# Paginate with cursor
curl "http://localhost:9123/api/v1/audit?cursor=<next_cursor>&limit=50"

Query parameters:

ParameterDescription
function_idFilter by function ID
run_idFilter by run ID
event_typeFilter by event type
fromStart timestamp (RFC3339)
toEnd timestamp (RFC3339)
limitMaximum events to return (default: 100)
cursorPagination cursor from previous response

Go SDK

client := ironflow.NewClient(ironflow.ClientConfig{
ServerURL: "http://localhost:9123",
})
result, err := client.GetAuditTrail(ctx, "run-abc123", ironflow.GetAuditTrailOpts{
EventType: "step.completed",
Limit: 50,
})
if err != nil {
log.Fatal(err)
}
for _, event := range result.Events {
fmt.Printf("%s %s %v\n", event.CreatedAt, event.EventType, event.Payload)
}
// Paginate
if result.NextCursor != "" {
nextPage, _ := client.GetAuditTrail(ctx, "run-abc123", ironflow.GetAuditTrailOpts{
Cursor: result.NextCursor,
})
// ...
}

Metadata

Note: The metadata column on audit events is reserved for future use. There is currently no user-facing API to attach custom metadata to individual audit events.


How It Works

The audit recorder is integrated directly into the Ironflow engine. When a function has recording: true, the engine writes an audit event to the database at each lifecycle point — run creation, status transitions, step starts, completions, failures, hot-patches, and saga compensations.

Event triggers run
┌─────────────────────────────────┐
│ Engine executes function │
│ │
│ run.created ──► audit_events │
│ step.started ──► audit_events │
│ step.completed ──► audit_events│
│ run.status_changed ──► ... │
└─────────────────────────────────┘
Query via API / CLI / Dashboard

Key characteristics:

  • Append-only — events are never modified or deleted (except by retention policy)
  • Non-blocking — audit write failures are logged but never block workflow execution
  • Time-ordered IDs — audit events use ULID identifiers for natural time ordering
  • Per-function caching — the engine caches each function’s recording status to avoid repeated database lookups

Auth Audit Trail

In addition to workflow audit events, Ironflow can record an audit trail of every authorization decision and auth-related mutation. This is useful for compliance, debugging access issues, and security auditing.

Auth Event Types

Event TypeWhenKey Payload Fields
authz.decision.allowedAuth middleware grants accessapi_key_id, action, resource, environment, method, path, decision, policy_id (conditional), impersonated_org_id (conditional)
authz.decision.deniedAuth middleware denies accessapi_key_id, action, resource, environment, method, path, reason, decision, policy_id (conditional), impersonated_org_id (conditional)
authz.key.createdAPI key createdapi_key_id, key_name, org_id, actor_id
authz.key.revokedAPI key deleted or rotatedapi_key_id, key_name, org_id, actor_id
authz.role.changedRole created, updated, or deletedrole_id, role_name, org_id, change_type, actor_id
authz.policy.changedPolicy created, updated, or deletedpolicy_id, policy_name, org_id, change_type, actor_id

Auth events use run_id="" and function_id="" since they are not workflow-scoped. The metadata JSON includes org_id and api_key_id (for decision events) for filtering. actor_id lives in the payload.

Querying Auth Audit Trail

CLI

Terminal window
# View auth audit trail for an organization
ironflow audit auth-trail --org <org_id>
# Filter by API key
ironflow audit auth-trail --org <org_id> --key <api_key_id>
# Filter by action
ironflow audit auth-trail --org <org_id> --action "functions:invoke"
# Time range
ironflow audit auth-trail --org <org_id> --from 2026-01-01T00:00:00Z --to 2026-01-02T00:00:00Z
# Limit results and JSON output
ironflow audit auth-trail --org <org_id> --limit 50 --json

Example output:

TIMESTAMP EVENT TYPE DECISION PAYLOAD
10:23:01.000 authz.decision.allowed allowed {"api_key_id":"key-1","action":"functions:invoke"...}
10:23:02.000 authz.decision.denied denied {"api_key_id":"key-2","action":"admin:manage","reason":"..."}
10:24:00.000 authz.role.changed {"role_id":"role-1","change_type":"created"...}
Total: 3 events

ConnectRPC API

The GetAuthAuditTrail RPC is available on the AuditService:

Terminal window
# Using buf curl (ConnectRPC)
buf curl --data '{"org_id": "org-123", "limit": 50}' \
http://localhost:9123/ironflow.v1.AuditService/GetAuthAuditTrail

Request fields:

FieldDescription
org_idRequired. Organization ID to query
api_key_idFilter by API key
actionFilter by action (e.g., functions:invoke)
from_timestampStart timestamp (RFC3339)
to_timestampEnd timestamp (RFC3339)
limitMaximum events to return (default: 100)
cursorPagination cursor from previous response

Configuration

Auth audit logging writes every authorization decision synchronously to the audit_events table. Write failures are logged but do not block the response path.

Verbosity filtering (ExcludeActions) is supported internally but not yet exposed through CLI flags or configuration.


What’s Next?

  • Time-Travel Debugging — audit events power time-travel debugging, letting you inspect run state at any historical point in time
  • Workflows — learn about functions, steps, and execution modes
  • Sagas — compensation patterns that generate saga.compensation.* audit events
  • Debugging — use the TUI debugger alongside the audit trail