Audit Log
What this page is
Track C — C3 exposes a joined audit surface that answers: who (principal) used which credential (attested source) to assert which fact (fact-id), and when.
Every call to POST /v1/facts writes an entry to fact_audit_log.
The audit endpoints join that table against agent_keys and facts
so you get the full identity trail in a single query.
Identity trail model
principal (entity_uri, oidc_sub)
└── attested_key_id → agent_keys(entity_uri, description)
└── fact_id → facts(entity, relation, value, scope)
entity_uri + oidc_suboidc:alice@example.com, agent:my-service); oidc_sub populated when the key was issued via OIDC.null for unattested writes.Endpoints
GET /v1/audit · paginated audit log
Query the enriched audit log with optional filters.
entity_urioidc_subsourcesource field.fact_idattestedtrue = attested entries only; false = unattested only.cursorlimitResults are ordered ts DESC, id DESC (newest first). For
oldest-first streaming, use GET /v1/audit/export.
Response (200 OK):
{
"entries": [
{
"id": "uuid",
"fact_id": "uuid",
"event_type": "assert",
"entity_uri": "oidc:alice@example.com",
"oidc_sub": "108...",
"source": "agent:coding-assistant",
"attested_key_id": "uuid-or-null",
"ts": "2026-05-03T01:00:00Z",
"attested_key_entity_uri": "agent:coding-assistant",
"attested_key_description": "prod keypair",
"fact_entity": "user:alice",
"fact_relation": "memory:context",
"fact_value_type": "str",
"fact_value_v": "working on stigmem docs",
"fact_scope": "local"
}
],
"total": 1,
"cursor": null
}
attested_key_entity_uri and attested_key_description are null
when the entry is unattested.
Examples:
# All entries for a specific principal
curl -s "http://localhost:8000/v1/audit?entity_uri=oidc:alice@example.com&limit=20" \
-H 'Authorization: Bearer stgm_...' | jq .
# Only attested writes
curl -s "http://localhost:8000/v1/audit?attested=true" \
-H 'Authorization: Bearer stgm_...' | jq '.entries[] | {entity_uri, source, fact_entity}'
# Paginate
curl -s "http://localhost:8000/v1/audit?cursor=<cursor-from-previous>" \
-H 'Authorization: Bearer stgm_...' | jq .
GET /v1/audit/facts/{fact_id} · trail for a single fact
Returns all audit entries for a specific fact, oldest first. Useful for tracing the full assert/retract history of one fact including every principal that touched it.
curl -s "http://localhost:8000/v1/audit/facts/<fact-id>" \
-H 'Authorization: Bearer stgm_...' | jq .
# → array of enriched AuditLogEntry objects, ASC order
Returns 404 if the fact does not exist. Returns an empty array []
if the fact exists but has no audit entries (possible for facts that
predate Track C).
GET /v1/audit/export · CSV compliance export
Streams a CSV with all join columns for SIEM import or compliance
archival. Supports the same filters as GET /v1/audit. Default limit
is 5000 rows; maximum is 50000.
curl -s "http://localhost:8000/v1/audit/export" \
-H 'Authorization: Bearer stgm_...' \
-o stigmem-audit.csv
# Filtered export: one principal, last 7 days
curl -s "http://localhost:8000/v1/audit/export?entity_uri=oidc:alice@example.com&limit=10000" \
-H 'Authorization: Bearer stgm_...' \
-o alice-audit.csv
CSV columns:
id, fact_id, event_type,
principal_entity_uri, principal_oidc_sub,
source,
attested_key_id, attested_key_entity_uri, attested_key_description,
fact_entity, fact_relation, fact_value_type, fact_value_v, fact_scope,
ts
Tenant scoping
Audit entries are scoped to the caller's tenant when tenant context is active.
Default installs collapse audit tenant context to "default". With
the opt-in multi-tenant plugin registered and enabled, a key provisioned for
tenant "acme" can only query audit entries where
tenant_id = 'acme'; cross-tenant audit data is not returned through
tenant-scoped audit queries.
This scoping was added in migration 012_multi_tenant.sql. Rows
written before that migration carry tenant_id = 'default'. See
Multi-Tenant Scoping
for the full isolation model.
Retention and backfill
Pre-Track-C facts have no entries
The fact_audit_log table was created by migration 006_fact_audit.sql — entries with ts prior to that migration date are absent.
No automatic retention
Log rotation / archival is not yet implemented; all entries accumulate indefinitely. Set up external archival if you need bounded storage.
Web UI
The Audit Log tab in the browser UI (/ on the node) shows the same
joined view with filters for principal, source, and attestation
status. The Export CSV button triggers GET /v1/audit/export
with any active filters applied.