Skip to content
VerifAIer
Home / Docs / Engineering / Evidence & receipt
Engineering · Lifecycles

Evidence & receipt lifecycle

The Evidence Envelope is the one object every capability consumes. Here is exactly how it, and its receipt and provenance, are built.

Evidence lifecycle

An Evidence Envelope is the immutable, canonical record of one completed AI operation. It sits above the Provider Router and consolidates a provider result plus all operational metadata into a single serializable structure. The pipeline is a straight line:

ProviderRouter → RoutingResult → EvidenceEngine.build → EvidenceEnvelope → API response
src/vailidator/evidence/engine.py · envelope.py
flowchart LR
  RR["RoutingResult
(ProviderResult + routing metadata)"] --> EE["EvidenceEngine.build(operation)"] EE --> T["created_at = clock()
ids: ev_ / rcpt_ / prov_"] T --> RC["Receipt.from_routing_result()"] T --> PV["Provenance.create(context)"] T --> EV["EvidenceEnvelope.from_routing_result()"] RC --> EV PV --> EV EV --> OUT["EvidenceEnvelope
provider · routing · execution · diagnostics
evidence · receipt · provenance · replay"]
Evidence assembly, one build() call, one shared timestamp, three linked records. Source: evidence/engine.py.

The EvidenceEngine is not a provider and makes no model calls, it only assembles evidence. A single build() call mints a shared created_at and three ids (ev_ rcpt_ prov_), then constructs the Receipt, the Provenance and the Envelope so all three agree on time and linkage. Clock and id factory are injectable, which is what makes the whole layer replay-safe and testable.

Envelope structure

The envelope is a frozen dataclass with eleven top-level blocks. It is built by EvidenceEnvelope.from_routing_result(...), which flattens the routing result into execution, copies the provider's already-safe diagnostics, and stamps the identifier block:

flowchart TB
  ENV["EvidenceEnvelope"] --> P["provider, ProviderResult.to_dict()"]
  ENV --> R["routing, requested/selected/attempted/skipped/fallback"]
  ENV --> X["execution, flattened operational summary"]
  ENV --> D["diagnostics, safe provider metadata"]
  ENV --> I["evidence, id · kind · schema_version · operation · created_at"]
  ENV --> RC["receipt, Receipt.to_dict()"]
  ENV --> PR["provenance, Provenance.to_dict()"]
  ENV --> RP["replay, INERT placeholder {pending, replayable:false}"]
EvidenceEnvelope structure. Source: evidence/envelope.py, dataclass fields + from_routing_result.

See the full field list on the evidence schema page. Two envelope methods define the API surface:

  • to_dict(): full, JSON-serializable representation (deep-copies mutable blocks). This is what persistence and the assessment engines consume.
  • identifiers(): the compact {id, schema_version, operation, created_at} block surfaced additively on the audit response.

Receipt lifecycle

The Receipt is a lightweight, operational record of the evidence produced by one operation, generated automatically while the envelope is built (Receipt.from_routing_result). It is intentionally not a cryptographic receipt: no signatures, no Merkle tree, no ZK, no blockchain, no persistence.

src/vailidator/evidence/receipt.py
FieldMeaning
receipt_idthis receipt's id (rcpt_…)
evidence_idparent envelope id (ev_…)
operation_idoperation/type identifier for the run
provider / selected_provideradapter that produced the result / provider the router chose
statusprovider result status: ok · fallback · error
execution_statusfallback if the router fell back, else primary
diagnostics_summarycompact safe subset, keys provider, env, provider_mode, enabled, key_present, reason only
routing_summaryrequested/selected provider, fallback flag, router mode, attempted providers

The diagnostics summary is a deliberate allow-list (_SAFE_DIAG_KEYS). key_present is a boolean presence flag, never the key. Nested blocks like the mirrored routing are excluded to keep the receipt small. See the full receipt schema.

Operational provenance

Provenance links one operation to its operational context, session, workflow, organization, actor and source, plus the evidence and receipt ids. It is generated by Provenance.create(...) from an optional context mapping; absent values default to None, and source defaults to "provider_router". It is not a provenance graph, distributed lineage, or cryptographic provenance.

src/vailidator/evidence/provenance.py

Explicit non-goals (enforced by tests)

  • No cryptography anywhere on this layer, the receipt, provenance and replay blocks carry operational metadata only.
  • The replay block is an inert placeholder: {status:"pending", replayable:false, input_ref:null}. See replay flow for what is and is not implemented.
  • No secrets and no raw prompt/input text ever enter the envelope, it carries only what the provider/router already deemed safe.