TrustReceipt

September 20, 2026 · View on GitHub

English | Español | Français | Deutsch

TrustReceipt

Merchant-side evidence layer for agentic commerce: portable, signed receipts you can verify offline

Version License npm TrustReceipt Conformant


What it is

TrustReceipt is an open receipt format for merchants. It records evidence of agentic commerce transactions in a form anyone can verify offline, across protocols such as ACP, AP2, x402, MCP, UCP and MCAP. It sits alongside those protocols. AP2 mandates, ACP checkout sessions, Visa TAP signatures and x402 settlements stay as they are, and TrustReceipt adds a portable cryptographic record of the policy decision applied to each one.

A TrustReceipt is a JSON payload signed as a JWS, which you can verify offline against a public JWKS endpoint. Each receipt records who the agent was, which protocol ran, which trust providers vouched for the transaction, which policy applied and what decision it reached. All of that fits in one self-contained token that any party can verify without calling the issuer.

This package is the reference verifier and issuer implementation. It is part of Trusteed's merchant-control stack (policy snapshots + agent control points + receipts), but the receipt format itself is open and portable across issuers.


Recent updates (2026-09-16)

This sync brings the public repo current with the reference implementation (previous sync: 2026-08-17) and adds an accuracy pass over the existing docs. New:

  • Two new third-party identity verifiers, each working without the underlying protocol's transport. Merchant Identity Assertions (MIA) covers self-issued and DNS-authorized third-party issuance (draft-anders-merchant-identity-assertions-01, 30 conformance vectors). AGTP merchant identity covers Agent Identity Document, Intent Assertion and Cart-Digest checks (draft-hood-agtp-merchant-identity-02, 50 conformance vectors).
  • Mandate and approval evidence, State Witness evidence and operation link: three new optional field groups (see Receipt anatomy and SPEC.md §3.2), injected identically into all three receipt shapes with no schema_version bump.
  • The ATEP passport reference verifier (reference-verifier/verify-atep-passport.mjs), a zero-dependency script that uses only Node built-ins. It shows that a merchant's portable trust attestation can be verified offline without any Trusteed code.
  • A 7th legacy-compact conformance vector (signer declarations).
  • Two accuracy fixes to existing docs. The "11 v1.1 vectors" line undercounted a table that already had 12 rows (SPEC.md §11.6). And CONTRIBUTING.md, docs/architecture.md and this README's Conformance section now say that scripts/validate-vectors.ts currently reports 9/10, not 10/10 (TC-007 / expired, issue #6). Code and spec disagree on that vector.
  • A packaging gap that was already there and is now tracked: the RFC 3161 capability row was marked optional, but it is non-functional for any external installer today (issue #5).
  • CHANGELOG.md cleanup: two entries had sat under a stale "Unreleased" heading since before this repo's public launch even though they had already shipped. They are retitled to the real version and date. An internal Trusteed deployment-coupling paragraph and an unreachable sibling-package cross-reference, both leaked in from the source monorepo, were removed.

See CHANGELOG.md for the full version history.


Capability status

CapabilityStatusNotes
JWS verification (Ed25519)✅ ImplementedCLI + library, no custom crypto (uses jose v6)
JWKS-based public key resolution✅ ImplementedDirect fetch() per verification (5s timeout), no in-process cache; inline JWK set also supported
Schema v1.0✅ Stable10 conformance vectors passing
Schema v1.1 (eIDAS-aligned fields)🟡 Code-complete / experimental16 additional vectors passing (12 in test-vectors/v11/ + 4 negative strict-mode vectors in test-vectors/v11-strict/, T-AUD-012); field set may evolve before v1.2
RFC 8785 canonical JSON✅ ImplementedUsed for signing + audit chain hashes
Audit chain (hash_chain_prev)✅ ImplementedPer-merchant tamper-evident linkage
Signer declarations (signers)✅ ImplementedWho signed, under which custody model, and how each signer relates to the subject; a platform-held key and a merchant-held key are no longer indistinguishable
Evaluation identity (evaluation_id)✅ ImplementedPoints at the enforcement record that produced the verdict, so correlation stops relying on timestamp proximity. Identifies the EVALUATION, not the operation
eIDAS Advanced Electronic Seal posture🟡 CandidateField-level support; not a Qualified Electronic Seal (no QTSP)
ESIGN / UETA evidence shape🟡 Partialesign_disclosure_hash + consent context; full disclosure workflow in progress
RFC 3161 trusted timestamp evidence🔴 Hook present, not externally usableverify-timestamp-evidence.ts imports the real implementation from @agenticmcpstores/trust-receipt-tsa-client, a Trusteed-internal package that is not published to npm and not vendored in this repo; npm install resolves everything else but that one dependency has no registry entry; tracked in issue #5
Merchant Identity Assertions (MIA): third-party verifier✅ ImplementedVerifies draft-anders-merchant-identity-assertions-01 documents: self-issued and DNS-authorized third-party issuance, replay/tamper/redirect detection; 30 conformance vectors under conformance/mia-vectors/
AGTP merchant identity: Intent Assertion / Cart-Digest✅ ImplementedVerifies exactly what draft-hood-agtp-merchant-identity-02 declares checkable without speaking AGTP, no AGTP transport client ships here, only the Agent Identity Document, Intent Assertion, and Cart-Digest checks; 50 conformance vectors under conformance/agtp-merchant-vectors/
Mandate & approval evidence (mandate-evidence.ts)🟡 Structural onlyLets a third party recompute mandate_claims_hash and confirm a charged amount fell inside what was authorized; mandate_verification is a closed enum and today only ever reports "structure_only"; no issuer verifies a mandate signature yet
State Witness evidence (state-witness-evidence.ts)✅ ImplementedDeclares what the issuer's state comparator resolved before money moved, and against which authoritative state. Closes the gap where an unchanged-state checkout left no signed record that the comparison ran
Operation link (operation-link.ts)✅ Implemented (schema only)Ties a corrected retry or a reconfirmed execution back to the receipt it supersedes, distinct from hash_chain_prev, which only orders receipts by time and makes no continuation claim; not re-exported from index.ts, import from trust-receipt-verifier/schema/operation-link.js
AWS KMS issuer-side signing🟡 Optional / issuer-sideProvided by sibling package trust-receipt-kms-signer; not required for verification
Reference ports (TS) / language ports (Python, Go, Java)🟡 TS only todayPorts welcome; see CONTRIBUTING.md
AIVS proof-bundle export/verify (aivs-export.ts)🟡 Code-completeProjects a signed v1.0 receipt into an AIVS-compatible { manifest_hash, session_sig, audit_log } bundle, offline-verifiable with no Trusteed code (spec-062 US1, alignment not escrow)
Extension artifact verification (verify-extension-artifact.ts)🟡 Code-completeVerifies developer-signed erasure receipts and extension manifests from the Trusteed Extension Marketplace ecosystem
v1.0-legacy compact receipt shape (verifier.ts)✅ ImplementedverifyTrustReceipt also accepts the JWT-style compact payload emitted by the platform issuer since spec-040; surfaced as result.variant / result.legacyReceipt
Declared trust-anchor degradation (accepted_degraded)✅ ImplementedThree-valued verdict for v1.1 envelopes; see Verification verdicts and SPEC.md §4.1 (NORMATIVE)
Consumer-side revocation (revocation.ts)✅ ImplementedcheckRevocation() against a merchant's published status list. Pure and offline: you fetch, it decides. Every failure resolves to unknown, never not_revoked
Canonicalization reporting (result.canonicalization)✅ Implemented"jcs" vs "json-stringify-legacy" on the legacy-compact path, independent of variant, which describes the payload shape

✅ = production-grade implementation. 🟡 = present and tested but subject to change before v1.2 GA, or dependent on operator-side integration.

Verification verdicts

verifyReceiptEnvelope (v1.1) returns three values. Treating the verdict as binary is a conformance failure either way: it reports a degraded receipt as fully verified, or it discards a valid one.

VerdictMeaning
acceptedSignature and structure verified, and so is the chain of trust.
accepted_degradedSignature and structure verified; the receipt declares its chain of trust unverifiable.
rejectedAny check failed.

accepted_degraded is emitted only when the issuer root behind the bundled JWKS history could not be cryptographically verified and the receipt's signed body carries a legal_posture_warnings[] entry with reason: "trust_anchor_staging". A receipt that stays silent about an unverifiable anchor is rejected, and the unsigned envelope_metadata mirror alone can never unlock the downgrade.

It attests internal consistency and issuer intent, never issuer authenticity. Consumers that branch on outcome === "accepted" keep refusing it. Accepting the weaker guarantee has to be a conscious act.


How it works

Two independent operations handle a TrustReceipt, issuing and verifying. They can run in different systems at different times, and neither needs a shared secret.

Issuing a receipt

sequenceDiagram
    autonumber
    participant Agent as 🤖 Agent / Platform
    participant Issuer as 🏭 Issuer (trusteed.xyz)
    participant KMS as 🔑 KMS / Ed25519 Key

    Agent->>Issuer: Transaction event<br/>(protocol, merchant_id, agent_id,<br/>cart_hash, user_intent_hash, …)
    Issuer->>Issuer: Build 24-field payload<br/>(5 groups: core, participants,<br/>evidence, trust assertions, compliance)
    Issuer->>Issuer: RFC 8785 canonical serialization<br/>(sorted keys, no whitespace)
    Issuer->>KMS: Sign canonical bytes
    KMS-->>Issuer: Ed25519 signature
    Issuer->>Issuer: Encode as compact JWS<br/>header.payload.signature (base64url)
    Issuer-->>Agent: 📄 Compact JWS token

Verifying a receipt

sequenceDiagram
    autonumber
    participant Verifier as 🔍 Verifier (any party)
    participant JWKS as 🌐 JWKS Endpoint<br/>/.well-known/jwks.json
    participant Schema as 📐 Zod Schema

    Verifier->>Verifier: Parse JWS header<br/>extract kid + alg
    Verifier->>JWKS: GET public keys<br/>(direct fetch, 5s timeout)
    JWKS-->>Verifier: Public JWK set
    Verifier->>Verifier: Match kid → public key
    Verifier->>Verifier: Verify Ed25519 signature<br/>(jose, no custom crypto)
    Verifier->>Schema: Validate decoded payload
    Schema-->>Verifier: Zod parse result
    Verifier->>Verifier: Check issued_at / expires_at<br/>(± clock tolerance)
    Verifier-->>Verifier: ✅ VerifyResult { valid, receipt }<br/>or ❌ { valid: false, reason, errors }

Full picture

flowchart LR
    subgraph Protocols
        P1[x402]
        P2[AP2]
        P3[ACP]
        P4[MCP]
        P5[UCP]
        P6[MCAP]
    end

    subgraph Issuer ["Issuer (trusteed.xyz)"]
        direction TB
        B1["Build payload\n24 fields · 5 groups"]
        B2["RFC 8785 canonicalize"]
        B3["Ed25519 sign\n(kid pinned)"]
        B4["Compact JWS"]
        B1 --> B2 --> B3 --> B4
    end

    subgraph Verifier ["Verifier (any party, offline-capable)"]
        direction TB
        V1["Parse header\nextract kid"]
        V2["Fetch JWKS\n(or inline JWK set)"]
        V3["Match kid → key\nverify signature"]
        V4["Zod schema check\nexpiry check"]
        V5{Result}
        V1 --> V2 --> V3 --> V4 --> V5
    end

    Protocols --> Issuer
    Issuer -->|"📄 JWS token"| Verifier
    V5 -->|valid| R1["✅ receipt object\n(policy_decision, agent_id, …)"]
    V5 -->|invalid| R2["❌ reason + errors\n(tampered / expired / schema_invalid / …)"]

Key properties:

  • Verification works offline. It needs only the JWKS URL (publicly reachable and HTTP-cacheable at the edge) and never calls back to the issuer
  • One receipt format covers x402, AP2, ACP, MCP, UCP and MCAP through protocol_artifacts
  • hash_chain_prev links receipts into a tamper-evident, per-merchant chain (RFC 8785)
  • signers states who signed and in whose custody the key lives
  • evaluation_id points at the enforcement record behind the verdict
  • legal_posture tracks the eIDAS, ESIGN and UK-DIATF compliance posture of each receipt

Verifiable seal for agentic commerce. Each TrustReceipt generates portable cryptographic evidence of origin, integrity, consent, agent authorization, and auditable retention. Designed to be aligned with ESIGN/UETA in the US, aligned with eIDAS in the EU as candidate advanced electronic seal evidence, and compatible with the UK Electronic Signatures and Trust Services framework. Qualified seals/signatures require issuance or validation by an applicable QTSP.

Disclaimer: TrustReceipt is cryptographically verifiable technical evidence. It does not by itself determine legal liability. Whether a given receipt is admissible or persuasive in a specific jurisdiction or proceeding depends on applicable local law, the consenting parties' agreements, and other facts beyond the scope of this record format.

The issuer maintains an internal claims policy that fixes the permitted and prohibited wording for every posture in this table. It is not published with this repository. Ask the issuer if you need the canonical list.

Regulatory compatibility status

FrameworkJurisdictionStatusv1.1 Fields
eIDAS (Regulation 910/2014)EU🟡 Candidate. legal_posture progresses ades_candidate_no_tsaades_candidate_timestampedades_candidate_kms. Qualified seal (QeSeal) requires a QTSP.legal_posture, legal_posture_warnings, timestamp_evidence, esign_disclosure_hash
ESIGN / UETAUS🟡 Partial. Verifiable seal with consent evidence, agent attribution, versioned disclosure, and auditable retention, designed to support ESIGN/UETA. Full disclosure workflow (withdrawal URI, version pinning) in progress.esign_disclosure_hash, consent_context.consent_disclosure_version, consent_context.withdrawal_uri_hash
Electronic Communications Act 2000 / DIATFUK🟡 Schema-compatible. Jurisdiction-aware retention (UK: 7 y default) and legal_posture field carry UK trust-service evidence. DIATF alignment is verified at schema level. Operational certification is pending.legal_posture, privacy_classification.jurisdiction, export_bundle.retention_policy

⚠️ None of the above constitutes legal advice. Regulatory qualification status may change as the implementation evolves. Consult qualified legal counsel for jurisdiction-specific requirements.


Quick verify

npm install trust-receipt-verifier

v1.0 receipt (compact JWS):

import { verifyTrustReceipt } from "trust-receipt-verifier";

const result = await verifyTrustReceipt(jwsToken, {
  jwksUrl: "https://trusteed.xyz/.well-known/jwks.json",
});

if (result.valid) {
  console.log(result.receipt.policy_decision); // "allow"
} else {
  console.error(result.reason, result.errors);
}

v1.1 envelope (receipt + envelope_metadata + optional sidecars):

import { verifyReceiptEnvelope } from "trust-receipt-verifier";
import type { VerifyOptions } from "trust-receipt-verifier";

const opts: VerifyOptions = {
  jwksHistory: {
    jws_compact: "<SignedJwksHistory JWS>",
    signed_by_root_sha256: "<issuer-root-sha256>",
  },
  trustAnchorPemSha256: "<64-hex-sha256-of-the-issuer-root-PEM>",
  policyOidAllowlist: ["1.2.3.4.5.6.7.8.9"],
  // toleranceSeconds: 30,  // default clock-skew tolerance (seconds)
  // mode: "strict",        // default "compat"; see "Strict vs compat" below
  // allowStagingRoots: true, // staging/CI only, never set in production
};

const result = await verifyReceiptEnvelope(envelope, opts);

if (result.outcome === "accepted") {
  console.log(result.recomputedLegalPosture); // "ades_candidate_timestamped"
  if (result.warnings.includes("unknown_trust_provider_present")) {
    // envelope references a trust provider not yet recognised by this verifier version
  }
} else {
  console.error(result.errorCode, result.detail);
  // errorCode may be: "receipt_expired" | "receipt_not_yet_valid" |
  // "jwks_history_signature_invalid" | "unknown_kid" | "schema_invalid" | …
}

allowStagingRoots: defaults to false. When false (production default) any jwksHistory.signed_by_root_sha256 not present in the embedded trust anchor list causes immediate rejection (jwks_history_signature_invalid). Set to true only in staging or CI environments that use unsigned/stub JWKS history bundles.

⚠️ The trust anchor shipped in this package is a staging stub, not a production root. EMBEDDED_ISSUER_ROOTS (src/embedded-issuer-root.ts) currently holds a single structurally-valid-but-non-verifying placeholder certificate whose subject CN is marked (STAGING). validateChain() deliberately fail-closes on it with root_key_not_provisioned, so no caller can mistake the placeholder for authoritative trust. The real self-signed Ed25519 root is produced by an offline key ceremony that has not yet run. When it does, the constant is replaced and the verifier package gets a SemVer bump. Until then, pin your own trustAnchorPemSha256. Do not rely on the embedded list, and treat every trustAnchorPemSha256 value in this README as a placeholder to substitute.


Receipt anatomy

A TrustReceipt payload contains 24 fields across five groups:

Core

FieldTypeDescription
receipt_idUUID v4Unique receipt identifier
schema_version"1.0"Schema version literal
issued_atUnix secondsWhen the receipt was created
expires_atUnix secondsWhen the receipt expires
issuerstringIssuing platform domain

Participants

FieldTypeDescription
merchant_idstringMerchant identifier
agent_idstringAgent session or instance identifier
agent_providerstringAI provider (anthropic, openai, google, …)

Transaction Evidence

FieldTypeDescription
user_intent_hashstring (non-empty)Hash of the user's original intent text. Must be non-empty (SHA-256 hex recommended)
cart_hashSHA-256 hexHash of cart contents at decision time (optional)
order_hashSHA-256 hexHash of settled order object (optional)
transaction_idstringPlatform transaction reference (optional)
protocolenumx402 | AP2 | ACP | MCP | UCP | MCAP
protocol_artifactsarrayHashes of protocol-specific evidence objects
payment_referenceobjectPSP name + reference, no raw payment data (optional)

Trust Assertions

FieldTypeDescription
risk_signalsarrayNormalized signals from issuer or providers
trust_provider_assertionsarrayScored assertions from ClearSale, Trulioo, Mastercard, etc.
policy_decisionenumallow | deny | review | challenge

Compliance

FieldTypeDescription
liability_contextobjectAssertor and scope (optional)
consent_contextobjectConsent hash, scope, timestamp (optional)
privacy_classificationobjectPII flag, retention days, jurisdiction (optional)
verification_methodsarrayJWKS URL or DID for key resolution; at least one entry required
kidstringKey ID used to sign this receipt
hash_chain_prevSHA-256 hexPrevious receipt in audit chain (optional)
signersarrayDeclared signers: party, kid, custody model, relation to subject (optional)
evaluation_idstringIdentity of the evaluation that produced the verdict (optional); see caveats below
rule_set_versionintegerVersion of the policy catalogue the verdict was evaluated under (optional)
evaluated_rulesarrayRule codes that RAN, as opposed to rules_triggered which fired (optional)
mandate_id / mandate_claims_hash / mandate_max_amount_cents / mandate_currency / mandate_subject / mandate_audience / mandate_expires_at / mandate_verificationmixedMandate evidence (2026-09, optional): lets a third party confirm the charged amount fell inside what was authorized; see SPEC.md §3.2 for the full field-by-field reference
approval_ref / approval_channel / approval_atmixedOut-of-band human approval evidence (2026-09, optional). Distinct from the mandate: the mandate says what an agent COULD spend, this says a person said yes to THIS purchase
state_witness_resolution / state_witness_authoritative_hash / state_witness_reasonsmixedState Witness evidence (2026-09, optional): what the comparator resolved before money moved, and against which authoritative state
operation_id / supersedes_receipt_hash / superseded_reason / reconfirmed_state_hashmixedOperation link (2026-09, optional): ties a corrected retry or reconfirmed execution back to the receipt it supersedes; distinct from hash_chain_prev, which only orders by time
attachmentsarrayNamed, hashed file references (optional)

Protocol support

ProtocolArtifact mappingPrimary artifact types
MCAPDefinedmcap_consent_hash, mcap_nonce
x402Definedpermit2_hash, settlement_hash, upto_envelope_hash
AP2Definedmandate_hash, ap2_consent_hash
MCPDefinedmcp_call_hash, tool_call_hash
ACPDefinedacp_session_hash, acp_policy_hash
UCPDefineducp_token_hash

Conformance

A verifier implementation must pass all 10 test vectors (v1.0) to claim TrustReceipt conformance. Three levels are defined:

v1.1 status (2026-05-06). eIDAS hardening adds 12 v1.1 vectors under test-vectors/v11/. This line used to say 11, but the table in SPEC.md §11.6 always listed 12 rows, including 019b. The v1.1 schema drops legacy mandate_hash / permit2 / mcp rail fields and introduces payment_authorization_hash, authorization_scheme, legal_posture_warnings, and esign_disclosure_hash.

⚠️ Conformance discrepancy, as of 2026-09-16 (issue #6): running npx tsx scripts/validate-vectors.ts today reports 9/10, not 10/10. verifyTrustReceipt's expiry check became informative-only (result.freshness.expired, not fatal) on 2026-07-28 to satisfy spec-049 FR-018's multi-year retention requirement, and TC-007's expected: "invalid" entry was never reconciled with that change. Read that issue before you write a port against this line. test-vectors/vectors.json is the cross-language verifier ABI (see PUBLISH.md), and the wrong side of this discrepancy could get frozen into an independent port.

2026-09-16 additions. Two new third-party identity verifiers, each with its own conformance suite: 50 vectors under conformance/agtp-merchant-vectors/ (draft-hood-agtp-merchant-identity-02) and 30 vectors under conformance/mia-vectors/ (draft-anders-merchant-identity-assertions-01), plus a 7th legacy-compact vector (L007-signers-declaration.json). Counting every vector file across test-vectors/ and conformance/ (v1.0 core 10, v1.1 12, v1.1-strict 4, legacy-compact 7, x402-binding 11, agtp-merchant 50, mia 30) gives 124 conformance vectors on disk. The automated test suite as a whole (pnpm test) is 475 passing tests across 36 files, more than the raw vector count because several suites assert per-field and boundary behavior beyond the fixed vector set. Both figures were measured directly (pnpm vitest run / npx tsc --noEmit, both clean) against this exact tree, not carried over from an older release.

LevelNameRequirement
1VerifierPasses all 10 test vectors
2IssuerLevel 1 + correctly issues valid receipts
3ProviderLevel 2 + co-authors ≥1 trust_provider_assertions type with real data

This reference implementation is Level 2 conformant. There are two ways to run the conformance suite:

(a) Unit tests. Verifies all 10 vectors using pre-built test infrastructure (10 tests):

pnpm test

(b) End-to-end JWS conformance. Generates a fresh keypair, signs all 10 vectors, calls verifyTrustReceipt and reports pass or fail per vector:

# Via CLI (requires the package to be built first)
trust-receipt conformance

# Or directly with tsx (no build required)
npx tsx scripts/validate-vectors.ts

Add the badge to your project once all 10 pass:

[![TrustReceipt Conformant](https://img.shields.io/badge/TrustReceipt-v1.0%20Conformant-blue)](https://github.com/Trusteedxyz/Trust-Receipt-Verifier)

Repo structure

trust-receipt-verifier/
├── SPEC.md                            # formal specification (authoritative)
├── CONTRIBUTING.md                    # how to contribute vectors, ports, and provider schemas
├── LICENSE                            # MIT
├── src/
│   ├── index.ts                       # package exports
│   ├── verifier.ts                    # verifyTrustReceipt() + parseTrustReceiptUnsafe() (v1.0, incl. legacy-compact shape)
│   ├── verify-1.0.ts                  # v1.0 verifier internals
│   ├── verify-1.1.ts                  # verifyReceiptEnvelope() (v1.1 eIDAS envelope) + typed trust-provider predicates
│   ├── zod-1.1.ts                     # v1.1 Zod schema (strict root, rejects unknown top-level keys)
│   ├── types-1.1.ts                   # typed trust-provider assertion shapes
│   ├── issuer.ts                      # issueTrustReceipt()
│   ├── embedded-issuer-root.ts        # compile-time trust anchor + validateChain()
│   ├── verify-jwks-history.ts         # JWKS history chain verification
│   ├── verify-timestamp-evidence.ts   # RFC 3161 timestamp verification
│   ├── verify-export-bundle.ts        # offline export-bundle verification
│   ├── verify-extension-artifact.ts   # erasure receipt / extension manifest verification (Extension Marketplace)
│   ├── aivs-export.ts                 # AIVS proof-bundle export/verify (spec-062 US1)
│   ├── revocation.ts                  # consumer-side revocation check against a merchant status list
│   ├── agtp-merchant/                 # Agent Identity Document / Intent Assertion / Cart-Digest (`draft-hood-agtp-merchant-identity-02`), no AGTP transport client
│   ├── mia/                           # Merchant Identity Assertions, self-issued + third-party (`draft-anders-merchant-identity-assertions-01`)
│   ├── __tests__/                     # unit + conformance tests
│   └── schema/
│       ├── trust-receipt.schema.ts        # Zod schema (source of truth for v1.0 TypeScript types)
│       ├── trust-receipt-legacy.schema.ts # v1.0-legacy compact shape (issued by platform since spec-040)
│       ├── policy-evidence.ts             # policy-evidence fields, shared by all three receipt shapes
│       ├── mandate-evidence.ts            # mandate + approval evidence fields (2026-09)
│       ├── state-witness-evidence.ts      # state-comparator evidence fields (2026-09)
│       └── operation-link.ts              # receipt-to-receipt operation linkage, distinct from `hash_chain_prev` (2026-09)
├── schema/
│   ├── trust-receipt-v1.0-final.schema.json # NORMATIVE v1.0 JSON Schema (+ .sha256)
│   └── trust-receipt-v1.schema.json         # superseded draft, retained for link stability; do NOT implement against it
├── test-vectors/
│   ├── README.md                    # how to use the vectors
│   ├── vectors.json                 # vector manifest with expected outcomes
│   ├── valid/                       # TC-001 through TC-005
│   ├── invalid/                     # TC-006 through TC-010
│   └── v11/, v11-strict/            # v1.1 + strict-mode conformance vectors
├── conformance/
│   ├── legacy-compact-vectors/      # L001–L007: both canonicalization regimes, expiry, tamper, unknown kid, signer declarations
│   ├── agtp-merchant-vectors/       # 50 vectors for `src/agtp-merchant/` (identity, intent, cart-digest)
│   └── mia-vectors/                 # 30 vectors for `src/mia/` (self-issued + DNS-authorized third-party MIA)
├── reference-verifier/
│   ├── verify-aivs-bundle.mjs       # verifies an exported AIVS bundle with no dependency on this package
│   └── verify-atep-passport.mjs     # zero-dependency ATEP passport verifier (Node built-ins only, no `jose`, no npm install)
├── bin/
│   └── trust-receipt.ts (source) → dist/bin/trust-receipt.js (compiled) # CLI: verify, inspect, generate-key, conformance
└── demo/                            # runnable demo scripts

Issue a receipt

import { issueTrustReceipt } from "trust-receipt-verifier";

const jws = await issueTrustReceipt({
  payload: {
    issuer: "trusteed.xyz",
    merchant_id: "merchant-001",
    agent_id: "agent-session-xyz",
    agent_provider: "anthropic",
    user_intent_hash: "<sha256-hex-of-user-intent>",
    protocol: "MCP",
    protocol_artifacts: [{ type: "mcp_call_hash", hash: "<sha256-hex>" }],
    policy_decision: "allow",
    verification_methods: [
      { type: "jwks", value: "https://trusteed.xyz/.well-known/jwks.json" },
    ],
    kid: "tr-ed25519-2026-04-29",
  },
  privateKeyJwk: myEd25519PrivateKey,
  kid: "tr-ed25519-2026-04-29",
});

Canonicalization: the payload is serialized with RFC 8785 (sorted keys, no whitespace) before signing, so SHA-256(payload) is identical in any conforming implementation.

AIVS proof-bundle export. Projects a signed v1.0 receipt into an AIVS-compatible bundle (draft-stone-aivs-00). You can verify it offline with only the JWS and the issuer JWKS:

import {
  exportAivsProofBundle,
  verifyAivsProofBundle,
} from "trust-receipt-verifier";

const bundle = exportAivsProofBundle(receiptJws); // { manifest_hash, session_sig, kid, alg, audit_log }
const result = await verifyAivsProofBundle(bundle, { jwks: issuerJwks });

Extension Marketplace artifacts. Verify developer-signed erasure receipts (proof of data destruction after uninstall) or extension manifests:

import { verifyExtensionArtifact } from "trust-receipt-verifier";

const result = await verifyExtensionArtifact(jws, {
  kind: "erasure", // or "manifest"; caller states which artifact this is
  jwksUrl: "https://trusteed.xyz/.well-known/jwks.json",
});
// result.valid: boolean; result.reason on failure ("malformed_jws" | "unsupported_alg" | "missing_kid" | "jwks_unreachable" | "kid_not_found" | "signature_invalid" | "payload_not_json" | "shape_invalid")

ATEP passport (zero-dependency). A standalone script under reference-verifier/verify-atep-passport.mjs proves a merchant's portable trust attestation offline using only Node.js built-ins (no jose, no npm install, no code from this package):

node reference-verifier/verify-atep-passport.mjs passport.json jwks.json

Merchant Identity Assertions (MIA). Verify a third party's own signed identity document, self-issued or DNS-authorized (draft-anders-merchant-identity-assertions-01):

import { verifyMiaForDomain } from "trust-receipt-verifier";

const result = await verifyMiaForDomain("supplier.example.com", {
  io: { fetchDocument: (url) => fetch(url).then(/* → FetchedDocument | null */ null) },
});
// result.valid: boolean; result.reason on failure (retrieval, tamper, expiry, DNS authorization mismatch, …)

AGTP merchant identity. The parts of draft-hood-agtp-merchant-identity-02 that are checkable without speaking the AGTP transport itself (no AGTP client ships here):

import {
  verifyMerchantIdentityDocument,
  verifyIntentAssertion,
  verifyCartDigest,
} from "trust-receipt-verifier";

const identity = verifyMerchantIdentityDocument(agentIdentityDocument, { /* … */ });
const intent = await verifyIntentAssertion(compactJwt, { /* keyLookup, expected audience, … */ });
const cart = verifyCartDigest(presentedDigest, cartContents, canonicalizeJSON);

CLI

# Generate an Ed25519 key pair
trust-receipt generate-key

# Verify a v1.0 receipt (compact JWS)
trust-receipt verify receipt.jws --jwks-url https://trusteed.xyz/.well-known/jwks.json

# Verify a v1.1 envelope (JSON object with `receipt` + `envelope_metadata`)
trust-receipt verify envelope.json \
  --type receipt-v11 \
  --jwks-history-file issuer-jwks-history.json \
  --trust-anchor-sha256 <64-hex-sha256-of-the-issuer-root-PEM> \
  --policy-oid 1.2.3.4.5.6.7.8.9

# Verify a v1.1 envelope in STRICT mode (semantic trust-anchor enforcement)
trust-receipt verify envelope.json \
  --type receipt-v11 \
  --jwks-history-file issuer-jwks-history.json \
  --trust-anchor-sha256 <64-hex-sha256-of-the-issuer-root-PEM> \
  --strict

# Staging / CI only: skip root-anchor check (never use in production)
trust-receipt verify envelope.json --type receipt-v11 \
  --jwks-history-file issuer-jwks-history.json \
  --trust-anchor-sha256 <sha256> \
  --allow-staging-roots

# Inspect a receipt without verifying the signature
trust-receipt inspect receipt.jws

# Run full end-to-end conformance suite (signs + verifies all 10 vectors)
trust-receipt conformance

--type autodetection: when --type is omitted, the CLI inspects the input shape. A JSON object with both receipt and envelope_metadata keys is treated as receipt-v11 automatically. A compact header.payload.sig string is treated as receipt (v1.0). --type also accepts erasure, manifest and jwks-history for the artifact verifiers described above. Pass it explicitly when auto-detection is ambiguous (both erasure and manifest payloads are compact JWS with no receipt/envelope_metadata keys).


Strict vs compat verification mode (v1.1)

At the schema layer, the v1.1 verifier checks verification_methods.trust_anchor_sha256 and verification_methods.jwks_sha256 only by regex format (64 hex chars). That check lets an opaque stub anchor through, meaning all zeros or a single-nibble value. The stub is well-formed but has no real chain-of-trust binding, and issuers emit such stubs before a production anchor ceremony. The mode option (library) and the --strict flag (CLI) add a semantic layer:

Conditioncompat (default, canary)strict
trust_anchor_sha256 is an opaque stub (all-zeros / single-nibble)warn trust_anchor_sha256_stubreject trust_anchor_stub_rejected
jwks_sha256 is an opaque stubwarn jwks_sha256_stubreject jwks_sha256_stub_rejected
trust_anchor_sha256 ≠ operator-pinned trustAnchorPemSha256warn trust_anchor_sha256_mismatchreject trust_anchor_mismatch
buyer_agent receipt with no agent-identity bindingwarn agent_identity_absentreject agent_identity_required_strict
RFC 3161 / LOTL timestamp degraded (e.g. TSA unavailable)warn (tsa_unavailable)warn (tsa_unavailable), accepted in both

compat is the default so the rollout breaks nobody while observability data builds up. Switch to strict once issuers have completed the production anchor ceremony. The four named negative conformance vectors live in test-vectors/v11-strict/ and are regenerated by scripts/generate-strict-mode-vectors.ts.

// Library usage
import { verifyReceiptEnvelope } from "trust-receipt-verifier";

const result = await verifyReceiptEnvelope(envelope, {
  jwksHistory,
  trustAnchorPemSha256: "<64-hex-pinned-anchor>",
  policyOidAllowlist: ["1.2.3.4.5.6.7.8.9"],
  mode: "strict", // default is "compat"
});

Documentation

DocumentDescription
SPEC.mdFormal specification: wire format, field reference, conformance rules
docs/architecture.mdInternal architecture: signing envelope, key resolution, verification pipeline, security properties
CONTRIBUTING.mdHow to add conformance vectors, language ports, or trust provider schemas
CHANGELOG.mdVersion history and breaking changes

Contributing

See CONTRIBUTING.md for how to add conformance vectors, port the verifier to another language, or co-author a trust_provider_assertions schema as a trust provider partner.


What a TrustReceipt does NOT prove

A receipt is technical evidence, not legal proof or operational guarantee. It deliberately does not assert:

  • That the payment was captured or settled. A receipt with policy_decision: "allow" records the decision and intent. Settlement is recorded by the underlying PSP / rail (Stripe charge, x402 on-chain tx, ACP completion, etc.) and referenced via payment_reference or protocol_artifacts, not by the receipt itself.
  • That goods or services were delivered. Fulfilment evidence lives in the merchant's order system.
  • KYC / KYA compliance. A receipt records that a trust provider asserted a level (e.g. kya_status) at issuance time. It is not a substitute for independent KYC/KYA verification.
  • eIDAS Qualified Electronic Seal status. Even with legal_posture populated, a TrustReceipt is at best an Advanced Electronic Seal candidate. Qualified seals require issuance by an EU-listed QTSP, which is out of scope for this package.
  • Legal liability or admissibility. A receipt is cryptographic evidence. Whether it is admissible or persuasive in a specific jurisdiction depends on local law, the parties' agreements, and facts beyond the receipt format.
  • That the user actually intended what the agent did. The receipt records user_intent_hash, which shows that an intent text existed and was hashed. It does not show that the hash matches a verified human utterance.

If your use case needs any of the above guarantees, use the receipt as an audit primitive next to those mechanisms. It does not replace them.


Threat model

The verifier is designed to detect the following classes of tampering. For each, the verifier returns a structured { valid: false, reason } rather than throwing.

ThreatDefenceVerifier behaviour (v1.0 / v1.1)
Signature forgery / payload tamperEd25519 over RFC 8785 canonical bytes; kid pinned in header and payload"signature_invalid" / "signature_invalid"
Wrong key used to signkid mismatch between JWS header and JWKS entry"kid_not_found" / "unknown_kid"
Expired receiptexpires_at checked against verifier clock with configurable tolerance (default ±30 s)"expired" / "receipt_expired"
Receipt issued in the futureissued_at checked against verifier clock with same tolerance"not_yet_valid" / "receipt_not_yet_valid"
Schema downgrade / unknown fieldsZod schema validation strict on known fields; unknown top-level keys rejected"schema_invalid" / "schema_invalid"
Forged / unsigned JWKS historyjwksHistory.signed_by_root_sha256 must match an embedded trust anchor; hard-fail when unknown unless allowStagingRootsn/a (v1.0) / "jwks_history_signature_invalid"
Unknown trust provider assertionVerifier warns but does not reject, preserving forward-compatibilityn/a (v1.0) / warning "unknown_trust_provider_present"
Replay of an old receiptOut of scope for the verifier alone. Consumers must enforce uniqueness via receipt_id + issued_at + business rulesn/a. The verifier returns valid: true / outcome: "accepted" for not-yet-expired replays
JWKS rotation while a receipt is liveJWKS fetch refreshes on kid miss; old keys can be retained in the JWKS set during the rotation grace windowVerifies as long as kid is still published
Compromised issuer keyKey revocation is operator-side: remove the kid from the JWKS set; verifiers will fail closed"kid_not_found" / "unknown_kid" once removed
Clock skew between issuer/verifiertoleranceSeconds option (default 30 s)Within tolerance: pass. Outside: "expired" / "receipt_expired" or "receipt_not_yet_valid"
MITM on JWKS endpointTLS to JWKS host is the operator's responsibility; pinning the JWKS URL out-of-band defends against rogue substitutionn/a. The verifier trusts the configured URL

Non-goals. The verifier does not validate: (a) whether the underlying payment cleared, (b) whether merchant policy was correctly configured, (c) jurisdictional admissibility, (d) revocation lists external to the JWKS endpoint, or (e) protocol-specific evidence inside protocol_artifacts (those are caller-validated against the relevant protocol's spec).


Versioning policy

This package follows Semantic Versioning for both the public API and the receipt wire format.

Change typeBumpCompatibility
Add optional payload fieldminorOlder verifiers ignore unknown fields only if the field is namespaced or explicitly marked optional
Add required payload fieldmajorOlder verifiers will reject. Coordinated cutover required
Remove or rename payload fieldmajorBreaking. Issuers must continue emitting v1.x receipts until verifier population catches up
Add new protocol enum valueminorOlder verifiers will reject unknown enum values. Emit the new value only after verifiers support it
Tighten Zod constraint (e.g. format, length)minorBackward-compatible at parse time. The new constraint is forward-only
Verifier library API change (function signature)majorCaller code must update
Verifier library API change (new optional argument)minorExisting callers unaffected

Cross-version verification. Verifier v1.1.x verifies receipts issued under schema v1.0 and schema v1.1. v1.0 receipts will simply lack v1.1 fields (legal_posture, consent_context, etc.) and the verifier treats them as optional. There is no plan to drop v1.0 verification in any v1.x release. Dropping it requires a v2.0 major bump and a deprecation window of at least 12 months.

schema_version field. Receipts carry schema_version: "1.0" or schema_version: "1.1". The verifier dispatches schema validation on this field. Receipts without a schema_version are rejected (reason: "schema_invalid").


Acknowledgements

TrustReceipt is a cross-protocol evidence format. The following external parties define schemas, protocols, or infrastructure that TrustReceipt receipts can reference or attest to. None of these organisations is a formal collaborator on this repository. The relationships are interoperability integrations, not endorsements.

Protocol authors (define schema fields)

ProtocolAuthorTrustReceipt schema field
ACP (Agentic Commerce Protocol)OpenAI + Stripeauthorization_scheme: "acp_session_token", protocol: "ACP"
AP2 (Agent Payment Protocol v2)Googleauthorization_scheme: "ap2_mandate_jws", protocol: "AP2", ap2_consent_hash
x402 (stablecoin payment)Coinbase + Cloudflareauthorization_scheme: "evm_permit2" / "svm_token_authorization" / "x402_native", protocol: "x402"
MCAP (Mastercard Agent Pay)Mastercardauthorization_scheme: "mcap_cart_binding", protocol: "MCAP", mcap_consent_hash
MCP (Model Context Protocol)Anthropicauthorization_scheme: "mcp_tool_invocation", protocol: "MCP"
UCP (Universal Commerce Protocol)Googleauthorization_scheme: "ucp_rule_set_plus_agent_token", protocol: "UCP"

Active runtime providers (wired in trust_provider_assertions[])

These providers produce structured assertions that the recomputeLegalPosture logic in verify-1.1.ts reads when determining the verifier-authoritative LegalPosture. Use the exported type predicates (isRfc9421ProviderAssertion, isHumanProviderAssertion, isVisaTapProviderAssertion) to narrow to the typed shapes defined in types-1.1.ts.

ProviderAssertion provider fieldIntegration
IETF RFC 9421 (HTTP Message Signatures)"rfc9421-native"Verifies HTTP Message Signatures from any agent with a public JWKS endpoint. The issuer wires this in optionally
HUMAN Security — AgenticTrust"human"Optional agent-identity integration. No HUMAN SDK is imported in this verifier package
Visa TAP (Trusted Agent Protocol)"visa"Validated when signer domain is *.visa.com or *.visa.net with tag "agent-browser-auth" or "agent-payer-auth"

Issuer-side infrastructure (not used by this verifier package)

ToolRole
freeTSADefault Phase-1 RFC 3161 timestamp authority. The URL is per receipt (tsa_endpoint field), not hardcoded here
AWS KMSEd25519 issuer signing keys and HMAC CMKs for PII-sourced hashes; handled by sibling package trust-receipt-kms-signer

Trademark Notice

TrustReceipt is not affiliated with, endorsed by, or officially supported by Mastercard, Anthropic, Skyfire, Coinbase, HUMAN Security, Visa, or any other named protocol owner or company referenced in this specification. Protocol names (AP2, MCAP, ACP, MCP, x402, UCP) are used descriptively to indicate interoperability targets only. All trademarks and registered marks are the property of their respective owners.


License

MIT, see LICENSE. Copyright Trusteed (trusteed.xyz), 2026.

evaluation_id: two properties consumers must not assume

evaluation_id was added in 0.4.0. It makes a receipt point at the enforcement record that produced its verdict, so an adjudicator can request that record by identity instead of guessing from timestamp proximity. Two properties matter to consumers:

It identifies the evaluation, not the operation. A cached verdict is served as-is, so several distinct operations can legitimately carry the same evaluation_id. It is not unique per receipt and must never be used as an idempotency key.

Its absence is a statement, not missing data. The reference issuer emits the field only when it resolves to a record that can actually be fetched. Two cases where it is deliberately omitted:

  • the verdict came from cache, so the identity belongs to an earlier evaluation rather than to this operation;
  • the decision was not written to the audit table (the ALLOW branch is sampled, so most permitted operations have no record to point at).

Emitting the identity anyway would invite a third party to request an audit record that nobody wrote. An absent evaluation_id means the receipt does not claim an evaluation. It does not mean the evaluation is hidden.