Guide

Provenance, Causation & Tracing

Follow durable causal edges, application context, and safe runtime projections.

Logs tell you what a process happened to say. A trace tells you which runtime spans were connected while telemetry survived. Durable provenance answers the harder question: exactly why does this committed step exist?

Keep authority, context, and projection separate

Engine authorityCausation

One total durable origin for every admitted Statechart stimulus.

External admission or one exact committed intent.
Application contextCorrelation

An optional Schema-encoded envelope carried along causal edges.

Useful context, never chart input or engine identity.
Runtime projectionTracing

Searchable spans built from safe coordinates while telemetry exists.

Rebuildable evidence, not durable authority.

These three concerns belong together because an operator will search across all of them. They remain different structures because they answer different questions. Causation explains the edge, correlation groups application work, and tracing makes selected facts convenient to observe.

Origin + context + projection

The causal explanation outlives the trace.

Follow one external admission into a committed Timer intent, then see the engine project that durable edge into runtime spans without making telemetry authoritative.

1 · Admit an external stimulus

Causation names the origin; correlation carries context.

withCorrelation Schema-encodes one flat application envelope around the admission. The engine independently derives a stable ExternalAdmission identity and stores both beside the committed step.

import { Provenance } from "@motive/motive";
import { Effect, Schema } from "effect";
import type { OrdersClient } from "../model.ts";

const Correlation = Schema.Struct({
  requestId: Schema.String,
  orderId: Schema.String,
  authorizationToken: Schema.String,
});

export const capture = Effect.fn("Orders.capture")(function* (
  client: OrdersClient,
  input: typeof Correlation.Type,
) {
  yield* client.send.Capture().pipe(Provenance.withCorrelation(Correlation, input));
});

2 · Follow one committed consequence

An intent points back to the exact commit that produced it.

When the admitted step arms CaptureTimeout, the engine assigns that Timer a canonical intent index. Its later delivery stores a CommittedIntent edge back to the owning chart hash, instance, sequence, and index while inheriting correlation.

import { Effect } from "effect";
import type { OrdersClient } from "../model.ts";

export const explainCapture = Effect.fn("Orders.explainCapture")(function* (client: OrdersClient) {
  const { entries } = yield* client.timeline({ fromSeq: 12, limit: 2 });

  return entries.map(({ snapshot, provenance }) => ({
    seq: snapshot.seq,
    causation: provenance.causation,
    correlation: provenance.correlation,
  }));
});

3 · Project into telemetry

Tracing observes durable coordinates; it does not own them.

The engine emits Statechart.execute spans with chart, instance, step, stimulus, and causation attributes. Application correlation remains opaque unless the application explicitly projects a safe subset onto its own span.

import { Provenance } from "@motive/motive";
import { Effect, Schema } from "effect";
import type { OrdersClient } from "./model.ts";

const Correlation = Schema.Struct({
  requestId: Schema.String,
  orderId: Schema.String,
  authorizationToken: Schema.String,
});

export const capture = Effect.fn("Orders.capture")(function* (
  client: OrdersClient,
  input: typeof Correlation.Type,
) {
  yield* Effect.annotateCurrentSpan({
    "orders.id": input.orderId,
    "request.id": input.requestId,
  });

  yield* client.send.Capture().pipe(Provenance.withCorrelation(Correlation, input));
});

Every admission has one durable cause

StepProvenance is stored beside each immutable committed step. Its causation field is total: the engine never writes an admitted step with an unknown or absent origin.

An ExternalAdmission means a caller crossed an engine boundary directly. The engine derives its stable admissionId from the chart's entity type, instance id, and message id. It does not trust a request header, trace id, timestamp, or log position to stand in for that identity.

A CommittedIntent means a previous committed macrostep manufactured this stimulus. It identifies the exact parent through four coordinates: chartHash, the owning chart and instance, step sequence, and canonical intentIndex. Timers, Activities, children, planned deliveries, child cancellations, retries, and parent outcomes all occupy deterministic positions in that intent population.

That distinction turns an execution history into a causal graph. A Timer firing is not merely “near” the step that armed it; its durable record points back to the exact commit and intent that made the future delivery possible.

Correlation travels without entering the chart

Applications often need context that the engine cannot invent: request ids, tenant ids, case ids, or a cross-system operation name. Provenance.withCorrelation(schema, value) establishes that context around an admission. The Schema owns validation and transformation into one flat JSON record before the engine captures it.

withCorrelation replaces the ambient envelope for its scope. annotateCorrelation shallowly merges additional encoded fields, with newer keys winning. A committed intent inherits the parent step's envelope so a Timer, Activity, child, retry, or emitted delivery does not lose application context merely because it crossed time or ownership.

Correlation never enters the deterministic fold. States, guards, Actions, and Queries cannot read it. If behavior depends on a value, model that value as a decoded event, input, or facet. Correlation is evidence about work, not hidden chart data.

The envelope may still be confidential. Durability means it can outlive the request that supplied it, so its Schema and values require the same retention, access, and minimization policy as other stored operational data.

Trace durable coordinates, not guesses

Each execution attempt creates a consumer span named Statechart.execute. The engine annotates it with chart name and hash, instance id, step sequence, message id, stimulus kind, and the matching causation coordinates. An external admission exposes its stable admission id; a committed intent exposes its parent chart, owner, sequence, and intent index.

When live parent-span context is available, the engine connects a consequence such as a Timer delivery to the attempt that scheduled it. That relationship is useful in a trace viewer, but the span relationship is not written back into StepProvenance. Restarts, sampling, exporter failure, and retention can remove telemetry without making the durable causal edge ambiguous.

Do not reconstruct causation by sorting spans or comparing timestamps. Read the stored StepProvenance; use tracing to navigate and aggregate the same coordinates quickly.

Project application fields deliberately

The engine treats the correlation envelope as opaque and does not flatten it into span attributes. That is a security boundary, not a missing convenience. An envelope can carry a token, private subject, document coordinate, or other value that must never reach a telemetry backend.

If an application field is safe and useful for search, project it explicitly on an application-owned span with Effect.annotateCurrentSpan or span attributes. Keep that projection small and named for its operational purpose. In the walkthrough, orderId and requestId are approved; authorizationToken remains durable application context and never appears in telemetry.

Explicit projection also makes redaction review possible. A change to what leaves the application is visible at the tracing boundary rather than emerging automatically from every new correlation field.

Follow causation across operational boundaries

The same coordinates continue to matter when control changes shape. A child's delivery can point to the parent's committed intent; an Activity outcome can point to the start intent that mounted it; a retry can point to the commit that scheduled that attempt. Incidents retain the failing step's provenance so Park inspection does not have to infer origin from surviving logs.

Resources deserve one qualification: provenance explains the Statechart stimulus and committed intent chain, not every internal interaction with an external provider. Provider-native audit records and application spans may add evidence, but they should join through explicit safe coordinates rather than replacing the engine's durable edge.

This is also why chart hash belongs in causation. A source file can change while an older intent is still pending. The durable edge names the exact geometry that committed it, and migration must preserve or deliberately transform that explanation rather than silently relabeling history.

Change the world

  1. Admit one event with a Schema-encoded correlation envelope and verify the committed step stores an ExternalAdmission plus the encoded application context.
  2. Let that step arm a Timer, then follow the Timer step's CommittedIntent back to the exact parent sequence and intent index.
  3. Export the two Statechart.execute spans, drop them, and prove the same causal explanation still exists in retained step provenance.
  4. Add a confidential correlation field and confirm it remains absent from telemetry until an explicit safe projection names it.

Next, Explorer assembles those exact facts into one coherent operational workbench. Versioning & Migrations owns the separate decision to change chart identity while preserving this explanation.