Skip to content

Every state change in the Tenor executor is accompanied by a provenance chain: a complete record of why the change happened, what facts were evaluated, which rules fired, what verdicts were produced, and who authorized the operation. Provenance is not optional logging --- it is a structural guarantee of the executor (obligation E6).

Why Provenance Matters

In a traditional system, you find out that an order was cancelled by looking at a status column in a database. If you want to know why, you search through application logs, hope the relevant context was logged, and reconstruct the story manually.

In Tenor, provenance is the story. The chain tells you:

  • What facts were true at the moment of evaluation
  • Which rules fired and which did not
  • What verdicts were produced and at which stratum
  • Which persona authorized the operation
  • What precondition was satisfied
  • Which entity instances transitioned and from which state to which state
  • The exact timestamp and execution ID

This chain is immutable. It is recorded atomically with the state change. It cannot be retroactively modified or deleted (short of deleting the entire deployment).

Chain Structure

A provenance chain is a layered record, from outermost (flow-level) to innermost (fact-level):

FlowOutcome
  └── StepRecord (one per step executed)
        └── OperationProvenance
              ├── PersonaBinding (who authorized this)
              ├── PreconditionResult (which verdict was checked)
              └── VerdictSet (all verdicts at time of evaluation)
                    └── VerdictProvenance (per verdict)
                          └── FactProvenance (per fact used)

FlowOutcome

The top-level record for a flow execution. Contains metadata about the flow run and references to each step that was executed.

json
{
  "provenance_chain_id": "chain_20260215_001",
  "flow_id": "release_flow",
  "execution_id": "exec_20260215_001",
  "org": "acme",
  "contract": "escrow",
  "contract_hash": "sha256:9f4a2b...",
  "started_at": "2026-02-15T14:30:00.000Z",
  "completed_at": "2026-02-15T14:30:00.047Z",
  "status": "completed",
  "steps": [
    {"step_id": "step_001", "ref": "prov_20260215_001"},
    {"step_id": "step_002", "ref": "prov_20260215_002"}
  ]
}

StepRecord

One record per flow step that executed. References the operation provenance for OperationSteps, or records the handoff for HandoffSteps.

json
{
  "step_record_id": "step_001",
  "step_name": "verify_approval",
  "step_type": "OperationStep",
  "operation": "verify_release",
  "result": "success",
  "operation_provenance": "prov_20260215_001",
  "executed_at": "2026-02-15T14:30:00.012Z",
  "duration_ms": 3
}

For HandoffSteps:

json
{
  "step_record_id": "step_003",
  "step_name": "handoff_to_seller",
  "step_type": "HandoffStep",
  "from_persona": "escrow_agent",
  "to_persona": "seller",
  "executed_at": "2026-02-15T14:30:00.025Z"
}

OperationProvenance

The core provenance record for a single operation execution. This is where the actual reasoning chain lives.

json
{
  "provenance_id": "prov_20260215_001",
  "operation": "verify_release",
  "persona": {
    "contract_persona": "escrow_agent",
    "resolved_from": "tk_live_agent_key",
    "resolution_method": "key_binding"
  },
  "precondition": {
    "expression": "verdict_present(release_approval_ok)",
    "result": true,
    "verdict_ref": "release_approval_ok"
  },
  "entity_transitions": [
    {
      "entity": "Order",
      "instance": "order_001",
      "from_state": "pending",
      "to_state": "approved",
      "transition_declared": true
    }
  ],
  "verdict_set_ref": "vs_20260215_001",
  "evaluated_at": "2026-02-15T14:30:00.008Z",
  "applied_at": "2026-02-15T14:30:00.012Z",
  "snapshot_id": "snap_20260215_001"
}

VerdictSet

The complete set of verdicts computed during the evaluation that preceded the operation. Every verdict is recorded, not just the ones used by the operation's precondition.

json
{
  "verdict_set_id": "vs_20260215_001",
  "contract_hash": "sha256:9f4a2b...",
  "evaluated_at": "2026-02-15T14:30:00.008Z",
  "verdicts": [
    {
      "name": "payment_ok",
      "value": true,
      "type": "Bool",
      "stratum": 0,
      "rule": "check_payment",
      "provenance": "vp_20260215_001"
    },
    {
      "name": "release_approval_ok",
      "value": true,
      "type": "Bool",
      "stratum": 1,
      "rule": "check_release_approval",
      "provenance": "vp_20260215_002"
    },
    {
      "name": "eligible_for_express",
      "value": false,
      "type": "Bool",
      "stratum": 1,
      "rule": "check_express_eligibility",
      "provenance": "vp_20260215_003"
    }
  ]
}

VerdictProvenance

Per-verdict record showing exactly which facts and lower-stratum verdicts contributed to this verdict.

json
{
  "verdict_provenance_id": "vp_20260215_002",
  "verdict_name": "release_approval_ok",
  "rule": "check_release_approval",
  "stratum": 1,
  "when_expression": "verdict_present(payment_ok) AND release_approved = true",
  "result": true,
  "inputs": {
    "verdicts_referenced": [
      {
        "name": "payment_ok",
        "value": true,
        "stratum": 0,
        "provenance_ref": "vp_20260215_001"
      }
    ],
    "facts_referenced": [
      {
        "name": "release_approved",
        "value": true,
        "provenance_ref": "fp_20260215_002"
      }
    ]
  }
}

FactProvenance

The leaf node of the provenance chain. Records the fact value as it was received, its declared type, and the source declaration (if any).

json
{
  "fact_provenance_id": "fp_20260215_001",
  "fact_name": "payment_received",
  "declared_type": "Bool",
  "value": true,
  "source": "api_input",
  "received_at": "2026-02-15T14:30:00.001Z"
}

For facts with declared sources:

json
{
  "fact_provenance_id": "fp_20260215_003",
  "fact_name": "account_balance",
  "declared_type": "Money",
  "value": "15000.00",
  "source": {
    "name": "banking_api",
    "adapter": "rest_json",
    "endpoint": "https://bank.example.com/accounts/{id}/balance",
    "fetched_at": "2026-02-15T14:29:59.500Z",
    "cache_ttl_seconds": 30
  },
  "received_at": "2026-02-15T14:30:00.001Z"
}

Instance Bindings

Every provenance record references specific entity instances, not just entity types. When a flow executes against Order:order_001 and Payment:pay_001, those instance IDs are recorded in the provenance chain so you can trace the complete history of a specific entity instance.

json
{
  "entity_bindings": {
    "Order": "order_001",
    "Payment": "pay_001"
  }
}

This means you can query: "Show me every provenance chain that touched Order:order_001" and get the complete audit trail for that specific order.

Instance History Query

bash
curl "https://api.tenor.run/acme/escrow/provenance?entity=Order&instance=order_001" \
  -H "Authorization: Bearer tk_live_abc123"
json
{
  "entity": "Order",
  "instance": "order_001",
  "current_state": "released",
  "history": [
    {
      "timestamp": "2026-02-15T10:00:00Z",
      "operation": "create_order",
      "from_state": null,
      "to_state": "pending",
      "persona": "buyer",
      "provenance_chain_id": "chain_20260215_000"
    },
    {
      "timestamp": "2026-02-15T14:30:00Z",
      "operation": "verify_release",
      "from_state": "pending",
      "to_state": "approved",
      "persona": "escrow_agent",
      "provenance_chain_id": "chain_20260215_001"
    },
    {
      "timestamp": "2026-02-15T14:30:00Z",
      "operation": "release_funds",
      "from_state": "approved",
      "to_state": "released",
      "persona": "escrow_agent",
      "provenance_chain_id": "chain_20260215_001"
    }
  ]
}

System-Level TriggerProvenance

When a flow starts, the trigger condition is also recorded. This captures why the flow was initiated.

json
{
  "trigger_provenance_id": "tp_20260215_001",
  "flow_id": "release_flow",
  "trigger_type": "verdict_present",
  "trigger_expression": "verdict_present(order_ready)",
  "trigger_result": true,
  "trigger_verdict": {
    "name": "order_ready",
    "value": true,
    "stratum": 2,
    "provenance_ref": "vp_20260215_005"
  },
  "triggered_at": "2026-02-15T14:30:00.000Z",
  "triggered_by": {
    "api_call": "POST /acme/escrow/flows/release_flow/execute",
    "request_id": "req_20260215_jkl012",
    "api_key_id": "key_20260215_001",
    "resolved_persona": "escrow_agent"
  }
}

The trigger provenance connects the API call (who initiated), the trigger condition (why the flow was eligible to run), and the verdict chain (what facts supported the trigger verdict).

Full Example: End-to-End Provenance Chain

Here is a complete provenance chain for releasing funds in an escrow contract. Read from bottom to top to follow the reasoning chain, or from top to bottom to follow the execution sequence.

The Contract

hcl
fact payment_received {
  type: Bool
  source: payment_gateway
}

fact release_approved {
  type: Bool
}

entity Order {
  states: [pending, approved, released]
  initial: pending
  transitions: [
    (pending, approved),
    (approved, released)
  ]
}

rule check_payment {
  stratum: 0
  when: payment_received = true
  produce: verdict payment_ok { payload: Bool = true }
}

rule check_release {
  stratum: 1
  when: verdict_present(payment_ok) AND release_approved = true
  produce: verdict release_ready { payload: Bool = true }
}

operation approve_release {
  allowed_personas: [escrow_agent]
  precondition: verdict_present(release_ready)
  effects: [(Order, pending, approved)]
  error_contract: {
    unauthorized: "Only escrow agents can approve releases."
    invalid_state: "Order is not in pending state."
    precondition_not_met: "Release conditions not met."
  }
}

The Provenance Chain

json
{
  "provenance_chain_id": "chain_20260215_full",
  "flow_outcome": {
    "flow_id": "release_flow",
    "execution_id": "exec_20260215_full",
    "status": "completed",
    "started_at": "2026-02-15T14:30:00.000Z",
    "completed_at": "2026-02-15T14:30:00.047Z"
  },
  "trigger": {
    "type": "api_invocation",
    "persona": "escrow_agent",
    "api_key": "key_20260215_001",
    "request_id": "req_20260215_full"
  },
  "steps": [
    {
      "step": "approve",
      "operation": "approve_release",
      "persona": {
        "contract_persona": "escrow_agent",
        "resolved_from": "tk_live_agent_key",
        "method": "key_binding"
      },
      "precondition": {
        "expression": "verdict_present(release_ready)",
        "met": true
      },
      "transitions": [
        {
          "entity": "Order",
          "instance": "order_001",
          "from": "pending",
          "to": "approved"
        }
      ],
      "verdicts_at_evaluation": {
        "payment_ok": {
          "value": true,
          "stratum": 0,
          "rule": "check_payment",
          "facts_used": {
            "payment_received": {
              "value": true,
              "type": "Bool",
              "source": "payment_gateway",
              "received_at": "2026-02-15T14:30:00.001Z"
            }
          }
        },
        "release_ready": {
          "value": true,
          "stratum": 1,
          "rule": "check_release",
          "verdicts_used": ["payment_ok"],
          "facts_used": {
            "release_approved": {
              "value": true,
              "type": "Bool",
              "source": "api_input",
              "received_at": "2026-02-15T14:30:00.001Z"
            }
          }
        }
      },
      "snapshot_id": "snap_20260215_full",
      "evaluated_at": "2026-02-15T14:30:00.008Z",
      "applied_at": "2026-02-15T14:30:00.012Z"
    }
  ]
}

Reading this chain, you can answer every audit question:

  • Who authorized the release? The escrow_agent persona, resolved from API key tk_live_agent_key.
  • Why was the release allowed? Because release_ready was true (stratum 1).
  • Why was release_ready true? Because payment_ok was true (stratum 0) AND release_approved was true (fact).
  • Why was payment_ok true? Because payment_received was true (fact from payment_gateway).
  • What changed? Order:order_001 transitioned from pending to approved.
  • When? Evaluated at 14:30:00.008Z, applied at 14:30:00.012Z.
  • What contract version? sha256:9f4a2b... (deployment dep_20260215_001).

Deterministic Reproducibility

Provenance chains are deterministically reproducible. Given the same contract hash, the same fact values, and the same entity states, the evaluator will produce the same verdicts and the same provenance chain. This is guaranteed by:

  • E7 (Deterministic evaluation): The evaluator is a pure function.
  • E8 (Stratum ordering): Rules evaluate in the same order every time.
  • E1 (Snapshot isolation): The entity state used for evaluation is a fixed snapshot.
  • E19 (Version pinning): The contract version is pinned to a content hash.

This means you can take any provenance chain, extract the inputs (contract hash, facts, entity states), feed them to a local evaluator, and get the same verdicts. This is useful for:

  • Audit verification: Independently verify that a state change was correct.
  • Debugging: Reproduce a production evaluation locally.
  • Testing: Assert that specific fact combinations produce expected provenance chains.
  • Compliance: Demonstrate to regulators that the system produces consistent, traceable decisions.
bash
# Reproduce a provenance chain locally
tenor evaluate escrow.tenor \
  --facts '{"payment_received": true, "release_approved": true}' \
  --entities '{"Order": {"order_001": "pending"}}' \
  --contract-hash sha256:9f4a2b...

The output will match the verdicts in the recorded provenance chain exactly.