Durable Flow trace context v1
Source of truth
This guide defines the internal, versioned W3C trace-context contract for PostgreSQL-backed durable Flow execution. It is an operational correlation feature: it is never authorization, scope routing, payload storage, or a substitute for PostgreSQL RLS.
Preview boundary: Slice 4 supplies the deterministic persistence and process-loss proof described here. Public hosted-runtime activation remains Slice 6; applications must not treat the reference processor as a hosted worker.
Contract
The internal flow_trace_context relation stores only validated W3C version-00 fields and durable metadata:
| Field | Rule |
|---|---|
trace_context_id |
Runtime-generated UUID, referenced through (scope_id, trace_context_id). |
traceparent |
Exactly W3C version 00, 55 characters, lower-case hexadecimal IDs, never all-zero. |
tracestate |
Optional opaque ASCII value, at most 512 characters. It is not parsed for routing. |
correlation_token |
Runtime-generated UUID exported as the only identifier-valued durable tag when validated context exists. |
cause_kind |
One of command_accepted, activity_scheduled, activity_completed, event_winner, timer_winner, or evaluation_committed. |
contract_version |
1; a future W3C wire version requires a new durable contract version. |
No baggage, payload, scope, principal, Flow/command/event ID, exception text, or raw W3C value is exported as telemetry.
Lifecycle and trust
- Command acceptance captures a valid ambient
Activity.Currentcontext. When a listener records the command producer Activity, the committed command Activity becomes the cause; otherwise the valid ambient context is retained. - A Flow turn, timer, or child Work execution starts a fresh root Activity and supplies the persisted cause as an
ActivityLinkat creation time. Waits, scheduler delays, retries, and process gaps are never span duration. - Start writes the Flow row, command/history evidence, immutable trace row, and composite trace pointers inside one transaction. A transaction rollback leaves no trace evidence.
- A valid
traceparentwith rejectedtracestatekeeps the parent and drops only state. An invalidtraceparentdrops both fields. Persisted corruption never stops Flow evaluation; it produces an unlinked execution.
Diagnostics
| Code | Safe message | Behavior |
|---|---|---|
ASDUR212 |
Trace context invalid | Drop invalid traceparent and tracestate; continue without a link. |
ASDUR213 |
Trace state rejected | Keep the valid traceparent, drop tracestate, and continue with a link. |
Diagnostics contain only the code, cause kind, and context status. Never log header values.
Fixed telemetry attributes
Only these tags are emitted by Durable instrumentation:
appsurface.durable.trace.contract_versionappsurface.durable.execution.kindappsurface.durable.trigger.kindappsurface.durable.flow.stateappsurface.durable.outcomeappsurface.durable.correlation_token(only when a validated context exists)appsurface.durable.context.status(ambientfor command capture; otherwise the committed trigger'slinked,absent, orinvalidstatus, even though the fresh execution receives a new W3C context)
Configure the canonical ForgeTrust.AppSurface source through the Observability package or a host-owned OpenTelemetry registration.
Five-minute warm local proof
With the existing PostgreSQL reference-workload prerequisites running, use an in-process listener to verify that a resumed Flow Activity contains a link:
using System.Diagnostics;
using var listener = new ActivityListener
{
ShouldListenTo = source => source.Name == "ForgeTrust.AppSurface",
Sample = static (ref ActivityCreationOptions<ActivityContext> _) =>
ActivitySamplingResult.AllData,
ActivityStarted = activity =>
{
foreach (var link in activity.Links)
{
Console.WriteLine($"{activity.OperationName}: {link.Context.TraceId}/{link.Context.SpanId}");
}
}
};
// Start the Flow with a valid ambient Activity, drive the reference processor,
// then inspect the fresh flow/timer Activity and its link.
Run the reference proof with ./Durable/verify-postgresql.sh --quick --flow. For the strict real-PostgreSQL suite, use ./Durable/verify-postgresql.sh --ci --flow.
Deployment order and retention
- Apply
0004_schedule_protocol.sql, then0005_runtime_heartbeat.sql, then the reviewed forward-only0006_flow_trace_context.sqlmigration with the migration-owner connection. Never rename or reorder an applied migration. - Re-run
configure-postgresql-roles.sqlafter both migrations so only the scoped runtime can read or write trace metadata; the global dispatcher has no access. - Deploy trace-aware binaries. Older supported binaries keep null trace pointers; a later trace-aware rollout interprets those rows as
context.absentand never backfills.
Trace rows retain the same lifecycle as their linked Flow protocol records until an explicit archival policy is introduced.