Skip to main content
Version: v0.11.2

Evidence Carrier Contract

The Evidence Carrier Contract defines a universal interface for carrying verifiable evidence across different transports. Instead of coupling receipt delivery to a single protocol, the contract provides a common type (PeacEvidenceCarrier) and adapter pattern (CarrierAdapter) that work across MCP, A2A, ACP, UCP, x402, and HTTP.


The problem

Verifiable interaction records need to travel across many different protocols. An MCP tool response carries data differently from an A2A task status, which differs from an HTTP header. Without a common interface, every transport needs custom receipt handling logic, and evidence becomes non-portable.

The Evidence Carrier Contract solves this by defining one type that all transports can produce and consume.


PeacEvidenceCarrier

The core type that represents portable evidence. Defined in @peac/kernel (Layer 0, zero runtime dependencies).

PeacEvidenceCarrier
interface PeacEvidenceCarrier {
receipt_ref: string; // sha256(receipt_jws) -- integrity anchor
receipt_jws: string; // Compact JWS (EdDSA, Ed25519)
receipt_url?: string; // Optional HTTPS-only locator hint (max 2048 chars)
policy_binding?: string; // Policy hash at issuance time
issued_at?: string; // ISO 8601 timestamp
carrier_version?: string; // Carrier format version
}

Key properties

  • receipt_ref is always sha256(receipt_jws). Verifiers MUST check this integrity constraint at extraction time.
  • receipt_jws is the signed JWS artifact -- the actual receipt.
  • receipt_url is a locator hint only. It MUST NOT trigger implicit fetch (SSRF prevention). Callers who fetch the URL MUST verify sha256(fetched_jws) === receipt_ref.
  • policy_binding records which policy was in effect when the receipt was issued. Full policy binding evaluation is a Wire 0.2 feature.

CarrierAdapter pattern

Each transport implements a CarrierAdapter<TInput, TOutput> with three operations:

CarrierAdapter
interface CarrierAdapter<TInput, TOutput> {
extract(input: TInput): PeacEvidenceCarrier | null;
attach(output: TOutput, carrier: PeacEvidenceCarrier): TOutput;
validateConstraints?(carrier: PeacEvidenceCarrier): boolean;
}
  • extract pulls a carrier from a protocol-specific container (e.g., MCP _meta, A2A metadata)
  • attach places a carrier into a protocol-specific container
  • validateConstraints checks transport-specific limits (e.g., 64 KB embed for MCP)

Transport implementations

Each transport places evidence in a protocol-appropriate location:

TransportPlacementSize limitPackage
MCP_meta["org.peacprotocol/receipt_ref"] + _meta["org.peacprotocol/receipt_jws"]64 KB embed@peac/mappings-mcp
A2Ametadata[extensionURI].carriers[]64 KB embed@peac/mappings-a2a
ACPCustom headers8 KB header@peac/mappings-acp
UCPWebhook payload64 KB embed@peac/mappings-ucp
x402Response headers/body8 KB header@peac/adapter-x402
HTTPPEAC-Receipt header (compact JWS)8 KB header@peac/middleware-express
Transport-neutral evidence

The same receipt can be carried across any of these transports without modification. The carrier adapter handles placement and extraction; the receipt itself is identical regardless of transport.


Receipt reference integrity

The receipt_ref field is the SHA-256 hash of receipt_jws. This is verified at extraction time:

Integrity check
import { computeReceiptRef } from '@peac/schema';

const carrier = extractMcpCarrier(toolResponse);
const expectedRef = computeReceiptRef(carrier.receipt_jws);

if (carrier.receipt_ref !== expectedRef) {
throw new Error('Receipt reference integrity check failed');
}

This prevents tampering: if a transport modifies the JWS during delivery, the reference check will fail.


Discovery

Agents and servers discover PEAC support through a 3-step process:

  1. Agent Card (A2A): check capabilities.extensions[] for https://www.peacprotocol.org/ext/traceability/v1
  2. Well-known: check /.well-known/peac-issuer.json for issuer configuration
  3. Header probe: check for PEAC-Receipt header in HTTP responses

Next steps