FabricFabricPlatform
Platform referenceGovernance

Audit trail

What the platform records about every attempted action, and how to read it.

The audit trail is not a separate logging system. It is a direct readout of three platform tables that are populated by the mutation pipeline as a side effect of normal operation.

The three tables

TablePopulated whenWhat it carries
ActionInvocationEvery invokeAction callactor, parameters, status (pending → terminal), correlation/causation, error message
PolicyEvaluationEvery policy that runs during an invocationpolicy ID, version, kind, result, reason, full dispatchEvidence
AssetEventEvery domain event emitted by a handler/sagatyped payload, subjectType/subjectId, sequence, actor info, actionInvocationId link

Every action is one ActionInvocation row + zero-or-more PolicyEvaluation rows + zero-or-more AssetEvent rows. They share correlationId so a single SQL WHERE correlationId = … reconstructs everything.

What you can answer

QuestionQuery
Who took which actions in this tenant in the last 30 days?ActionInvocation filtered by tenantId, createdAt
Why was this invocation blocked?PolicyEvaluation WHERE actionInvocationId = …
What was the parameter shape of an action 6 months ago?ActionInvocation.parameters is preserved verbatim
What events did this saga produce?AssetEvent WHERE correlationId = …
Did agents take any actions in the last week?ActionInvocation WHERE actorType = 'agent'
Did anyone bypass policy X?They couldn't — but you can confirm by joining PolicyEvaluation

What auditors get for free

Because every mutation flows through the pipeline:

  1. No "shadow writes." Boundary tests (packages/api/modules/*/boundary.test.ts) reject direct ORM mutations from API or UI code. There is no second path.
  2. Failed attempts are persisted. A validation_failed, blocked_by_policy, or failed invocation creates an audit row. Discarded attempts do not exist.
  3. Decisions are inspectable. dispatchEvidence records exactly which conditions evaluated, which evaluator ran, and which fallback triggered. "Why" is structured, not narrative.
  4. Causality is preserved. Sagas link child invocations via causationId. A reviewer can walk the tree from a top-level user action to every downstream side effect.
  5. Time travel. The AssetEvent log replays. State at any historical timestamp is reconstructible.

Evidence packets

For deeper compliance use cases, the platform's evidence package (packages/platform/evidence/index.ts) defines an EvidencePacketEnvelope:

interface EvidencePacketEnvelope<TPayload> {
  id: string;                 // cep_<ULID>
  tenantId: string;
  spaceId: string;
  packetType: string;         // e.g. "credit_pull_consent_packet"
  status: "draft" | "sealed" | "superseded";
  subjects: EvidencePacketSubject[];
  references: EvidencePacketReference[];   // → action_invocation, asset_event, etc.
  payload: TPayload;
  generatedAt: Date | string;
  generatedBy: string;
  policyVersion?: string;
  checksum?: string;
  correlationId: string;
  causationId?: string;
}

A packet is a curated bundle of references the platform can hand to a regulator: "here is the consent record, the credit pull invocation, the policy evaluations that allowed it, and the resulting events." Packets are produced by registered evidenceRenderers declared on a FabricModule.

Packets have status — draft (under construction), sealed (immutable, the official record), superseded (replaced by a newer sealed packet, retained).

Query patterns

-- Reconstruct everything that happened in one user request
SELECT *
FROM action_invocation, policy_evaluation, asset_event
WHERE correlation_id = $1
ORDER BY created_at;

-- All blocked attempts for a tenant
SELECT ai.*, pe.policy_id, pe.reason
FROM action_invocation ai
JOIN policy_evaluation pe ON pe.action_invocation_id = ai.id
WHERE ai.tenant_id = $1 AND ai.status = 'blocked_by_policy';

-- Per-actor activity heatmap
SELECT actor_type, action_id, COUNT(*)
FROM action_invocation
WHERE tenant_id = $1 AND created_at >= NOW() - INTERVAL '30 days'
GROUP BY 1, 2;

See also

On this page