Provenance
Core Concepts

Unit of Work

Correlate related interactions across distributed transactions.

A Unit of Work (UOW) is a UUID that links related interactions together — even when they happen in different services at different times.

How it works

Pass the x-uowid header on every interaction that belongs to the same logical operation:

# Service A: Create order
curl -X POST https://provenance.onrender.com/api/interactions \
  -H "x-api-key: your-api-key" \
  -H "x-uowid: 550e8400-e29b-41d4-a716-446655440000" \
  -H "Content-Type: application/json" \
  -d '{
    "resourceId": "order-789",
    "resourceType": "ORDER",
    "action": "CREATE",
    "origin": "WEBAPP",
    "interaction": { "total": 99.99 }
  }'

# Service B: Process payment
curl -X POST https://provenance.onrender.com/api/interactions \
  -H "x-api-key: your-api-key" \
  -H "x-uowid: 550e8400-e29b-41d4-a716-446655440000" \
  -H "Content-Type: application/json" \
  -d '{
    "resourceId": "payment-456",
    "resourceType": "PAYMENT",
    "action": "PROCESS",
    "origin": "PAYMENT_SVC",
    "interaction": { "method": "card", "last4": "4242" }
  }'

# Service C: Update inventory
curl -X POST https://provenance.onrender.com/api/interactions \
  -H "x-api-key: your-api-key" \
  -H "x-uowid: 550e8400-e29b-41d4-a716-446655440000" \
  -H "Content-Type: application/json" \
  -d '{
    "resourceId": "sku-001",
    "resourceType": "INVENTORY",
    "action": "UPDATE",
    "origin": "INVENTORY_SVC",
    "interaction": { "quantity": -1 }
  }'

All three interactions are now linked by the same UOW ID.

Auto-generation

If you don't provide x-uowid, the API generates one automatically. This means every interaction always has a UOW ID — you just need to provide your own when you want to correlate multiple interactions.

Querying by UOW

Find all interactions in a unit of work:

GET /api/interactions?uowId=550e8400-e29b-41d4-a716-446655440000

Or via the activity endpoint:

POST /api/activity
{
  "resources": [
    { "resourceId": "550e8400-e29b-41d4-a716-446655440000", "resourceType": "ORDER" }
  ]
}

Using the CLI

# Generate a UOW ID
UOWID=$(provenance uow new)

# Use it across multiple track commands
provenance track -r order-789 -t ORDER -a CREATE -u $UOWID \
  -d '{"total":99.99}'

provenance track -r payment-456 -t PAYMENT -a PROCESS -u $UOWID \
  -d '{"method":"card"}'

provenance track -r sku-001 -t INVENTORY -a UPDATE -u $UOWID \
  -d '{"quantity":-1}'

Using the SDK

Queue ingestion

const { v4: uuidv4 } = require('uuid');

const uowId = uuidv4();

await provenance.log({
  resourceType: 'order', resourceId: 'order-789',
  action: 'create', uowId,
  interaction: { total: 99.99 }
});

await provenance.log({
  resourceType: 'payment', resourceId: 'payment-456',
  action: 'process', uowId,
  interaction: { method: 'card' }
});

REST API client

const api = await provenance.api();

// Find all interactions in a unit of work
const results = await api.activity.search({
  filters: { uowId: '550e8400-e29b-41d4-a716-446655440000' },
});

When to use UOW

  • Multi-step transactions — order → payment → fulfillment → notification
  • CI/CD pipelines — build → test → deploy → verify
  • Data pipelines — extract → transform → load → validate
  • User journeys — signup → verify email → complete profile → first login
  • Any operation that spans multiple services or resources

Span hierarchy

While UOW IDs group interactions into a flat set, span IDs add tree structure within a trace. Every interaction has a spanId (auto-generated UUID) and an optional parentSpanId that points to the spanId of the interaction that caused it.

Think of it this way:

  • uowId = "these interactions belong to the same logical operation" (flat grouping)
  • spanId / parentSpanId = "this interaction was triggered by that interaction" (tree structure)

How it works

Every call to log() (SDK), track (CLI), or POST /api/interactions automatically generates a spanId. To build a tree, pass the parent's spanId as parentSpanId on the child interaction.

# API: use x-span-id and x-parent-span-id headers
curl -X POST https://provenance.onrender.com/api/interactions \
  -H "x-api-key: your-api-key" \
  -H "x-uowid: 550e8400-e29b-41d4-a716-446655440000" \
  -H "x-parent-span-id: <parent-span-id>" \
  -H "Content-Type: application/json" \
  -d '{
    "resourceId": "payment-456",
    "resourceType": "PAYMENT",
    "action": "PROCESS",
    "origin": "PAYMENT_SVC",
    "interaction": { "method": "card" }
  }'

SDK examples

log() returns the generated spanId, which you can pass as parentSpanId to child interactions:

const { v4: uuidv4 } = require('uuid');

const uowId = uuidv4();

// Parent interaction — log() returns the spanId
const parentSpanId = await provenance.log({
  resourceType: 'order', resourceId: 'order-789',
  action: 'create', uowId,
  interaction: { total: 99.99 },
});

// Child interaction — linked via parentSpanId
await provenance.log({
  resourceType: 'payment', resourceId: 'payment-456',
  action: 'process', uowId,
  parentSpanId,
  interaction: { method: 'card' },
});

Context layering with parentSpanId

Use addContext() to propagate parentSpanId to all subsequent calls from a scoped client:

const parentSpanId = await provenance.log({ ... });

const child = provenance.addContext({ parentSpanId });
// All log() calls on `child` will include this parentSpanId
await child.log({ resourceId: 'item-1', action: 'reserve', ... });
await child.log({ resourceId: 'item-2', action: 'reserve', ... });

CLI examples

# Track with a parent span
PARENT=$(provenance track -r order-789 -t ORDER -a CREATE -u $UOWID -d '{"total":99.99}')
provenance track -r payment-456 -t PAYMENT -a PROCESS -u $UOWID \
  --parent-span $PARENT -d '{"method":"card"}'

# View trace as a tree
provenance trace $UOWID --tree

# Include span IDs in output
provenance trace $UOWID --spans

Notification auto-linking

When a notification subscriber is triggered by an interaction, the resulting QUEUED/notified/notify-err interactions automatically have their parentSpanId set to the triggering interaction's spanId. This means notification chains appear as children in the span tree — no manual linking needed.

Inbound webhooks

Inbound webhook interactions also participate in the span hierarchy. A spanId is auto-generated for every inbound interaction, just like SDK and CLI interactions. If the external system includes a correlation ID in its payload, you can map it via metadataPaths:

{
  "metadataPaths": {
    "uowId": "$.data.object.payment_intent",
    "spanId": "$.data.object.metadata.traceId",
    "parentSpanId": "$.data.object.metadata.parentTraceId"
  }
}

If spanId is not mapped or not found in the payload, one is generated automatically. This means any notifications triggered by an inbound interaction will correctly appear as children in the trace tree. See Inbound Mappings — Metadata paths for details.

Visualizing the tree

The Trace Viewer renders span hierarchies as an expandable tree timeline and an interactive graph view. Root interactions (those without a parentSpanId) appear at the top level, with children nested below.