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 responsesrc/vailidator/evidence/engine.py · envelope.py
flowchart LR RR["RoutingResultEvidence assembly, one build() call, one shared timestamp, three linked records. Source: evidence/engine.py.
(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"]
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.
| Field | Meaning |
|---|---|
receipt_id | this receipt's id (rcpt_…) |
evidence_id | parent envelope id (ev_…) |
operation_id | operation/type identifier for the run |
provider / selected_provider | adapter that produced the result / provider the router chose |
status | provider result status: ok · fallback · error |
execution_status | fallback if the router fell back, else primary |
diagnostics_summary | compact safe subset, keys provider, env, provider_mode, enabled, key_present, reason only |
routing_summary | requested/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.
Explicit non-goals (enforced by tests)
- No cryptography anywhere on this layer, the
receipt,provenanceandreplayblocks carry operational metadata only. - The
replayblock 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.