Skip to main content

Decision Mode SIEM Correlation Recipe

This guide shows how to correlate AxonFlow Decision Mode records with the cloud audit logs of the systems your agents act on — BigQuery Cloud Audit Logs, AWS CloudTrail, Azure Monitor — inside your SIEM. The goal is a single timeline that links "AxonFlow allowed this tool call" to "the data warehouse then executed this query", so an auditor (or your own detection rules) can see both the governance decision and its downstream effect on one record.

Requires Enterprise license. Decision Mode is exposed through POST /api/v1/decide and the read-back endpoints below.

caution

This guide is engineering guidance for teams operating governed AI on AxonFlow. It is not legal advice. Validate any regulatory mapping with your legal and compliance teams.

Quickstart: ship decisions to your central store

Getting decision records into the same store as your cloud audit logs is turnkey: set two or three environment variables, no bespoke pipeline. Pick whichever producer is closer to your decision path; both emit the same date-partitioned record layout (<prefix>/<YYYY>/<MM>/<DD>/<decision_id>.json), so they land side by side.

# (A) The AxonFlow agent ships every /api/v1/decide record to a central store.
# Records carry decision_id, the request context (incl. x_session_id), the
# verdict, the evaluated policies, and the trace_id.
export AXONFLOW_AUDIT_SINK=s3
export AXONFLOW_AUDIT_S3_BUCKET=my-axonflow-audit
export AXONFLOW_AUDIT_S3_PREFIX=axonflow/decisions # optional (default shown)
# AWS region + credentials resolve via the standard chain (AWS_REGION, role, ...).

# (B) A reference PEP (the MCP Decision Mode example) ships its Layer 1 row the
# same way: to S3, or over HTTP to an OTel collector / BigQuery proxy.
export MCP_AUDIT_SINK=s3 && export MCP_AUDIT_S3_BUCKET=my-axonflow-audit
# …or: export MCP_AUDIT_SINK=http MCP_AUDIT_HTTP_URL=http://otel-collector:4318/v1/audit

Both shippers are best-effort and fail-open: a central-store outage never blocks a decision or a tool call, and the source system keeps its own audit trail while the PEP keeps local JSONL. A per-ship timeout plus a circuit breaker keep a dead sink from adding latency. Point a BigQuery / Athena / Snowflake external table at the prefix, then run the correlation queries below.

The agent exporter also emits one OpenTelemetry span per decision when AXONFLOW_OTEL_ENDPOINT is set; use that for the live, low-latency feed and the central-store records for durable, long-window correlation. The knobs (AXONFLOW_AUDIT_SINK_TIMEOUT_MS, AXONFLOW_AUDIT_SINK_BREAKER_THRESHOLD, AXONFLOW_AUDIT_SINK_BREAKER_COOLDOWN_MS, AXONFLOW_AUDIT_SINK_BUFFER) tune the defensive behavior.

The correlation primitives

Decision Mode emits three candidate join keys, ranked by precision:

  1. request.context.x_session_id (best, available v8.5.0+). When you pass a stable x_session_id in the decision request context, it is persisted on the decision record and surfaces under policy_details -> 'context' ->> 'x_session_id'. This lets you join an entire agent session to every downstream warehouse query. Context-map persistence ships in v8.5.0; on earlier releases the context value is accepted but not persisted, so use decision_id instead (see Known gotchas).
  2. decision_id (acceptable, available today). Every call to POST /api/v1/decide returns a UUID decision_id. Thread it as a header (for example X-AxonFlow-Decision-Id) into the upstream call your agent then makes, and log it on the downstream side. This gives a per-call join that works on every release.
  3. trace_id (fallback). The W3C trace ID (32-char hex) on the decision response correlates only if the downstream system also captures the inbound traceparent header. Useful when you already run distributed tracing end to end, but most managed data services do not propagate traceparent into their audit logs.

The read-back surface for the AxonFlow side is GET /api/v1/decisions, whose summary rows carry decision_id, timestamp, decision (the canonical audit verdict — allowed / blocked / redacted / needs_approval / error; normalized on read for every row as of v9.0.0), policy_id, and tool_signature. Full identity and context are available per-id through the explain endpoint and the OJK audit export.

Field mapping

The table below maps the AxonFlow Decision Mode fields to BigQuery Cloud Audit Log (cloudaudit_googleapis_com_data_access) fields. The same shape applies to CloudTrail (eventTime / userIdentity.arn / eventSource / eventName) and Azure Monitor (TimeGenerated / Caller / ResourceProvider / OperationName).

DimensionAxonFlow Decision ModeBigQuery Cloud Audit Log
Timestamptimestamp (UTC)timestamp (UTC)
Identitycaller identity (explain endpoint / audit export)protoPayload.authenticationInfo.principalEmail
Target systemtool_signature (e.g. bigquery.jobs.query)resource.type / protoPayload.serviceName
Operationtool_signature operation segmentprotoPayload.methodName
Decision / verdictdecision (canonical: allowed / blocked / redacted / needs_approval / error)protoPayload.authorizationInfo.granted
Join keydecision_id (or x_session_id, v8.5.0+)label / request attribute carrying the echoed decision_id

AxonFlow's five-field decision summary does not itself include the caller identity; for SIEM correlation the join is on decision_id, and identity is read from the cloud-provider side (principalEmail) or from the AxonFlow audit export.

Splunk (SPL)

Join the AxonFlow decision index to the GCP audit index on the echoed decision_id:

index=axonflow_decisions sourcetype=axonflow:decide
| rename decision AS axonflow_decision
| join type=inner decision_id
[ search index=gcp-cloudaudit-logs sourcetype="google:gcp:pubsub:message"
| rex field=labels "decision_id=(?<decision_id>[0-9a-f-]{36})"
| rename protoPayload.methodName AS bq_operation,
protoPayload.authenticationInfo.principalEmail AS principal ]
| table _time decision_id axonflow_decision policy_id tool_signature principal bq_operation
| sort 0 - _time

Elasticsearch / Elastic Security

An Elasticsearch query for the AxonFlow side, filtered to denied decisions on KYC tooling. The downstream join is done in your SIEM's correlation layer (Elastic Transform or an enrich policy keyed on decision_id):

{
"query": {
"bool": {
"filter": [
{ "term": { "decision": "blocked" } },
{ "wildcard": { "tool_signature": "bigquery.jobs.query*" } },
{ "range": { "timestamp": { "gte": "now-24h", "lte": "now" } } }
]
}
},
"_source": ["decision_id", "timestamp", "decision", "policy_id", "tool_signature"],
"sort": [ { "timestamp": { "order": "desc" } } ]
}

BigQuery SQL

When AxonFlow decision records are in BigQuery (loaded from the central store the Quickstart populates via an external table over the S3/GCS prefix or a scheduled load job, via the OTel collector's BigQuery exporter, or by a direct export of the decision_chain table), join them to Cloud Audit Logs directly:

SELECT
d.timestamp AS decided_at,
d.decision_id,
d.decision,
d.policy_id,
d.tool_signature,
a.protoPayload.authenticationInfo.principalEmail AS principal,
a.protoPayload.methodName AS bq_operation
FROM `axonflow.audit.decisions` AS d
JOIN `axonflow.audit.cloudaudit_googleapis_com_data_access` AS a
ON d.decision_id = a.labels.axonflow_decision_id
WHERE d.timestamp BETWEEN TIMESTAMP('2026-01-01') AND TIMESTAMP('2026-12-31')
AND d.decision = 'allowed'
ORDER BY decided_at DESC;

Anomaly alert templates

These map to Layer 4 of the four-layer audit framework — behavioral alerting on the downstream warehouse activity that AxonFlow governed. Tune the thresholds to your baseline.

Splunk — query volume > 3× baseline:

index=gcp-cloudaudit-logs protoPayload.methodName="jobservice.jobcompleted"
| timechart span=1h count AS hourly
| eventstats avg(hourly) AS baseline
| where hourly > 3 * baseline

Elasticsearch — full-table scan on a KYC table (no row filter):

{
"query": {
"bool": {
"filter": [
{ "match_phrase": { "protoPayload.metadata.tableDataRead.fields": "nik" } }
],
"must_not": [
{ "exists": { "field": "protoPayload.metadata.jobChange.job.queryFilter" } }
]
}
}
}

BigQuery — off-hours (WIB 22:00–07:00) or bulk retrieval > 500 records:

SELECT principalEmail, event_hour_wib, rows_read
FROM (
SELECT
protoPayload.authenticationInfo.principalEmail AS principalEmail,
EXTRACT(HOUR FROM timestamp AT TIME ZONE 'Asia/Jakarta') AS event_hour_wib,
CAST(protoPayload.metadata.tableDataRead.recordsRead AS INT64) AS rows_read
FROM `axonflow.audit.cloudaudit_googleapis_com_data_access`
)
WHERE event_hour_wib >= 22 OR event_hour_wib < 7 OR rows_read > 500;

Pasal 56(b) attestation surfacing

For UU PDP cross-border transfers, the OJK audit export reports each transfer's recorded legal basis. The explicit Pasal 56(b) (binding legal instrument / DPA) tag is transfer_basis = "pasal_56b_dpa"; the generic safeguards value is its recognized semantic equivalent. Both are surfaced verbatim — never auto-translated — so a regulator-facing report shows exactly the value recorded at decision time.

Pull the cross-border records and filter on the Pasal 56(b) tag:

curl -X POST http://localhost:8080/api/v1/ojk/audit/export \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: your-tenant" \
-d '{
"start_date": "2026-01-01",
"end_date": "2026-12-31",
"data_types": ["cross_border_transfers"],
"framework": "UU_PDP"
}'

Each cross_border_transfers record carries transfer_basis, data_residency, destination_country, and timestamp. In your SIEM, filter on transfer_basis IN ("pasal_56b_dpa", "safeguards") to produce the DPA-backed-transfer evidence set for an auditor. See the UU PDP Compliance Guide for the full Pasal 56 workflow.

Known gotchas

  • Pre-v8.5.0, x_session_id is not persisted. The decision request accepts a context map, but values are not written to the decision record before v8.5.0. On earlier releases, correlate on decision_id instead of x_session_id.
  • BigQuery Cloud Audit Log retention is 400 days maximum (the _Default log bucket). AxonFlow audit retention is configurable (Indonesia compliance floor is 1825 days / 5 years). Export AxonFlow records to a longer-lived store if your correlation window must exceed 400 days.
  • Normalize time zones to UTC. BigQuery Cloud Audit Logs and AxonFlow decision records are both UTC. Convert to WIB (Asia/Jakarta, UTC+7) only at the alert-evaluation step, as in the off-hours template above — never store WIB-local timestamps.