<?xml version="1.0" encoding="utf-8"?>
<!-- Source of truth for the Vaara Receipt Internet-Draft.
     Mirrors SPEC.md (repo root). Regenerate the rendered text by running
     xml2rfc with the text/html options. See ietf/README.md for the toolchain. -->
<rfc xmlns:xi="http://www.w3.org/2001/XInclude"
     version="3"
     ipr="trust200902"
     submissionType="independent"
     category="info"
     docName="draft-sirkkavaara-vaara-receipt-01"
     tocInclude="true"
     sortRefs="true"
     symRefs="true">

  <front>
    <title abbrev="Vaara Receipt">The Vaara Receipt: A Recomputable Receipt Format for Decisions About Agent Actions</title>
    <seriesInfo name="Internet-Draft" value="draft-sirkkavaara-vaara-receipt-01"/>

    <author fullname="Henri Sirkkavaara" initials="H." surname="Sirkkavaara">
      <organization>Vaara</organization>
      <address>
        <email>hello@vaara.io</email>
        <uri>https://github.com/vaaraio/vaara</uri>
      </address>
    </author>

    <date year="2026" month="June" day="23"/>

    <area>General</area>
    <workgroup>Independent Submission</workgroup>
    <keyword>receipt</keyword>
    <keyword>audit</keyword>
    <keyword>agent</keyword>
    <keyword>provenance</keyword>
    <keyword>canonicalization</keyword>
    <keyword>timestamp</keyword>

    <abstract>
      <t>This document specifies vaara.receipt/v1, a signed and independently
      recomputable record that binds a decision about an agent action to the
      evidence the decision was made on, and optionally to one or more external
      timestamp anchors. The format is canonicalized with the JSON
      Canonicalization Scheme (JCS) so that any third party can recompute its
      digests and verify its signature without access to the issuer.</t>
      <t>The receipt's trust is root-agnostic: the same record is verifiable with
      or without a hardware trusted execution environment and is re-expressible
      as an IETF RATS Entity Attestation Result. Downstream specifications (a
      payment rail, a compliance regime, a framework integration) define profiles
      that pin to a version of this document and add only their own evidence
      schema; they do not redefine the envelope. The format described here is
      deployed and is recomputed today by independent implementers from public
      conformance vectors.</t>
    </abstract>
  </front>

  <middle>

    <section anchor="intro" numbered="true">
      <name>Introduction</name>
      <t>A Vaara receipt is a signed, independently recomputable record that binds
      a decision about an agent action to the evidence it was made on, and
      optionally to one or more external timestamp anchors. Any system that emits
      or consumes Vaara receipts conforms to this document. Downstream
      specifications define profiles that pin to a version of this document and
      add only their own evidence schema; they do not redefine the envelope.</t>

      <t>The receipt's trust is root-agnostic. The same record is verifiable with
      or without a hardware TEE and re-expressible as an IETF RATS
      (<xref target="RFC9334"/>) Entity Attestation Result (an AR4SI vector,
      <xref target="I-D.ietf-rats-ear"/>), whether rooted in a TPM 2.0 host, an
      AMD SEV-SNP confidential VM, or software alone. The signature and the optional external time anchor carry
      the evidence, not a single trust root.</t>

      <t>This document packages a format that already ships and is already
      recomputed by independent implementers. The executable conformance fixtures
      live under tests/vectors/ in the source repository
      (<xref target="VAARA-REPO"/>) with a dependency-light checker
      (_check_independent.py) that imports only the standard library, a signature
      library, and a JCS implementation.</t>

      <section anchor="terminology" numbered="true">
        <name>Terminology</name>
        <t>The key words "MUST", "MUST NOT", "REQUIRED", "SHOULD", and "MAY" in
        this document are to be interpreted as described in RFC 2119
        (<xref target="RFC2119"/>).</t>
      </section>
    </section>

    <section anchor="canon" numbered="true">
      <name>Canonicalization</name>
      <t>All digests and all signed payloads in this specification are computed
      over the JSON Canonicalization Scheme (JCS, <xref target="RFC8785"/>). The
      canonicalization label for the evidenceRef.canonicalization field
      (<xref target="evidence"/>) is "jcs-rfc8785". The values "JCS" and
      "jcs-json-v1" are accepted aliases for the same algorithm; producers SHOULD
      emit "jcs-rfc8785", and consumers MUST accept all three.</t>
      <t>A digest is written "sha256:" followed by the lowercase hexadecimal
      SHA-256 (<xref target="FIPS180-4"/>) of the JCS-canonical bytes of the
      referenced object.</t>
    </section>

    <section anchor="envelope" numbered="true">
      <name>The Receipt Envelope</name>
      <t>A receipt is a JSON object with these top-level members:</t>

      <table anchor="envelope-fields">
        <name>Receipt envelope members</name>
        <thead>
          <tr><th>Field</th><th>Type</th><th>Required</th><th>Meaning</th></tr>
        </thead>
        <tbody>
          <tr><td>version</td><td>integer</td><td>MUST</td><td>Envelope version. 1 for this document.</td></tr>
          <tr><td>alg</td><td>string</td><td>MUST</td><td>Signature algorithm. ES256 in v1; ML-DSA-65 MAY be offered as a post-quantum scheme.</td></tr>
          <tr><td>backLink</td><td>object</td><td>MUST</td><td>Binds this receipt to its attestation/predecessor: attestationDigest, attestationNonce.</td></tr>
          <tr><td>decisionDerived</td><td>object</td><td>MUST</td><td>The decision and the evidence it derives from. See <xref target="evidence"/>.</td></tr>
          <tr><td>issuerAsserted</td><td>object</td><td>MUST</td><td>Issuer-asserted identity claims: iss, sub, iat, nonce, alg, secretVersion.</td></tr>
          <tr><td>signature</td><td>string</td><td>MUST</td><td>Detached signature, hex. For ES256, the 64-byte r||s pair (128 hex chars).</td></tr>
          <tr><td>timestampAnchors</td><td>array</td><td>MAY</td><td>External time attestations over this receipt. See <xref target="anchors"/>.</td></tr>
        </tbody>
      </table>

      <t>The ES256 algorithm label and its 64-byte r||s signature encoding are as
      defined for "ES256" in JSON Web Algorithms (<xref target="RFC7518"/>). The
      ML-DSA-65 scheme is as defined in <xref target="FIPS204"/>.</t>

      <section anchor="signed-payload" numbered="true">
        <name>Signed Payload</name>
        <t>The signature is computed over the JCS-canonical bytes of the object
        containing exactly these members, in this set, with their receipt
        values:</t>
        <sourcecode type="abnf"><![CDATA[
("version", "alg", "backLink", "decisionDerived", "issuerAsserted")
]]></sourcecode>
        <t>"signature" and "timestampAnchors" are NOT part of the signed payload:
        a receipt can gain anchors after signing without invalidating the
        signature. A consumer MUST verify the signature by reconstructing this
        payload, canonicalizing it, and checking it against the public key under
        "alg".</t>
      </section>
    </section>
    <section anchor="evidence" numbered="true">
      <name>Evidence Binding (decisionDerived.evidenceRef)</name>
      <t>decisionDerived carries the decision (decision, decidedAt, policyId,
      reason, riskScore, thresholdAllow, thresholdBlock) and one evidenceRef
      object that binds the decision to a recomputable evidence record:</t>

      <table anchor="evidenceref-fields">
        <name>evidenceRef members</name>
        <thead>
          <tr><th>Field</th><th>Meaning</th></tr>
        </thead>
        <tbody>
          <tr><td>canonicalization</td><td>The label from <xref target="canon"/> (jcs-rfc8785 / JCS / jcs-json-v1).</td></tr>
          <tr><td>digest</td><td>"sha256:" of the JCS-canonical evidence record.</td></tr>
          <tr><td>ref</td><td>An opaque locator for the evidence record (profile-defined).</td></tr>
          <tr><td>schema</td><td>The schema id of the evidence record (profile-defined).</td></tr>
        </tbody>
      </table>

      <t>The binding is recomputable: given the receipt and the evidence record, a
      third party confirms that sha256(JCS(evidence_record)) equals
      evidenceRef.digest with no access to the issuer. This is the property
      independent implementers verify today.</t>
    </section>

    <section anchor="anchors" numbered="true">
      <name>Timestamp Anchors (timestampAnchors)</name>
      <t>A timestamp anchor is an external attestation that this receipt existed no
      later than a stated time. Anchors are additive and optional. Each anchor
      binds the anchored digest, which is "sha256:" of the JCS-canonical signed
      payload (<xref target="signed-payload"/>), so an anchor commits to the exact
      signed receipt without depending on later anchors.</t>

      <sourcecode type="json"><![CDATA[
{
  "method": "rfc3161",
  "anchoredDigest": "sha256:...",
  "token": "<method-specific time token>",
  "authority": "<optional human-readable authority id>"
}
]]></sourcecode>

      <t>Registered methods (the registry is open; a profile MAY register more):</t>

      <table anchor="anchor-methods">
        <name>Timestamp anchor methods</name>
        <thead>
          <tr><th>method</th><th>What it is</th><th>Who can produce it</th></tr>
        </thead>
        <tbody>
          <tr>
            <td>rfc3161</td>
            <td>An RFC 3161 (<xref target="RFC3161"/>) timestamp token from any Time-Stamping Authority.</td>
            <td>Self-hostable (e.g. OpenSSL ts); needs no third party.</td>
          </tr>
          <tr>
            <td>rfc3161-eidas-qualified</td>
            <td>An RFC 3161 token from a qualified TSA under eIDAS (<xref target="eIDAS"/>).</td>
            <td>A qualified trust service provider. Adds legal / court-admissible weight; this is the only thing the qualification adds over rfc3161.</td>
          </tr>
          <tr>
            <td>ledger</td>
            <td>A commitment of the anchored digest to a public ledger; the block time bounds existence.</td>
            <td>Self-producible; trust-minimized, no TSA.</td>
          </tr>
        </tbody>
      </table>

      <t>A receipt MAY carry several anchors of different methods. The technical
      anchor (rfc3161, ledger) and the legal anchor (rfc3161-eidas-qualified) are
      independent: a producer can stand up its own time evidence and add qualified
      legal weight as a separate, swappable method. No single anchor method is
      load-bearing for the receipt's integrity, which rests on the
      <xref target="signed-payload"/> signature.</t>
    </section>

    <section anchor="profiles" numbered="true">
      <name>Profiles</name>
      <t>A profile is a downstream specification that uses this envelope unchanged
      and defines only its own evidence record (the schema and contents behind
      evidenceRef), plus any join keys it needs. A profile MUST state the
      vaara.receipt/vN version it pins to and SHOULD ship recomputable vectors.</t>

      <t>There is one binding mechanism, not one per plane. Each named profile
      (<xref target="profile-x402"/>, <xref target="profile-authz"/>,
      <xref target="profile-ap2"/>, <xref target="profile-tap"/>) names an external
      artifact by content address and binds it through this envelope unchanged; they
      differ only in which artifact is hashed and the evidenceRef.ref label.
      <xref target="profile-generic"/> states that mechanism in schema-agnostic form:
      a single binding that does not depend on what is connected to it. The named
      profiles are instances of it, kept because a given ecosystem pins to a label it
      recognizes as its own.</t>

      <section anchor="registry" numbered="true">
        <name>Registry</name>
        <t>The profiles below pin to vaara.receipt/v1. Vector paths are relative
        to the source repository (<xref target="VAARA-REPO"/>).</t>
        <table anchor="profile-registry">
          <name>Profile registry</name>
          <thead>
            <tr><th>Profile</th><th>Evidence schema</th><th>Vectors</th></tr>
          </thead>
          <tbody>
            <tr><td>x402 settlement binding</td><td>x402.settlement.*/v0</td><td>tests/vectors/x402_settlement_v0/</td></tr>
            <tr><td>authorization decision</td><td>vaara.authorization/v0</td><td>tests/vectors/authorization_v0/, tests/vectors/contiguity_v0/, tests/vectors/class_gate_v0/</td></tr>
            <tr><td>AP2 checkout binding</td><td>vaara.authorization/v0 (names AP2 PEF frame_id)</td><td>tests/vectors/ap2_v0/</td></tr>
            <tr><td>TAP request binding</td><td>tap.request/v0</td><td>tests/vectors/tap_v0/</td></tr>
            <tr><td>generic external execution evidence</td><td>vaara.authorization/v0 (names an external_execution_evidence slot)</td><td>tests/vectors/external_evidence_v0/</td></tr>
          </tbody>
        </table>
      </section>

      <section anchor="profile-x402" numbered="true">
        <name>Profile Example: x402 Settlement Binding</name>
        <t>This profile binds an x402 payment settlement to a Vaara receipt across
        an action lifecycle, on a generic rail and on the Sui exact-payment rail.
        It adds:</t>
        <ul>
          <li>A settlement record (schema = x402.settlement.&lt;rail&gt;/v0) whose
          JCS digest is the receipt's evidenceRef.digest.</li>
          <li>A join key actionRef = sha256(JCS({agentId, actionType, scope,
          timestampMs, seq, terminal})), carried on the settlement, so an
          in-progress receipt (terminal: false) cannot be presented where the
          terminal one is required.</li>
        </ul>
        <t>A third party recomputes three per-step verdicts (action-ref
        recomputes, settlement binding resolves, signature verifies) and one
        lifecycle verdict, with only the settlement and the receipt in hand. See
        _check_independent.py in the vectors directory.</t>
      </section>

      <section anchor="profile-authz" numbered="true">
        <name>Profile Example: Authorization Decision</name>
        <t>This profile turns an enforcement decision into a receipt. A credential
        broker authorizes a tool call against a signed, attestation-bound grant
        with typed capability scopes; the gateway's verdict, allow or deny, is
        minted as a receipt instead of being discarded. The decision maps onto the
        envelope verdict vocabulary: an allowed call is "allow", a refused call is
        "block" carrying the machine reason (capability_exceeded, binding_unknown,
        missing_credential, ...) as decisionDerived.reason. It adds:</t>
        <ul>
          <li>An authorization record (schema = vaara.authorization/v0) whose JCS
          digest is the receipt's evidenceRef.digest. It binds toolName, tenantId,
          the grant by content address (grantFingerprint = sha256(JCS(signed
          grant))), the runtime argument commitment (argsCommitment =
          sha256(JCS(args))), the evaluated capabilities, and the verdict /
          reason.</li>
          <li>The raw arguments never enter the record; only their commitment
          does, so the receipt is publishable while the arguments stay private. An
          auditor holding the arguments out of band recomputes the commitment and
          re-runs the verdict.</li>
          <li>An optional coverage block names the observation boundary the
          decision was made under, inside the record and therefore under the
          signature. It binds the boundary (the chokepoint identity), the
          serverFingerprint (the exact capability surface in scope,
          manifest:sha256(JCS(tools)) or the command hash), and a scope literal
          stating that only calls routed through the chokepoint are observed. A
          tool reached on an out-of-band path is out of coverage. The block is
          absent when no boundary is asserted, leaving the record byte-identical to
          a coverage-free decision.</li>
          <li>An optional completeness block scopes a sequence to that boundary,
          inside the record and therefore under the signature. It binds the
          boundaryId (the same boundary the coverage block names), a monotonic seq
          starting at 0 with no gaps by construction, and a runningCount equal to
          the total receipts issued under the boundary up to and including this one
          (runningCount = seq + 1). The block is absent when no sequence is
          asserted, leaving the record byte-identical to a completeness-free
          decision.</li>
          <li>An optional sealing record finalizes the boundary: a terminal
          completeness block ({boundaryId, sealed: true, total: N}) that pins the
          boundary's final count independently of the per-record sequence. It is
          additive and emitted once the boundary is closed; a boundary that is never
          sealed verifies exactly as before, with the seal absent and the stream
          byte-identical. The seal may also carry maxClass, the highest action class
          the boundary authorized; it bounds a gap's worst case and is itself
          optional.</li>
        </ul>
        <t>A verdict is only as meaningful as what the issuer could see. "allow"
        over an unbounded surface and "allow" over a stated one are identical bytes
        with opposite meaning, so an absent refusal reads as fact only against a
        declared scope: "not refused within this boundary", never "not observed".
        The coverage block carries that boundary in the trace itself, so it is
        recomputable evidence rather than a separate trust root. The verdict stays
        a thin read over it. The chokepoint remains an observer of what passes
        through it, not a claim about what does not.</t>
        <t>The deny case is the point. A refused call leaves a signed,
        content-addressed, portable proof of the non-action: a third party
        recomputes the verdict from the grant and the arguments and confirms the
        refusal, trusting only the issuer's public key. A third party recomputes
        five verdicts per case (grant fingerprint, argument commitment, capability
        verdict, evidence binding, signature) with only the grant, the arguments,
        the evidence, and the receipt in hand. See _check_independent.py.</t>
        <t>Coverage states the boundary; completeness makes a gap inside it
        provable. With the per-boundary seq contiguous by construction and the
        runningCount signed into each record, a dropped receipt is a missing
        sequence number that any holder detects from the receipts alone: the
        highest running count names how many exist, so a short set is
        self-evidently incomplete and the absent seq is named. This needs no issuer
        access and no external witness. The tests/vectors/contiguity_v0/ vectors and
        the "vaara verify-contiguity" surface carry that check.</t>
        <t>The per-record running count alone cannot tell a pure tail truncation
        (holding 0..k with nothing after) from a complete stream, since the latest
        held count is then k + 1 and reads as whole. The optional sealing record
        closes that gap: when a boundary is finalized, the holder expects
        max(seq + 1, runningCount, total) records, so a dropped tail shows as the
        missing range up to the sealed total. A boundary that is never sealed
        verifies exactly as before. One residual remains, and it is irreducible from
        the held set alone: a suffix drop that also suppresses the sealing record
        leaves nothing to detect. Closing that is the job of an rfc3161 anchor over
        the running count (<xref target="anchors"/>), which attests that at time T, N
        receipts existed under the boundary. The layering is seq for order, the hash
        chain for tamper-evidence, the sealing record for a truncated tail, and the
        timestamp anchor for the seal-suppressed residual.</t>
        <t>A gap proves that a record is absent but not what it would have
        authorized. When worst-case-governs is the reading, the seal's optional
        maxClass bounds it: it names the highest action class the boundary
        authorized, so a missing record could have authorized an action of at most
        that class. The verifier surfaces this as worstCaseClass, computed from the
        held set and the seal alone, with no issuer. The field is optional; absent
        it, a gap reports only that a record is missing.</t>
        <t>Beyond bounding a gap at audit time, the sealed maxClass is consumable at
        enforcement time. A chain recipient gating its own next unattended action
        holds a policy set of action classes it will proceed under and permits if and
        only if the sealed worst-case class is a member of that set, failing closed
        when no class is sealed. This is a membership test, not an ordering: this
        document computes no ordering over class labels, so the recipient asks "is
        the sealed class one I permit", never "is it at or below a ceiling". Because
        the seal bounds a gap's worst case at maxClass, a permitted class permits
        even when the boundary has a gap: the recipient consumes the committed bound
        and does not re-derive the chain or query a log. The bound is trustworthy
        under the honest issuer whose seal commits before any tail is trimmed; a seal
        that under-states the class is a reconciliation question against the issuer's
        log, not one this held-set-alone gate answers.</t>
        <t>maxClass lives in the unsigned evidence block, so a recipient MUST NOT
        consume it raw. It rides under signature only through the binding: the seal's
        signed decisionDerived.evidenceRef.digest is "sha256:" + JCS(evidence), so
        recomputing that digest proves the class is the class that was signed. Before
        gating, a recipient MUST verify each receipt's signature and that its
        evidence recomputes to the signed digest; a seal whose binding fails is not
        trusted, contributes no class, and the gate fails closed. Without this, an
        agent loosens the gate by relabeling an irreversible action's class into a
        permitted one while the record signature, which never covered the evidence,
        still verifies. The conformance vectors are in tests/vectors/class_gate_v0/;
        the deny_relabeled case carries exactly this attack and the independent
        checker rejects it.</t>
      </section>
      <section anchor="profile-ap2" numbered="true">
        <name>Profile Example: AP2 Checkout Binding</name>
        <t>This profile binds an AP2 checkout to the post-checkout agent actions a
        credential broker authorizes, so the actions taken after a payment settles
        carry the same recomputable, gap-evident record as the authorization
        decisions in <xref target="profile-authz"/>. It reuses the
        vaara.authorization/v0 evidence record unchanged and adds a join to the AP2
        Payment Evidence Frame (PEF, AP2 PR #274):</t>
        <ul>
          <li>The AP2 checkout emits a PEF whose frame_id = sha256(JCS(frame)),
          with frame_id and signature excluded from the preimage, and whose
          receipt_hash = sha256(JCS(receipt)) content-addresses the wrapped
          Checkout Receipt. Canonicalization is
          urn:x402:canonicalisation:jcs-rfc8785-v1 (JCS / RFC 8785), the same as
          this envelope, so the address joins with no re-canonicalization.</li>
          <li>Each post-checkout authorization receipt names the checkout it
          followed by content address: decisionDerived.evidenceRef.ref =
          ap2:checkout/&lt;frame_id&gt;, under the receipt signature. The AP2 task
          scope is the coverage.boundary (<xref target="profile-authz"/>), and the
          completeness block sequences the actions under it.</li>
        </ul>
        <t>The identity of the checkout is the PEF frame_id, a content address the
        payment side already computes; the completeness of the actions taken under
        it is the vaara.authorization/v0 contiguity stream. A per-action hash says
        an action was recorded; the running count says none inside the AP2 task
        boundary was dropped. A third party recomputes the frame address, confirms
        every receipt names that checkout, resolves each evidence binding, verifies
        each signature, and re-runs the gap check, with only the PEF and the held
        receipts in hand. See tests/vectors/ap2_v0/_check_independent.py. AP2 can
        pin from the point the Checkout Receipt ends rather than define a new
        post-settlement primitive.</t>
      </section>

      <section anchor="profile-tap" numbered="true">
        <name>Profile Example: TAP Request Binding</name>
        <t>This profile binds a Visa Trusted Agent Protocol (TAP) request to the
        action a trusted agent takes under it, across the action lifecycle, so the
        post-authorization record is the same recomputable evidence as any other
        decision receipt. It adds a TAP request evidence record (schema =
        tap.request/v0) whose JCS digest is the receipt's evidenceRef.digest, and the
        join key actionRef = sha256(JCS({agentId, actionType, scope, timestampMs,
        seq, terminal})) carried on the request:</t>
        <ul>
          <li>The trusted agent presents the TAP request to the relying party. The
          decision receipt names it by content address:
          decisionDerived.evidenceRef.digest = sha256(JCS(request)),
          decisionDerived.evidenceRef.ref = tap:request/&lt;actionRef&gt;, both under
          the receipt signature. Canonicalization is JCS / RFC 8785, the same as this
          envelope, so the address joins with no re-canonicalization.</li>
          <li>The lifecycle lives in the join key. Because the action tuple covers
          terminal, the in-progress (terminal: false) request has a different
          actionRef than the final (terminal: true) one, and the in-progress receipt
          does not resolve against the terminal request. A mid-action receipt cannot
          be presented where the final one is required.</li>
        </ul>
        <t>The verdict is recomputable offline. A third party recomputes the action
        ref, resolves the request binding, and verifies the signature with only the
        TAP request, the held receipts, and the issuer's public key, with the TAP
        service offline and no live verifier endpoint to trust. See
        tests/vectors/tap_v0/_check_independent.py. TAP can pin to vaara.receipt/v1
        for the post-authorization record rather than define a new primitive.</t>
      </section>

      <section anchor="profile-generic" numbered="true">
        <name>Profile: Generic External Execution Evidence</name>
        <t>This is the schema-agnostic binding the named profiles above are instances
        of. It takes any external execution-evidence artifact, content-addresses it,
        and binds it through this envelope unchanged, with no field names that depend
        on what produced it. A verifier carrying an external_execution_evidence slot
        (linked_call_id / evidence_hash / evidence_type) resolves that slot against a
        vaara.receipt/v1 authorization receipt as the recomputable producer:</t>
        <ul>
          <li>evidence_hash = sha256(JCS(evidence_record)), equal to the receipt's
          decisionDerived.evidenceRef.digest, so the slot and the receipt name the
          same recomputable artifact (JCS / RFC 8785, no re-canonicalization).</li>
          <li>linked_call_id is the call the receipt names:
          decisionDerived.evidenceRef.ref = mcp:call/&lt;linked_call_id&gt;, under the
          receipt signature.</li>
          <li>evidence_type is the receipt's evidence schema
          (vaara.authorization/v0).</li>
        </ul>
        <t>The trace is the coverage.boundary, and each receipt carries a signed
        completeness block (seq + runningCount), so the held set proves not only that
        each named call's evidence resolves but that none inside the boundary was
        dropped. A slot's evidence_hash alone proves a given record exists; the
        completeness block turns a silent drop into a named gap. The dropped vector
        withholds one record, slot and receipt both, and the signed running count
        still proves it existed.</t>
        <t>A third party recomputes every verdict offline with only the held slots,
        the receipts, and the issuer's public key, with no live verifier endpoint to
        trust. See tests/vectors/external_evidence_v0/_check_independent.py. Any plane
        that emits execution evidence pins here by naming its artifact through this
        slot, rather than defining a new primitive or a profile of its own.</t>
      </section>
    </section>
    <section anchor="conformance" numbered="true">
      <name>Conformance</name>
      <t>An implementation conforms to vaara.receipt/v1 if, for every receipt it
      emits:</t>
      <ol>
        <li>The <xref target="signed-payload"/> signature verifies against the
        stated alg and key.</li>
        <li>evidenceRef.digest equals sha256(JCS(evidence_record)) for the
        referenced record, under one of the <xref target="canon"/>
        canonicalization labels.</li>
        <li>Any timestampAnchors[].anchoredDigest equals the "sha256:" of the JCS
        signed payload of the same receipt.</li>
      </ol>
      <t>The committed vectors plus _check_independent.py are the reference
      conformance suite; running the x402 profile checker and having it exit 0 is a
      passing run for that profile.</t>
    </section>

    <section anchor="versioning" numbered="true">
      <name>Versioning</name>
      <t>The envelope version is the integer "version" field and the
      vaara.receipt/vN schema id. Additive, backward-compatible changes (new
      optional fields, new anchor methods, new profiles) do not bump N. A change to
      the signed-payload field set, the canonicalization, or the signature
      construction bumps N.</t>
    </section>

    <section anchor="security" numbered="true">
      <name>Security Considerations</name>
      <t>The integrity of a receipt rests on the <xref target="signed-payload"/>
      signature over the JCS-canonical signed payload, not on any timestamp anchor
      or trust root. A consumer MUST verify that signature against the public key
      named under "alg" before relying on any field. Because the signed payload
      excludes "signature" and "timestampAnchors", anchors added after signing
      cannot alter the signed content; a consumer MUST recompute each
      anchoredDigest from the signed payload rather than trusting the anchor's
      stated value.</t>
      <t>Recomputability depends entirely on canonicalization. A producer and a
      consumer that disagree on JCS output for the same JSON value will compute
      different digests; implementations MUST use a conformant JCS
      (<xref target="RFC8785"/>) implementation and MUST treat any of the three
      accepted labels as the same algorithm.</t>
      <t>The argument commitment in the authorization profile
      (<xref target="profile-authz"/>) lets a receipt be published while the raw
      arguments stay private, but a low-entropy argument set is open to a
      dictionary attack against the commitment. Producers SHOULD ensure the
      committed object carries sufficient entropy (for example a per-call nonce)
      where argument confidentiality matters.</t>
      <t>An absent refusal is evidence only within a declared coverage boundary
      (<xref target="profile-authz"/>). A reader MUST NOT read a missing receipt as
      "the action did not happen"; without a coverage block it means only "not
      observed", and with one it means "not refused within this boundary". The
      completeness block makes a dropped receipt inside the boundary detectable,
      but a pure tail truncation is not detectable by sequence contiguity alone and
      requires a timestamp anchor over the running count to close.</t>
      <t>A recipient that consumes a sealed maxClass to gate its own next action
      (<xref target="profile-authz"/>) MUST bind the class to the signature before
      acting on it. maxClass sits in the unsigned evidence block and is covered by
      the signature only through the seal's decisionDerived.evidenceRef.digest =
      "sha256:" + JCS(evidence). A recipient MUST verify each receipt's signature and
      that its evidence recomputes to that signed digest; a seal whose binding fails
      contributes no class and the gate fails closed. A recipient that reads maxClass
      raw, without recomputing the binding, can be made to permit an irreversible
      action whose class an agent relabeled into a permitted one while the record
      signature, which never covered the evidence, still verifies. The gate is also
      a membership test over class labels, not an ordering; this document defines no
      ordering over classes, and a recipient MUST NOT infer one.</t>
    </section>

    <section anchor="iana" numbered="true">
      <name>IANA Considerations</name>
      <t>This document has no IANA actions. The timestamp anchor method registry
      (<xref target="anchors"/>) and the profile registry
      (<xref target="registry"/>) are maintained by the specification, not by IANA,
      in this version.</t>
    </section>

  </middle>

  <back>
    <references>
      <name>Normative References</name>

      <reference anchor="RFC2119" target="https://www.rfc-editor.org/info/rfc2119">
        <front>
          <title>Key words for use in RFCs to Indicate Requirement Levels</title>
          <author initials="S." surname="Bradner" fullname="Scott Bradner"/>
          <date year="1997" month="March"/>
        </front>
        <seriesInfo name="BCP" value="14"/>
        <seriesInfo name="RFC" value="2119"/>
        <seriesInfo name="DOI" value="10.17487/RFC2119"/>
      </reference>

      <reference anchor="RFC3161" target="https://www.rfc-editor.org/info/rfc3161">
        <front>
          <title>Internet X.509 Public Key Infrastructure Time-Stamp Protocol (TSP)</title>
          <author initials="C." surname="Adams" fullname="Carlisle Adams"/>
          <author initials="P." surname="Cain" fullname="Pat Cain"/>
          <author initials="D." surname="Pinkas" fullname="Denis Pinkas"/>
          <author initials="R." surname="Zuccherato" fullname="Robert Zuccherato"/>
          <date year="2001" month="August"/>
        </front>
        <seriesInfo name="RFC" value="3161"/>
        <seriesInfo name="DOI" value="10.17487/RFC3161"/>
      </reference>

      <reference anchor="RFC8785" target="https://www.rfc-editor.org/info/rfc8785">
        <front>
          <title>JSON Canonicalization Scheme (JCS)</title>
          <author initials="A." surname="Rundgren" fullname="Anders Rundgren"/>
          <author initials="B." surname="Jordan" fullname="Bret Jordan"/>
          <author initials="S." surname="Erdtman" fullname="Samuel Erdtman"/>
          <date year="2020" month="June"/>
        </front>
        <seriesInfo name="RFC" value="8785"/>
        <seriesInfo name="DOI" value="10.17487/RFC8785"/>
      </reference>

      <reference anchor="FIPS180-4" target="https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf">
        <front>
          <title>Secure Hash Standard (SHS)</title>
          <author>
            <organization>National Institute of Standards and Technology</organization>
          </author>
          <date year="2015" month="August"/>
        </front>
        <seriesInfo name="FIPS" value="PUB 180-4"/>
      </reference>
    </references>

    <references>
      <name>Informative References</name>

      <reference anchor="RFC7518" target="https://www.rfc-editor.org/info/rfc7518">
        <front>
          <title>JSON Web Algorithms (JWA)</title>
          <author initials="M." surname="Jones" fullname="Michael B. Jones"/>
          <date year="2015" month="May"/>
        </front>
        <seriesInfo name="RFC" value="7518"/>
        <seriesInfo name="DOI" value="10.17487/RFC7518"/>
      </reference>

      <reference anchor="RFC9334" target="https://www.rfc-editor.org/info/rfc9334">
        <front>
          <title>Remote ATtestation procedureS (RATS) Architecture</title>
          <author initials="H." surname="Birkholz" fullname="Henk Birkholz"/>
          <author initials="D." surname="Thaler" fullname="Dave Thaler"/>
          <author initials="M." surname="Richardson" fullname="Michael Richardson"/>
          <author initials="N." surname="Smith" fullname="Ned Smith"/>
          <author initials="W." surname="Pan" fullname="Wei Pan"/>
          <date year="2023" month="January"/>
        </front>
        <seriesInfo name="RFC" value="9334"/>
        <seriesInfo name="DOI" value="10.17487/RFC9334"/>
      </reference>

      <reference anchor="I-D.ietf-rats-ear" target="https://datatracker.ietf.org/doc/draft-ietf-rats-ear/">
        <front>
          <title>Attestation Results for Secure Interactions</title>
          <author initials="T." surname="Fossati" fullname="Thomas Fossati"/>
          <author initials="S." surname="Frost" fullname="Simon Frost"/>
          <date year="2026"/>
        </front>
        <seriesInfo name="Internet-Draft" value="draft-ietf-rats-ear"/>
      </reference>

      <reference anchor="FIPS204" target="https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf">
        <front>
          <title>Module-Lattice-Based Digital Signature Standard</title>
          <author>
            <organization>National Institute of Standards and Technology</organization>
          </author>
          <date year="2024" month="August"/>
        </front>
        <seriesInfo name="FIPS" value="PUB 204"/>
      </reference>

      <reference anchor="eIDAS" target="https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32014R0910">
        <front>
          <title>Regulation (EU) No 910/2014 on electronic identification and trust services for electronic transactions in the internal market (eIDAS)</title>
          <author>
            <organization>European Parliament and Council</organization>
          </author>
          <date year="2014" month="July"/>
        </front>
      </reference>

      <reference anchor="VAARA-REPO" target="https://github.com/vaaraio/vaara/blob/main/SPEC.md">
        <front>
          <title>Vaara Receipt Specification (vaara.receipt/v1) and conformance vectors</title>
          <author>
            <organization>Vaara</organization>
          </author>
          <date year="2026"/>
        </front>
      </reference>
    </references>
  </back>
</rfc>
