Docs Reference
Evidence Record schema
LiveUpdated
Sixteen core fields, six addendum fields, the optional fields of enruta/0.2 and enruta/0.3, and a second shape for linked records. Records sealed today carry schema: "enruta/0.3"; enruta/0 and enruta/0.2 records keep their schema and verify exactly as before.
oneOf) with $id https://enruta.ai/schemas/evidence-record-0.3.json, shipped in every export and in the enruta-verify package.Core · 16
B2B addendum · 6
enruta/0.2 · 8
enruta/0.3 · 11
record
Identity of the record: a ULID with the rec_ prefix, the schema version, creation time and the sequence number inside the customer chain.
"record": {
"id": "rec_01J9Q2W6G5X8ZK3R7T1V9M4N2B",
"schema": "enruta/0.3",
"created_at": "2026-11-30T18:01:44Z",
"sequence": 4127
}Core
| # | Field | Contents |
|---|---|---|
| 1 | record | id (ULID with rec_ prefix), schema, created_at, sequence within the principal’s chain |
| 2 | principal | Organization that owns the funds and the policy; org_ id, display name and, in 0.2, login_id (halvern/dana.reyes@halvernrobotics.com) |
| 3 | agent | agt_ id, the identity attestation reference (KYA) and the host it ran in |
| 4 | policy | pol_ id, version, and the hash of the signed policy document |
| 5 | request | Payee, amount, currency, purpose, checkout reference, evidence references with digests, idempotency_key, hash; in 0.2 the three intent layers |
| 6 | decision | allow / step_up / deny / observe, reason codes, budget state before and after, checks run, decided_at; in 0.2 shadow_result and the signed risk assessment; in 0.3 judgement, retryable and retry_after |
| 7 | approval | state (approved, declined, expired), actor, method (passkey / link), the bound request hash, approved_at; null when no person was required |
| 8 | mandate | mnd_ id, scope (amount, currency, payee, expiry), single_use, consumed_state, the bound request hash; in 0.2 mode, refill_policy, finality_class, deviation, policy and the ledger snapshot; in 0.3 retry_policy, visibility_scope and hidden_constraints_digest |
| 9 | protocol | The upstream object mapped in: type (ap2_mandate, acp_allowance, ucp_consent, x402_offer, mpp_receipt, card_authorization, export) and its digest |
| 10 | execution | Rail, the instruction or credential reference (token, never a card number), executed_at |
| 11 | receipt | Merchant or rail receipt digest, order or transaction identifier |
| 12 | outcome | authorized / declined / refunded / disputed / pending / completed / handed_off / fulfilled / voided / cancelled / expired, amounts, posted_at; a hand-off adds continue_url and handed_off_at |
| 13 | strength | third_party_signed / customer_attested / self_asserted, per claim group |
| 14 | seal | Canonicalization (rfc8785), hash algorithm, hash, prev record hash |
| 15 | keys | References to the snapshotted verification keys used at capture, each with a provenance tag |
| 16 | attestations | Pointer to the companion bundle: RFC 3161 tokens, daily-root inclusion proof, renewals |
B2B addendum
| Field | Contents |
|---|---|
settlement_rail | issuing_card, ramp, mercury, bill_com, ach, wire, x402, mpp, and since enruta/0.3 stripe_spt and link; the account or program it ran on |
rail_authorization_record | The rail’s own authorization identifier and decision, and the time Enruta answered |
account_validation | How the payee’s account was validated and when |
rfp_context | Request-for-payment or invoice context: bill, purchase order, match status |
card_control_state | The controls compiled onto the card or limit at issuance: amount, merchant, MCC, expiry |
dispute_afterlife | Anything that happened after posting: retries, refunds, disputes, with references; patched only in the bundle, never in the sealed bytes |
Added in enruta/0.2
All optional on a payment record; absent means the v0 meaning.
| Field | Type | Meaning |
|---|---|---|
kind | "payment" | Default when absent; linked records carry their own kind |
chain | { relation: "correction_of" | null, of, of_hash? } | A payment record may only be a correction of another |
principal.login_id | string | <org slug>/<email>, plain text, stable, recognizable by the cardholder; the Visa CE3.0 login id |
request.intent_text | string | What the person said, verbatim (the acquirer field consumerPrompt) |
request.constraints | { max_amount?, categories?[], substitutions_allowed? } | The structured constraints the agent derived |
request.intent_mapping | { model, at, confidence? } | Which model turned the words into constraints, and when |
decision.result | adds "observe" | Shadow mode; decision.shadow_result: { result, reasons } is present exactly then |
decision.risk | RiskAssessment | The risk assessment made with the decision, signed inside the bundle (signatures.risk) |
mandate.mode, refill_policy, finality_class, deviation, policy | see Mandate | Mandate fields the platform signs into the JWS |
mandate.ledger | { ceiling, reserved, authorized, captured, refilled, consumed, remaining } | Snapshot of the mandate account at seal time |
deviations[] | { field, requested, delivered, flag } | What the merchant delivered against what was requested |
catalog_snapshot_hash, policy_snapshot_hash | sha256: digests | The catalog the agent saw and the policy document at decision time |
failure_class | captcha · anti_bot_script · cdn_block · waf_block · 3ds_challenge · payment_declined | Why an attempt did not reach the rail |
Added in enruta/0.3
They record the trust model: how the intent was judged, and who the two parties are. Every payment record sealed as enruta/0.3 carries agent.identity (assurance A0 when the agent has no anchors) and decision.judgement; the other fields appear when they apply. Records sealed earlier keep their schema.
| Field | Type | Meaning |
|---|---|---|
request.intent | { text, slots, coverage: { required, filled, missing }, clarifications[], mapping } | The person’s words, the slots, what the policy needed, and each question with its answer, answered_by (user or agent) and times (intent and clarification). Supersedes 0.2 intent_text, constraints and intent_mapping, which 0.2 records keep |
IntentSlots | { category?, items?[{ description, quantity?, unit_max? }], quantity?, max_total?, currency?, payee?, deliver_by?, substitutions_allowed?, recurrence?, notes? } | The slot names are the policy’s vocabulary |
decision.result | adds "clarify" | The request lacked a slot the policy requires; sealed only when the questions expire unanswered (outcome.status: expired) |
decision.judgement | { rules[{ name, result, detail? }], model: null | { provider, model, instruction_hash, input_hash, output_hash, verdict, rationale, confidence, at } } | Rules first; the model only for what rules cannot decide; the hashes are sha256: over the JCS-canonical input and output |
agent.identity | { assurance: "A0" | "A1" | "A2" | "A3", anchors[] } | Each anchor: { attestation_id, type, party?, issuer, status, verified_at, expires_at, evidence_hash } (identity anchors) |
payee.identity | { status: "verified" | "unverified" | "mismatch", checks[], seller_org, checked_at } | The merchant check made before paying, on a top-level payee object rather than inside request.payee: domain, ucp_keys, checkout_signature, seller_org, conformance |
economics_ref | { fee_schedule_as_of } | Present when a money split estimate was shown at decision time; the estimate itself is not sealed |
chain.relation | adds "occurrence_of" | A payment made under a recurrence; valid next to correction_of |
execution.rail, settlement_rail.rail | adds "stripe_spt" and "link" | A payment on a Stripe shared payment token or a Link spend request (rails); a record sealed earlier keeps the label it was sealed with |
outcome of a rail hand-off | { status: "handed_off", handed_off_at, rail, reason, message, next_step, continue_url? } | The rail could not issue a credential for an allowed payment: which rail, its reason, what it said and what a person does next; continue_url only when the rail named its own page |
decision.retryable, decision.retry_after | boolean; timestamp or null, present together | Whether the same payment may be requested again, and from when: true only when every reason that ended the request is remediable, and retry_after set only then (over_budget: the next budget window) (retrying) |
mandate.retry_policy | { max_attempts, window_seconds, same_amount_only } | How a failed payment may be tried again, as signed into the mandate |
mandate.visibility_scope | { merchant[], issuer_only[] } | Which constraints the merchant’s mandate carried; present when the policy declared a scope (mandate) |
mandate.hidden_constraints_digest | sha256: digest | The JWS claim of the same name: a commitment to the constraints kept from the merchant, whose values and salt are in the bundle as artifacts.hidden_constraints |
Reason codes added: needs_clarification, clarification_expired, identity_insufficient, payee_unverified, payee_identity_mismatch, intent_rule_failed, model_judgement_deny, model_unavailable, collateral_insufficient, retry_limit_reached, rail_unavailable, rail_error. Checks added: intent_coverage, intent_rules, intent_model, agent_identity, payee_identity, collateral, retry_policy. The bundle gains artifacts.identity[], each anchor’s raw evidence, artifacts.judgement_io, the model’s input and output, and artifacts.hidden_constraints, the constraints a mandate kept from the merchant with the salt of their digest.
Linked records
Every supporting object is its own record on the principal’s chain: same sequence and seal.prev, same daily checkpoint, same verifier. subject holds the object; subject.type equals kind.
{
"record": { "id": "rec_01M2G8Z0Q4W7T2K9N5B3D6H1XC", "schema": "enruta/0.2", "created_at": "2026-09-13T15:04:11Z", "sequence": 13 },
"kind": "reversal", // reversal | dispute_packet | dispute_outcome | settlement_record | reconciliation_line |
// reconciliation_report | consent_record | kya_attestation | agent_pause | agent_order | recurrence
"principal": { "id": "org_halvern", "login_id": "halvern/dana.reyes@halvernrobotics.com" },
"agent": { "id": "agt_procurement_02", "host": "claude" }, // null only for reconciliation_report
"chain": { "relation": "reversal_of", "of": "rec_01M25129GZMJXTNQ47TMZRDGHN", "of_hash": "7e2a…c41d" },
"policy": { "id": "pol_office_supplies", "version": 4, "hash": "sha256:…" },
"subject": { "type": "reversal", "id": "rev_01M2G8YV5R3T7W2Q9K4N8B6DXP", "kind": "refund", "amount": { "value": "9.99", "currency": "USD" }, … },
"decision": { "result": "allow", "reasons": [], "checks": [{ "name": "original_amount", "ok": true }, …] },
"approval": null,
"outcome": { "status": "refunded", "amount": "9.99", "currency": "USD", "posted_at": "2026-09-13T15:04:02Z" },
"strength": { "subject": "third_party_signed", "policy": "customer_attested" },
"seal": { "canon": "rfc8785", "alg": "sha256", "hash": "c19b…8e02", "prev": "7e2a…c41d" },
"keys": ["key_org_halvern_2026-09", "key_enruta_seal_2026-09", "key_merchant_store_2026-09"],
"attestations": { "bundle": "rec_01M2G8Z0Q4W7T2K9N5B3D6H1XC.bundle.json" }
}kind | chain.relation | subject.id prefix | Carries |
|---|---|---|---|
reversal | reversal_of | rev_ | A real decision; an approval when the refund stepped up; outcome refunded / voided / cancelled |
dispute_packet | dispute_of | dsp_ | The packet without its exports; the mandate JWS sits in artifacts.mandate |
dispute_outcome | dispute_of (the packet record) | dsp_ | won / lost / withdrawn, the note, where it was submitted |
settlement_record | settlement_of | stl_ | Stage, amounts, finality; the last rail object in artifacts.rail_event |
reconciliation_line | reconciliation_of | rcl_ | The five legs, match state, exceptions |
reconciliation_report | report_for (of: null, agent: null) | rcr_ | Counts, coverage, root hash, the line record ids; the signature of the person who approved it as approval |
consent_record | consent_for | cns_ | The consent object; a revocation is a later record chained to the first, with subject.action schedule_revocation while it waits for its effective_at and revoke when it takes effect |
kya_attestation | attestation_for | kya_ | Registry, tier, method, evidence_hash |
agent_pause | pause_of (of: null) | agt_ | pause / resume, by whom, why, when |
agent_order | merchant_view_of (the buyer record, with of_hash) | mor_ | The seller’s view: labels, risk signal, line items by id and total |
recurrence | recurrence_for (its consent record, or of: null) | rcu_ | The recurrence at creation and at every change: payee, caps, schedule, status |
Strength of a linked record
strength.subject is third_party_signed when the bundle carries artifacts.subject plus a detached JWS by a merchant key over it, or an object from a rail (artifacts.rail_event); customer_attested when Enruta signed the subject hash with the organization’s link key (signatures.subject); self_asserted otherwise. The verifier applies the same never-upgrade rule as for payment records.
The bundle
<record id>.bundle.json sits next to the record and holds everything that changes after sealing: time-stamp tokens, the inclusion proof in the daily root, the root’s signed checkpoint, renewals, the key snapshots, and since 0.2 artifacts.subject, artifacts.subject_signature, artifacts.rail_event, signatures.subject and signatures.risk, and since 0.3 artifacts.identity[], artifacts.judgement_io and artifacts.hidden_constraints (the constraints a mandate kept from the merchant, with the salt of their digest; never served to a merchant). The record’s hash never depends on the bundle.