Docs Reference

Evidence Record schema

LiveUpdated

View as Markdown

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.

Field names are stable within a schema version; additions are optional so an older record keeps its hash and its meaning. Both shapes are published as one JSON Schema (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

#FieldContents
1recordid (ULID with rec_ prefix), schema, created_at, sequence within the principal’s chain
2principalOrganization that owns the funds and the policy; org_ id, display name and, in 0.2, login_id (halvern/dana.reyes@halvernrobotics.com)
3agentagt_ id, the identity attestation reference (KYA) and the host it ran in
4policypol_ id, version, and the hash of the signed policy document
5requestPayee, amount, currency, purpose, checkout reference, evidence references with digests, idempotency_key, hash; in 0.2 the three intent layers
6decisionallow / 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
7approvalstate (approved, declined, expired), actor, method (passkey / link), the bound request hash, approved_at; null when no person was required
8mandatemnd_ 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
9protocolThe upstream object mapped in: type (ap2_mandate, acp_allowance, ucp_consent, x402_offer, mpp_receipt, card_authorization, export) and its digest
10executionRail, the instruction or credential reference (token, never a card number), executed_at
11receiptMerchant or rail receipt digest, order or transaction identifier
12outcomeauthorized / declined / refunded / disputed / pending / completed / handed_off / fulfilled / voided / cancelled / expired, amounts, posted_at; a hand-off adds continue_url and handed_off_at
13strengththird_party_signed / customer_attested / self_asserted, per claim group
14sealCanonicalization (rfc8785), hash algorithm, hash, prev record hash
15keysReferences to the snapshotted verification keys used at capture, each with a provenance tag
16attestationsPointer to the companion bundle: RFC 3161 tokens, daily-root inclusion proof, renewals

B2B addendum

FieldContents
settlement_railissuing_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_recordThe rail’s own authorization identifier and decision, and the time Enruta answered
account_validationHow the payee’s account was validated and when
rfp_contextRequest-for-payment or invoice context: bill, purchase order, match status
card_control_stateThe controls compiled onto the card or limit at issuance: amount, merchant, MCC, expiry
dispute_afterlifeAnything 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.

FieldTypeMeaning
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_idstring<org slug>/<email>, plain text, stable, recognizable by the cardholder; the Visa CE3.0 login id
request.intent_textstringWhat 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.resultadds "observe"Shadow mode; decision.shadow_result: { result, reasons } is present exactly then
decision.riskRiskAssessmentThe risk assessment made with the decision, signed inside the bundle (signatures.risk)
mandate.mode, refill_policy, finality_class, deviation, policysee MandateMandate 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_hashsha256: digestsThe catalog the agent saw and the policy document at decision time
failure_classcaptcha · anti_bot_script · cdn_block · waf_block · 3ds_challenge · payment_declinedWhy 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.

FieldTypeMeaning
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.resultadds "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.relationadds "occurrence_of"A payment made under a recurrence; valid next to correction_of
execution.rail, settlement_rail.railadds "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_afterboolean; timestamp or null, present togetherWhether 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_digestsha256: digestThe 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" }
}
kindchain.relationsubject.id prefixCarries
reversalreversal_ofrev_A real decision; an approval when the refund stepped up; outcome refunded / voided / cancelled
dispute_packetdispute_ofdsp_The packet without its exports; the mandate JWS sits in artifacts.mandate
dispute_outcomedispute_of (the packet record)dsp_won / lost / withdrawn, the note, where it was submitted
settlement_recordsettlement_ofstl_Stage, amounts, finality; the last rail object in artifacts.rail_event
reconciliation_linereconciliation_ofrcl_The five legs, match state, exceptions
reconciliation_reportreport_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_recordconsent_forcns_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_attestationattestation_forkya_Registry, tier, method, evidence_hash
agent_pausepause_of (of: null)agt_pause / resume, by whom, why, when
agent_ordermerchant_view_of (the buyer record, with of_hash)mor_The seller’s view: labels, risk signal, line items by id and total
recurrencerecurrence_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.