<?xml version="1.0" encoding="utf-8"?>
<rfc version="3" docName="draft-janbjer-dewp-01" category="info" ipr="trust200902" submissionType="IETF" xml:lang="en" tocInclude="true" tocDepth="3" sortRefs="true" symRefs="true">
  <front>
    <title abbrev="DEWP">Deterministic Evidence &amp; Witness Protocol (DEWP) Specification</title>
    <seriesInfo name="Internet-Draft" value="draft-janbjer-dewp-01" />
    <author fullname="Christian Janbjer" initials="C." surname="Janbjer">
      <organization>Janbjer Technologies AB</organization>
      <address>
        <email>hello@intyga.com</email>
      </address>
    </author>
    <date year="2026" month="October" day="19" />
    <keyword>audit</keyword>
    <keyword>witness</keyword>
    <keyword>Merkle</keyword>
    <keyword>inclusion proof</keyword>
    <keyword>evidence</keyword>
    <abstract>
      <t>This document specifies the Deterministic Evidence and Witness Protocol (DEWP), a format for tamper-evident audit commitments and offline evidence verification. It defines canonical event preimages, domain-separated hashing, two-tier Merkle inclusion proofs, per-tenant sequence checks, checkpoint continuity, and independently verifiable anchor evidence. Verifiers report content, commitment, embedded-signature, and anchor verification separately. Completeness checks cover committed events within the evaluated range; they cannot prove that an event was never withheld before commitment or that a committed description accurately records a real-world action. Version 1.1 adds execution evidence: a record that an approved action was dispatched, statements signed by independent evidence sources about whether it took effect, and a status a verifier derives under its own trust; that status reports what a trusted source observed, not what physically happened. The specification also defines a streaming format that the reference implementations do not yet implement.</t>
    </abstract>
  </front>
  <middle>
    <section anchor="introduction" numbered="false">
      <name>Introduction</name>
      <t>This document specifies DEWP; the companion protocol is described in <xref target="DIV" />. This is an individual Internet-Draft and does not imply IETF endorsement.</t>
      <t>The numbered sections retain the numbering of the source specification, including lettered sections, so existing technical cross-references remain usable.</t>
      <t>The companion schemas and conformance vectors are pinned by <xref target="Artifacts" />. Paths beginning with docs/schemas/dewp/ correspond to schemas/dewp/ in that snapshot; packages/mcp-schemas/vectors/ corresponds to vectors/. Other repository paths are informative implementation locations. Schema identifiers are identifiers, not permission to substitute an unversioned schema for the pinned snapshot.</t>
      <t>Long source-code lines use the reversible folding convention of <xref target="RFC8792" />. Unfold a marked block before parsing it or computing any cryptographic digest.</t>
      <t>Related work includes <xref target="I-D.williams-intent-token" />, which describes a pre-execution authorization token and an audit structure.</t>
    </section>
    <section numbered="false">
      <name>Requirements Language</name>
      <t>The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 <xref target="RFC2119" />
        <xref target="RFC8174" /> when, and only when, they appear in all capitals, as shown here.</t>
    </section>
    <section anchor="s-1" numbered="false">
      <name>1. Scope &amp; Explicit Non-Goals</name>
      <t>DEWP narrowly defines the cryptographic structures and verification algorithms required for post-execution tamper-evidence and audit completeness.</t>
      <section anchor="s-1-1" numbered="false">
        <name>1.1 Scope</name>
        <t>DEWP specifies exclusively:</t>
        <ol type="1">
          <li>
            <t>The canonical event preimage schema and deterministic serialization rules using JSON Canonicalization Scheme (JCS) <xref target="RFC8785" />.</t>
          </li>
          <li>
            <t>Domain-separated hashing rules (<tt>0x00</tt> leaf tag, <tt>0x01</tt> node tag, <tt>0x02</tt> empty root tag, <tt>0x03</tt> anchor tag, <tt>0x04</tt> checkpoint-chain tag) to prevent cross-domain signature and second-preimage attacks.</t>
          </li>
          <li>
            <t>The two-tier Merkle log tree hierarchy (Tier 1 Block Roots and Tier 2 Daily Checkpoint Roots).</t>
          </li>
          <li>
            <t>The schema for portable single-event (<tt>dewp.audit.inclusion-proof</tt>), multi-event (<tt>dewp.audit.evidence-bundle</tt>), and streaming (<tt>dewp.audit.evidence-stream</tt>) proof containers.</t>
          </li>
          <li>
            <t>Content-Verified, Commitment-Only (Privacy Redacted), Signature-Verified, and Fully-Verified validation levels.</t>
          </li>
          <li>
            <t>The offline inclusion and gapless completeness verification procedure executed by a Relying Party or auditor.</t>
          </li>
          <li>
            <t>Execution evidence records, the Execution Evidence Statement, and the Execution Status a verifier derives from them (§4.7).</t>
          </li>
        </ol>
      </section>
      <section anchor="s-1-2" numbered="false">
        <name>1.2 Out-of-Scope (Explicit Non-Goals)</name>
        <t>DEWP explicitly does <strong>NOT</strong> define:</t>
        <ul>
          <li>
            <t>
              <strong>Pre-Execution Intent Authorization:</strong> Synchronous step-up approvals and intent binding are governed by companion specifications such as DIV <xref target="DIV" />.</t>
          </li>
          <li>
            <t>
              <strong>Storage Engine &amp; Database Schemas:</strong> Physical table storage, indexing, and database engine choice are implementation details.</t>
          </li>
          <li>
            <t>
              <strong>Public Anchor Transports:</strong> Specific block-anchoring transports (e.g., Git repositories, public transparency logs, distributed ledgers) are external integration choices.</t>
          </li>
          <li>
            <t>
              <strong>Physical Execution:</strong> §4.7 records what a caller-trusted evidence source reported about an action's effect. Whether that report is true of the physical world, and how a source measures it, are outside this specification (Appendix A is guidance only).</t>
          </li>
        </ul>
      </section>
      <section anchor="s-1-3" numbered="false">
        <name>1.3 Relationship to DIV</name>
        <t>DEWP and DIV are independent but complementary protocol primitives.</t>
        <t>DIV (<xref target="DIV" />) provides pre-execution authorization evidence by cryptographically binding approval to an intended execution payload.</t>
        <t>DEWP provides post-execution evidence by cryptographically committing completed events into an append-only audit structure.</t>
        <t>A compliant implementation MAY combine DIV Intent Proofs with DEWP Witness Events by embedding the verified DIV payload, digest hash (<tt>divIntentHash</tt>), and signature metadata into the DEWP Canonical Preimage (array indices 8, 9, 10).</t>
        <t>The presence of a DEWP Witness Event MUST NOT be interpreted as proof that DIV authorization occurred unless the associated DIV proof material is independently verified.</t>
        <t>DIV's reserved <tt>evidence</tt> field (DIV §4.3.4) is a different thing: external facts an authorization may be conditioned on <em>before</em> execution, inside the signed approval. Whether the approved action then took effect is recorded <em>after</em> execution, in DEWP (§4.7), and never alters the approval.</t>
        <t>Nothing in the preimage or its verification ties these slots to DIV specifically: indices 6..10 carry generic signer, payload and signature material, and verification per §4.6.1 checks the signature over the payload bytes without ever interpreting the payload's schema. Any pre-execution protocol whose proof is a signature over a canonical byte string can occupy them under the same rules; DIV is the companion specification this document uses as its worked example.</t>
      </section>
    </section>
    <section anchor="s-2" numbered="false">
      <name>2. Terminology &amp; Core Definitions</name>
      <ul>
        <li>
          <t>
            <strong>Witness Event:</strong> A canonical record representing a completed state-mutating operation, approval event, or security state transition.</t>
        </li>
        <li>
          <t>
            <strong>Canonical Preimage:</strong> The exact, deterministic byte sequence constructed from an event's attributes that forms the input to the leaf hash function.</t>
        </li>
        <li>
          <t>
            <strong>Leaf Hash:</strong> The domain-separated SHA-256 digest computed over a Canonical Preimage (<tt>hashLeaf(CanonicalPreimage)</tt>).</t>
        </li>
        <li>
          <t>
            <strong>Block Root (Tier 1):</strong> The Merkle root computed over a short-cycle batch of Leaf Hashes.</t>
        </li>
        <li>
          <t>
            <strong>Checkpoint Root / Daily Root (Tier 2):</strong> The Merkle root computed over a sequential sequence of <tt>hashLeaf(BlockRoot)</tt> values, committed to an external public anchor.</t>
        </li>
        <li>
          <t>
            <strong>Proof Step:</strong> An element in a Merkle path carrying the <strong>sibling's</strong> hash (<tt>siblingHash</tt>) and the sibling's position relative to the current node (<tt>siblingPosition</tt>: <tt>"LEFT"</tt> or <tt>"RIGHT"</tt>), ordered strictly from <strong>leaf to root</strong>.</t>
        </li>
        <li>
          <t>
            <strong>Inclusion Proof:</strong> A two-hop Merkle proof comprising a <tt>blockProof</tt> (leaf to Block Root) and a <tt>checkpointProof</tt> (<tt>hashLeaf(BlockRoot)</tt> to Daily Root), each accompanied by the bounds (<tt>leafIndex</tt>/<tt>blockLeafCount</tt>, <tt>checkpointLeafIndex</tt>/<tt>checkpointLeafCount</tt>) that make it a proof of membership rather than a proof that some path exists.</t>
        </li>
        <li>
          <t>
            <strong>Verification Properties:</strong> Four independent boolean properties a verifier evaluates — <tt>commitmentVerified</tt>, <tt>contentVerified</tt>, <tt>signatureVerified</tt>, and <tt>anchorVerified</tt> (see §7.1). These are orthogonal: anchor verification is NOT a strict superset of signature verification.</t>
        </li>
        <li>
          <t>
            <strong>Verification Level:</strong> A summary classification derived from the four properties: <tt>INVALID</tt>, <tt>COMMITMENT_VERIFIED</tt>, <tt>CONTENT_VERIFIED</tt>, <tt>SIGNATURE_VERIFIED</tt>, or <tt>FULLY_VERIFIED</tt>.</t>
        </li>
        <li>
          <t>
            <strong>Content-Verified Entry:</strong> A proof entry where the full Canonical Preimage is present, enabling offline verification of both tree inclusion and exact event parameter binding.</t>
        </li>
        <li>
          <t>
            <strong>Commitment-Only Entry:</strong> A privacy-redacted proof entry where event content has been purged under data retention rules, enabling offline verification of historical commitment without exposing sensitive event text.</t>
        </li>
        <li>
          <t>
            <strong>Tenant Sequence (<tt>tenantSeq</tt>):</strong> A strictly monotonic, gapless 64-bit integer assigned sequentially to a tenant's committed audit events.</t>
        </li>
        <li>
          <t>
            <strong>Global Sequence (<tt>seq</tt>):</strong> A strictly monotonic numeric ordering counter (stringified 64-bit integer). Distinct from the event <strong>identifier</strong>
            <tt>id</tt>.</t>
        </li>
        <li>
          <t>
            <strong>Evidence Source:</strong> A party independent of the one that sent a command, which observes the command's effect and signs an Execution Evidence Statement about it (§4.7.2).</t>
        </li>
        <li>
          <t>
            <strong>Execution Evidence Statement:</strong> The canonical object of type <tt>dewp-execution-evidence</tt> an evidence source signs, binding a verdict and an observation to one approval's nonce and payload hash (§4.7.2).</t>
        </li>
        <li>
          <t>
            <strong>Execution Status:</strong> The per-approval result a verifier derives from Approval, Dispatch and Evidence Records under its own evidence-source trust: <tt>confirmed</tt>, <tt>contradicted</tt>, <tt>unconfirmed</tt>, <tt>pending</tt> or <tt>not_reported</tt>, reported together with the §4.7.4 findings (dispatch conflict, late contradiction, untrusted contradiction, window capped, source continuity).</t>
        </li>
        <li>
          <t>
            <strong>Event Identifier (<tt>id</tt>):</strong> An opaque unique event ID (RECOMMENDED RFC 9562 <xref target="RFC9562" /> UUIDv7 for time-ordered sortability). It is an identity value, not an arithmetic ordering counter; verifiers MUST NOT perform <tt>+1</tt> continuity arithmetic on <tt>id</tt>.</t>
        </li>
      </ul>
    </section>
    <section anchor="s-3" numbered="false">
      <name>3. Protocol Invariants</name>
      <t>A compliant DEWP implementation MUST satisfy the following structural invariants:</t>
      <ol type="1">
        <li>
          <t>
            <strong>Domain-Separated Cryptographic Hashing</strong>
          </t>
          <t>To prevent an interior-node hash (or an empty-root, anchor, or chain preimage) from being reinterpreted as a valid leaf preimage, hashing MUST employ distinct one-byte domain-separation prefixes. This applies the Merkle audit-path domain-separation technique of RFC 6962 <xref target="RFC6962" /> §2.1 (leaf tag vs node tag), extended here with empty-root, anchor, and chain tags:</t>
          <ul>
            <li>
              <t>Leaf Hash: <tt>SHA-256(byte(0x00) || UTF8(CanonicalPreimageBytes))</tt>
              </t>
            </li>
            <li>
              <t>Node Hash: <tt>SHA-256(byte(0x01) || hexDecode(LeftChildHash) || hexDecode(RightChildHash))</tt>
              </t>
            </li>
            <li>
              <t>Empty Tree Root: <tt>SHA-256(byte(0x02))</tt>
              </t>
            </li>
            <li>
              <t>Anchor Signature Preimage: <tt>SHA-256(byte(0x03) || UTF8(JCS(AnchorPreimageArray)))</tt>
              </t>
            </li>
            <li>
              <t>Checkpoint Chain Hash: <tt>SHA-256(byte(0x04) || UTF8(JCS(ChainPreimageArray)))</tt> (§5.4)</t>
            </li>
          </ul>
        </li>
        <li>
          <t>
            <strong>Deterministic Preimage Binding</strong>
          </t>
          <t>For unredacted entries, the Relying Party MUST verify that the Leaf Hash in an Inclusion Proof equals <tt>leafHash(CanonicalPreimage)</tt>.</t>
        </li>
        <li>
          <t>
            <strong>Two-Hop Hierarchy Binding &amp; Leaf-to-Root Proof Ordering</strong>
          </t>
          <t>A valid inclusion proof MUST satisfy two distinct Merkle path verification steps ordered strictly from <strong>leaf to root</strong>, and each step MUST be bounded by the leaf's position and its tree's leaf count (§11.1):</t>
          <ul>
            <li>
              <t>Step 1 (Event Leaf): <tt>verifyMerkleProof(leaf, blockProof, blockRoot, {index: leafIndex, leafCount: blockLeafCount}) == true</tt>
              </t>
            </li>
            <li>
              <t>Step 2 (Block Root): <tt>verifyMerkleProof(hashLeaf(blockRoot), checkpointProof, dailyRoot, {index: checkpointLeafIndex, leafCount: checkpointLeafCount}) == true</tt>
              </t>
            </li>
          </ul>
          <t>The bounds are REQUIRED, not advisory. Because this tree self-pairs an unpaired trailing node, an unbounded path check accepts a proof for a leaf that was never in the tree — see §11.1.</t>
        </li>
        <li>
          <t>
            <strong>Gapless Tenant Completeness (of Committed Events)</strong>
          </t>
          <t>Every committed tenant event MUST carry a strictly monotonic <tt>tenantSeq</tt> integer counter (<tt>tenantSeq_N+1 == tenantSeq_N + 1</tt>).</t>
          <t>Auditors verifying an Evidence Bundle MUST reject any sequence containing gaps, duplicates, or out-of-order <tt>tenantSeq</tt> values.</t>
          <t>
            <strong>Which copy of the counter is read is normative.</strong> An Evidence Bundle carries <tt>tenantSeq</tt> in more than one place and only one of them is covered by the Merkle commitment. The check MUST read the leaf-bound counter — the final element of the Canonical Preimage (§4.1) — for every entry that carries a preimage; the sibling <tt>event.tenantSeq</tt> is a display copy that nothing signs, so a check run over it is satisfied by renumbering it. §7.2 gives the full source-precedence, disclosure and range cross-check rules.</t>
          <t>
            <strong>Scope of the guarantee:</strong> This detects <strong>modification or deletion of already-committed events</strong> — a producer cannot silently drop, reorder, or alter an event that was assigned a <tt>tenantSeq</tt> and committed under a published anchor without leaving a gap or a broken leaf hash. It does <strong>NOT</strong> prove that a producer never withheld an event from commitment in the first place: an event that was never assigned a <tt>tenantSeq</tt> leaves no hole to detect. Completeness claims MUST be scoped to the committed, anchored history, not to all real-world events.</t>
        </li>
        <li>
          <t>
            <strong>Global Sequence Monotonicity</strong>
          </t>
          <t>The numeric global sequence <tt>seq</tt> MUST satisfy <tt>seq(N+1) &gt; seq(N)</tt>. <tt>seq</tt> is an arithmetic ordering counter and MUST be numeric; it MUST NOT be a UUID. Time-ordered event <strong>identifiers</strong> (<tt>id</tt>), when used, are a separate field (RECOMMENDED RFC 9562 UUIDv7) and MUST NOT be used for <tt>+1</tt> continuity arithmetic.</t>
        </li>
        <li>
          <t>
            <strong>Offline Inclusion Verification</strong>
          </t>
          <t>Verification of an Inclusion Proof MUST run locally in the auditor's process by checking that <tt>blockProof</tt> and <tt>checkpointProof</tt>, each bounded per §11.1, recompute to the trusted Checkpoint Root. Verification MUST NOT require network calls or proprietary API keys.</t>
          <t>The Daily Root that carries a <strong>trust</strong> verdict MUST be one the auditor obtained independently of the artifact under verification — a root it recorded earlier, or one from the published roots file (§5.4.1) whose chain it has checked. An external anchor cannot supply it: a Rekor entry stores a hash of the §5.2 anchor digest and an RFC 3161 <xref target="RFC3161" /> token the digest itself, and neither reveals the root; what the anchors provide is a check on a root the auditor already holds (§5.3). A root carried inside the artifact — <tt>proof.checkpointRoot</tt>, or the <tt>dailyRoot</tt> inside a bundle-carried anchor — MUST NOT be treated as that trusted value, MUST NOT be reported as anchored, and MUST NOT support a non-repudiation claim: a bundle that supplies its own root vouches for itself.</t>
          <t>This forbids <em>trusting</em> a self-supplied root, not <em>computing against</em> one. Recomputing the two hops against a bundle-carried root, and reporting the outcome as an internal-consistency property explicitly labelled self-asserted, is permitted — that property is §7.1's <tt>commitmentVerified</tt>, and §7.1 is where the two readings reconcile: <tt>commitmentVerified</tt> says an entry recomputes to the root in hand, and only <tt>anchorVerified</tt> says that root is anyone else's.</t>
          <t>A verifier cannot tell where a root handed to it came from. It MUST NOT report a caller-supplied root as "independent": a root the caller copied out of the very bundle under verification is indistinguishable, to the verifier, from one recorded a year earlier. The reference implementations report <tt>rootSource: "caller-supplied"</tt>, and the independence of that root is the caller's claim to make, not the verifier's.</t>
        </li>
        <li>
          <t>
            <strong>Independent Anchor Verification (Quorum)</strong>
          </t>
          <t>
            <tt>anchorVerified</tt> (and therefore <tt>FULLY_VERIFIED</tt>) is satisfied if and only if the Checkpoint Root is verified against an <strong>anchor quorum</strong> as defined by the verifier's anchor policy (§5.3): at least <tt>requiredAnchors</tt> distinct anchors, each signed by an issuer in <tt>trustedIssuers</tt>, MUST present a valid signature over the <strong>same</strong>
            <tt>dailyRoot</tt> — binding the checkpoint's position, and for an external witness authenticated within the §5.3 time bound of the checkpoint's claimed time. A single anchor provider is a single point of compromise; <tt>requiredAnchors</tt> SHOULD be ≥ 2 for high-assurance non-repudiation. If two trusted anchors that the verifier obtained itself for the same checkpoint (out of band or through discovery) sign <strong>different</strong> roots, the verifier MUST treat the result as <tt>INVALID</tt> (anchor divergence) rather than pick one. A bundle-carried anchor set MUST NOT be treated as able to establish divergence (§6.3): the producer chooses what a bundle carries, so the bundle is no evidence that nothing conflicting exists. Self-asserted roots within unanchored bundles MUST NOT be treated as authoritative proof of non-repudiation.</t>
        </li>
      </ol>
    </section>
    <section anchor="s-4" numbered="false">
      <name>4. Canonical Preimage and Cryptographic Definitions</name>
      <section anchor="s-4-1" numbered="false">
        <name>4.1 Canonical Array Preimage Representation (DEWP Core)</name>
        <t>The DEWP Core Canonical Preimage MUST be represented as an ordered JSON array serialized using RFC 8785 JSON Canonicalization Scheme (JCS). It is defined by a single canonical rule:</t>
        <ul>
          <li>
            <t>
              <strong>Fixed core fields:</strong> indices <tt>0..10</tt> (<tt>seq</tt> .. <tt>sigAlg</tt>) MUST appear in the order defined in §4.2 and MUST NOT be reordered.</t>
          </li>
          <li>
            <t>
              <strong>
                <tt>tenantSeq</tt> is always the final element</strong> of the array (<tt>array[array.length - 1]</tt>).</t>
          </li>
          <li>
            <t>
              <strong>Bare Core:</strong> with no Application Profile, the array has exactly <strong>12</strong> elements — fixed fields <tt>0..10</tt> followed by <tt>tenantSeq</tt> at index <tt>11</tt>.</t>
          </li>
          <li>
            <t>
              <strong>Profiles (§4.5):</strong> an Application Profile MAY insert profile fields at indices <tt>11..N-1</tt>; <tt>tenantSeq</tt> is then relocated to the final index <tt>N</tt>. It is never at a fixed numeric index other than "last".</t>
          </li>
        </ul>
        <t>Vendor- or application-specific attributes MUST NOT be inserted among the fixed core fields <tt>0..10</tt>; they MUST be carried in <tt>metadata</tt> (index 5) or appended as profile fields per §4.5.</t>
        <sourcecode type="json">NOTE: '\' line wrapping per RFC 8792

[
  "1048576",
  "2026-07-24T12:00:00.000Z",
  "ACTION_APPROVED",
  "SUCCESS",
  "Database drop approved",
  "{\"target\":\"users\"}",
  "did:example:human:alice",
  "base64-spki-public-key",
  "{\"actionType\":\"db:dropTable\",\"type\":\"div-intent-verificati\
  on\",\"v\":1}",
  "base64-signature",
  "ES256",
  "42"
]</sourcecode>
      </section>
      <section anchor="s-4-2" numbered="false">
        <name>4.2 Array Element Definitions (DEWP Core)</name>
        <table>
          <thead>
            <tr>
              <th>Index</th>
              <th>Field</th>
              <th>Type</th>
              <th>Requirement</th>
              <th>Description</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>0</td>
              <td>
                <tt>seq</tt>
              </td>
              <td>string</td>
              <td>REQUIRED</td>
              <td>Global numeric ordering counter (stringified 64-bit integer). MUST NOT be a UUID. The opaque event identifier (<tt>id</tt>, RECOMMENDED UUIDv7) is a separate bundle-level field, not a Core preimage element in v1.</td>
            </tr>
            <tr>
              <td>1</td>
              <td>
                <tt>createdAt</tt>
              </td>
              <td>string</td>
              <td>REQUIRED</td>
              <td>ISO 8601 UTC timestamp (<tt>YYYY-MM-DDTHH:mm:ss.sssZ</tt>, zero offset).</td>
            </tr>
            <tr>
              <td>2</td>
              <td>
                <tt>event</tt>
              </td>
              <td>string</td>
              <td>REQUIRED</td>
              <td>Event <strong>type</strong> / name (e.g. <tt>ACTION_APPROVED</tt>), not a per-event unique id.</td>
            </tr>
            <tr>
              <td>3</td>
              <td>
                <tt>outcome</tt>
              </td>
              <td>string</td>
              <td>REQUIRED</td>
              <td>Outcome status. The registered values are <tt>SUCCESS</tt>, <tt>FAILURE</tt> and <tt>PENDING</tt>. <tt>PENDING</tt> is load-bearing rather than transitional: an event that RECORDS A REQUEST — a challenge raised, a signature received, a document submitted for signing — is committed to the ledger when it happens, and its outcome is not yet known. Suppressing those events until they resolve would leave the request itself unwitnessed, which is the opposite of what the log is for. A verifier MUST NOT treat <tt>PENDING</tt> as a failure, and MUST NOT assume a later event resolving it appears in the same bundle. Earlier drafts of this table listed <tt>DENIED</tt>; no producer emits it (a refusal is carried by the event type, e.g. <tt>AUTHZ_DENIED</tt>, with its own outcome), and it is not a registered value.</td>
            </tr>
            <tr>
              <td>4</td>
              <td>
                <tt>detail</tt>
              </td>
              <td>string | null</td>
              <td>REQUIRED</td>
              <td>Human-readable detail string (max 4,096 bytes; informational unless signed).</td>
            </tr>
            <tr>
              <td>5</td>
              <td>
                <tt>metadata</tt>
              </td>
              <td>string</td>
              <td>REQUIRED</td>
              <td>Compact <strong>JCS</strong> string of metadata (max 65,536 bytes; <tt>jcs(metadata ?? null)</tt> — i.e. RFC 8785 with keys sorted recursively by <strong>UTF-16 code units</strong> (§3.2.3 — a surrogate-pair key sorts before U+FFFD; pinned by the <tt>metadata-utf16-key-order</tt> golden vector), <strong>not</strong>
                <tt>JSON.stringify</tt>, which preserves insertion order and would make the leaf depend on how the producer happened to build the object).</td>
            </tr>
            <tr>
              <td>6</td>
              <td>
                <tt>signerDid</tt>
              </td>
              <td>string | null</td>
              <td>REQUIRED</td>
              <td>DID of the signing party (if applicable).</td>
            </tr>
            <tr>
              <td>7</td>
              <td>
                <tt>signerPublicKey</tt>
              </td>
              <td>string | null</td>
              <td>REQUIRED</td>
              <td>Base64-encoded public key (SPKI or COSE).</td>
            </tr>
            <tr>
              <td>8</td>
              <td>
                <tt>signedPayload</tt>
              </td>
              <td>string | null</td>
              <td>REQUIRED</td>
              <td>Exact canonical payload string signed (embedded intent-proof material, §4.6).</td>
            </tr>
            <tr>
              <td>9</td>
              <td>
                <tt>signature</tt>
              </td>
              <td>string | null</td>
              <td>REQUIRED</td>
              <td>Base64-encoded signature (embedded intent-proof material, §4.6).</td>
            </tr>
            <tr>
              <td>10</td>
              <td>
                <tt>sigAlg</tt>
              </td>
              <td>string | null</td>
              <td>REQUIRED</td>
              <td>Signature algorithm identifier (<tt>ES256</tt>, <tt>WEBAUTHN</tt>, <tt>AUTO_APPROVED</tt>).</td>
            </tr>
            <tr>
              <td>11</td>
              <td>
                <tt>tenantSeq</tt>
              </td>
              <td>string | null</td>
              <td>REQUIRED</td>
              <td>Per-tenant monotonic sequence counter (stringified 64-bit integer).</td>
            </tr>
          </tbody>
        </table>
      </section>
      <section anchor="s-4-3" numbered="false">
        <name>4.3 Canonical Null, Timestamp, and Size Bound Rules</name>
        <ul>
          <li>
            <t>
              <strong>Canonical Null Rules:</strong> A bare DEWP Core array MUST contain exactly 12 elements (a profile array contains <tt>12 + (number of profile fields)</tt> elements, with <tt>tenantSeq</tt> always last). Missing fields MUST NOT be omitted; they MUST be represented as explicit <tt>null</tt>. An empty string <tt>""</tt> MUST NOT be treated as <tt>null</tt>.</t>
          </li>
          <li>
            <t>
              <strong>Timestamp Rules:</strong> Timestamps MUST be formatted as ISO 8601 UTC strings (<tt>YYYY-MM-DDTHH:mm:ss.sssZ</tt>) with millisecond precision and a literal <tt>'Z'</tt> zero timezone offset. Implementations SHOULD attach RFC 3161 Timestamp Authority (TSA) tokens for high-assurance timestamp non-repudiation.</t>
          </li>
          <li>
            <t>
              <strong>Size Bounds:</strong> The <tt>detail</tt> field SHOULD NOT exceed 4,096 bytes. The <tt>metadata</tt> field SHOULD NOT exceed 65,536 bytes (64 KB). The total Canonical Preimage JCS byte string SHOULD NOT exceed 131,072 bytes (128 KB). <em>(These were stated as MUST in an earlier revision. No producer or verifier in the reference implementation enforces them, and a verifier that started rejecting oversized leaves would fail to verify history it had already anchored. They are downgraded to SHOULD to match reality; a future revision may enforce them at the producer only.)</em>
            </t>
          </li>
          <li>
            <t>
              <strong>ID Monotonicity:</strong> Event identifiers SHOULD be RFC 9562 UUIDv7 strings to guarantee time-ordered sortability across distributed systems.</t>
          </li>
        </ul>
        <section anchor="s-4-3-1" numbered="false">
          <name>4.3.1 Portable Number Range</name>
          <t>RFC 8785 defines a serialization for every finite double, but independent implementations disagree in practice: each language's number formatter switches to exponent notation at its own threshold, and <tt>-0</tt> has no single spelling. Because the <tt>metadata</tt> element (§4.2 index 5) is hashed as text, two verifiers that format one number differently compute different leaf hashes for the same row — which presents as tampering, the most damaging way for evidence to fail.</t>
          <t>A number appearing anywhere in <tt>metadata</tt> is <strong>portable</strong> when it is finite, is not <tt>-0</tt>, and satisfies one of:</t>
          <ul>
            <li>
              <t>it is an integer with <tt>|x| &lt; 1e16</tt>; or</t>
            </li>
            <li>
              <t>it is <tt>0</tt>; or</t>
            </li>
            <li>
              <t>it is a non-integer with <tt>1e-4 &lt;= |x| &lt; 1e16</tt>.</t>
            </li>
          </ul>
          <t>
            <strong>Producers MUST NOT commit a leaf whose <tt>metadata</tt> contains a non-portable number.</strong> A value outside the range MUST be carried as a string, as an integer in smaller units, or not at all. This is a producer obligation because it is the only place the problem can be solved once: after commitment the leaf hash is fixed and every verifier must live with it.</t>
          <t>
            <strong>Verifiers MAY</strong> either refuse to canonicalize a non-portable number (reporting the leaf as unverifiable) <strong>or</strong> serialize it on a best-effort basis and report an ordinary content mismatch. Both are conformant; a non-conformant producer is what puts a verifier in that position. The best-effort path has a limit worth stating plainly: a verifier whose number formatter happens to match the producer's reproduces the committed bytes exactly and reports the leaf as content-verified. That verdict attests inclusion of the committed bytes — it does not attest that the content is portable, and the same leaf will fail content verification in every port whose formatter differs. A leaf that verifies only under one language's number formatting is precisely the interoperability failure the producer MUST above exists to prevent; the producer rule, not the verifier, is the load-bearing control here. <em>(Normative change in this revision: an earlier draft added "a verifier MUST NOT report such a leaf as verified" — a requirement the best-effort path cannot satisfy when the verifier's formatter matches the producer's, and which every best-effort reference implementation therefore violated. That sentence is withdrawn; the two verifier options above are the complete conformance surface.)</em>
          </t>
          <t>
            <strong>Shortest round-trip formatting applies here too.</strong> Inside the portable range a number in <tt>metadata</tt> MUST serialize as the shortest decimal string that round-trips to the same double, per <strong>DIV §4.1.1.1</strong> — which also records why a language's built-in formatter may not be conformant (notably Java's <tt>Double.toString</tt> before JDK 19). Because <tt>metadata</tt> is hashed as text, an implementation that emits extra digits computes a different leaf hash for the same row and reports it as tampering. DIV §4.1.1's caveat on integers above <tt>2^53</tt> applies to <tt>metadata</tt> identically: an arbitrary-precision runtime preserves such an integer's exact digits where a double-based one rounds them, so the same row hashes differently across ports — keep integers within <tt>±2^53</tt> or carry them as strings.</t>
          <t>
            <em>(Reference implementations: the producer refuses at ingestion; <tt>verify-go</tt> refuses to canonicalize; <tt>verify</tt>, <tt>verify-rust</tt> and <tt>sdk-python</tt> best-effort match the TypeScript reference. <tt>verify-java</tt> refuses, with one exception it cannot see: its JSON parser normalizes an integer-form <tt>-0</tt> to <tt>0</tt> before the check runs, so that single value is best-effort — like <tt>verify-rust</tt>. The DIV signing path applies the identical range at signing time — see DIV §4.1.1.)</em>
          </t>
        </section>
      </section>
      <section anchor="s-4-4" numbered="false">
        <name>4.4 Cryptographic Hash Definitions</name>
        <t>All cryptographic digests in DEWP MUST use <strong>SHA-256</strong> as defined by FIPS 180-4 <xref target="FIPS180-4" />.</t>
        <t>Digest values MUST be encoded as 64-character lowercase ASCII hexadecimal strings (e.g. <tt>d4e5f6a1b2c3...</tt>).</t>
        <t>The normative hash functions are:</t>
        <ul>
          <li>
            <t>
              <tt>sha256Hex(dataBytes)</tt>: Computes <tt>SHA-256(dataBytes)</tt> and returns a 64-character lowercase hex string.</t>
          </li>
          <li>
            <t>
              <tt>hashLeaf(preimageStr)</tt>: Computes <tt>sha256Hex(byte(0x00) || UTF8Bytes(preimageStr))</tt>.</t>
          </li>
          <li>
            <t>
              <tt>hashPair(leftHex, rightHex)</tt>: Computes <tt>sha256Hex(byte(0x01) || hexDecode(leftHex) || hexDecode(rightHex))</tt>. The two child digests are <strong>hex-decoded to their raw 32 bytes</strong> before hashing (they are NOT concatenated as hex strings). Order encodes position (<tt>left</tt> then <tt>right</tt>) and MUST NOT be sorted.</t>
          </li>
        </ul>
      </section>
      <section anchor="s-4-5" numbered="false">
        <name>4.5 Application Profiles &amp; Extension Arrays</name>
        <t>Applications requiring additional domain-specific or platform-specific attributes MUST carry them inside element 5 (<tt>metadata</tt>) or define an explicit <strong>Application Profile</strong>.</t>
        <section numbered="false">
          <name>Profile Extension Array Rules:</name>
          <ol type="1">
            <li>
              <t>Core fields <tt>0..10</tt> (<tt>seq</tt> .. <tt>sigAlg</tt>) MUST remain fixed in position and semantic.</t>
            </li>
            <li>
              <t>Extension profile fields MUST be inserted at indices <tt>11..N-1</tt> — <strong>after</strong> core field 10 and <strong>before</strong> the final <tt>tenantSeq</tt>. They MUST NOT displace <tt>tenantSeq</tt> from the last position.</t>
            </li>
            <li>
              <t>
                <tt>tenantSeq</tt> MUST ALWAYS occupy the <strong>final element</strong> of the canonical array (<tt>array[array.length - 1]</tt>). This is the single, unambiguous rule for <tt>tenantSeq</tt>'s location in both Core and profiles.</t>
            </li>
            <li>
              <t>Profile naming MUST follow reverse-DNS format: <tt>vendor.profileName.vVersion</tt> (e.g. <tt>org.example.audit.v1</tt>).</t>
            </li>
          </ol>
        </section>
        <section numbered="false">
          <name>Informative Example: Extended Application Profile (profile: "org.example.audit.v1")</name>
          <table>
            <thead>
              <tr>
                <th>Index</th>
                <th>Field</th>
                <th>Scope</th>
                <th>Description</th>
              </tr>
            </thead>
            <tbody>
              <tr>
                <td>0..10</td>
                <td>
                  <tt>seq</tt> .. <tt>sigAlg</tt>
                </td>
                <td>DEWP Core</td>
                <td>Universal event attributes</td>
              </tr>
              <tr>
                <td>11</td>
                <td>
                  <tt>isBillable</tt>
                </td>
                <td>
                  <tt>example.v1</tt> Profile</td>
                <td>Application billing classification flag</td>
              </tr>
              <tr>
                <td>12</td>
                <td>
                  <tt>tenantId</tt>
                </td>
                <td>
                  <tt>example.v1</tt> Profile</td>
                <td>Organization / tenant ID</td>
              </tr>
              <tr>
                <td>13</td>
                <td>
                  <tt>actorNodeId</tt>
                </td>
                <td>
                  <tt>example.v1</tt> Profile</td>
                <td>Actor node identifier</td>
              </tr>
              <tr>
                <td>14</td>
                <td>
                  <tt>subjectNodeId</tt>
                </td>
                <td>
                  <tt>example.v1</tt> Profile</td>
                <td>Subject node identifier</td>
              </tr>
              <tr>
                <td>15</td>
                <td>
                  <tt>edgeId</tt>
                </td>
                <td>
                  <tt>example.v1</tt> Profile</td>
                <td>Relationship edge identifier</td>
              </tr>
              <tr>
                <td>16</td>
                <td>
                  <tt>challengeId</tt>
                </td>
                <td>
                  <tt>example.v1</tt> Profile</td>
                <td>Approval challenge identifier</td>
              </tr>
              <tr>
                <td>17</td>
                <td>
                  <tt>tenantSeq</tt>
                </td>
                <td>DEWP Core (Final Element)</td>
                <td>Per-tenant monotonic sequence counter</td>
              </tr>
            </tbody>
          </table>
        </section>
        <section numbered="false">
          <name>Registered Profiles</name>
          <table>
            <thead>
              <tr>
                <th>Profile</th>
                <th>Layout</th>
              </tr>
            </thead>
            <tbody>
              <tr>
                <td>
                  <em>(none)</em>
                </td>
                <td>Bare DEWP Core — 12 elements, <tt>tenantSeq</tt> at index 11.</td>
              </tr>
              <tr>
                <td>
                  <tt>trust.intyga.audit.v1</tt>
                </td>
                <td>Core <tt>0..10</tt>, then <tt>isBillable</tt>, <tt>tenantId</tt>, <tt>actorNodeId</tt>, <tt>subjectNodeId</tt>, <tt>edgeId</tt>, <tt>challengeId</tt>, then <tt>tenantSeq</tt> at index 17 — the layout tabulated above.</td>
              </tr>
            </tbody>
          </table>
          <t>A bundle SHOULD declare its profile in a top-level <tt>profile</tt> field. A verifier encountering a profile it does not implement MUST NOT attempt content verification against it: recomputing the leaf under a different field order yields a mismatch indistinguishable from tampering. It MUST instead report <tt>contentVerified: false</tt> and continue to evaluate <tt>commitmentVerified</tt>, which is profile-independent. Reporting <tt>contentVerified: false</tt> does not make the artifact pass: the profile is a producer-supplied field, so a verifier MUST NOT return an overall pass for a proof (§6.2) or an Evidence Bundle entry (§6.3) that ships a preimage it could not bind. An entry with no preimage (a <tt>COMMITMENT_ONLY</tt> redaction) has nothing to bind and still verifies as commitment-only.</t>
        </section>
      </section>
      <section anchor="s-4-6" numbered="false">
        <name>4.6 Event Signature Scope</name>
        <t>The event-level signature material carried in the preimage (<tt>signerDid</tt>, <tt>signerPublicKey</tt>, <tt>signedPayload</tt>, <tt>signature</tt>, <tt>sigAlg</tt> — indices 6..10) is <strong>embedded intent-proof material</strong>, reproduced by reference from a pre-execution authorization protocol — in this document's examples, DIV (<xref target="DIV" />) — <strong>or an Execution Evidence Statement</strong> signed by an evidence source (§4.7). The scope rules below apply to both; §4.7.5 says which trust a verifier applies to which. Its scope MUST be understood precisely to avoid over-claiming:</t>
        <ul>
          <li>
            <t>
              <strong>
                <tt>signedPayload</tt> is the exact, verbatim canonical byte string the signer's key signed</strong> — for a DIV approval, the DIV Intent Payload (<tt>{"actionType":...,"type":"div-intent-verification","v":1}</tt>). It is reproduced byte-for-byte; DEWP does not re-serialize it.</t>
          </li>
          <li>
            <t>
              <strong>
                <tt>signature</tt> covers ONLY the bytes of <tt>signedPayload</tt>.</strong> It does NOT cover any DEWP field — not <tt>seq</tt>, not <tt>tenantSeq</tt>, not <tt>createdAt</tt>, not the surrounding preimage array. A verifier MUST NOT infer that the signer attested to the DEWP sequence numbers, timestamps, or Merkle position.</t>
          </li>
          <li>
            <t>
              <strong>What binds the DEWP fields is the Merkle commitment, not the event signature.</strong> Integrity of <tt>seq</tt>/<tt>tenantSeq</tt>/<tt>createdAt</tt>/etc. derives from <tt>leafHash(CanonicalPreimage)</tt> being committed under an anchored root (§3 Invariants 2 and 3), a property independent of whether any event signature is present.</t>
          </li>
          <li>
            <t>Consequently, a <tt>SIGNATURE_VERIFIED</tt> result asserts <em>"a named key signed exactly <tt>signedPayload</tt>"</em> and nothing about the DEWP envelope; a <tt>CONTENT_VERIFIED</tt> result asserts the envelope was committed. Both are required to conclude <em>"this signed action was recorded at this position."</em>
            </t>
          </li>
        </ul>
        <section anchor="s-4-6-1" numbered="false">
          <name>4.6.1 Verifiability of each sigAlg from the leaf alone</name>
          <t>The Core preimage carries only <tt>signerPublicKey</tt>, <tt>signedPayload</tt>, <tt>signature</tt> and <tt>sigAlg</tt>. That is sufficient for a raw signature but not for every registered algorithm:</t>
          <table>
            <thead>
              <tr>
                <th>
                  <tt>sigAlg</tt>
                </th>
                <th>Verifiable from the Core preimage?</th>
              </tr>
            </thead>
            <tbody>
              <tr>
                <td>
                  <tt>ES256</tt>
                </td>
                <td>
                  <strong>Yes.</strong>
                  <tt>signature</tt> over <tt>signedPayload</tt> under <tt>signerPublicKey</tt> (SPKI, P-256).</td>
              </tr>
              <tr>
                <td>
                  <tt>WEBAUTHN</tt>
                </td>
                <td>
                  <strong>No.</strong> A WebAuthn <xref target="WebAuthn" /> assertion signs <tt>authenticatorData ‖ SHA-256(clientDataJSON)</tt>, and neither component has a Core slot. A verifier MUST report <tt>signatureVerified: false</tt> for such an event rather than guess, so a passkey-approved event tops out at <tt>CONTENT_VERIFIED</tt> unless a profile carries the two components.</td>
              </tr>
              <tr>
                <td>
                  <tt>AUTO_APPROVED</tt>
                </td>
                <td>
                  <strong>No signature exists.</strong> A policy pre-approval carries no human attestation; <tt>signatureVerified</tt> is false and MUST NOT be read as a failure.</td>
              </tr>
            </tbody>
          </table>
          <t>An implementation needing offline signature verification of passkey approvals MUST define an Application Profile carrying <tt>authenticatorData</tt> and <tt>clientDataJSON</tt>; verifying such an assertion also requires the expected <tt>origin</tt> and RP ID, which are deployment configuration and not evidence, so they MUST be supplied by the verifier rather than read from the bundle.</t>
          <t>The <tt>trust.intyga.audit.v1</tt> Application Profile uses the existing committed <tt>metadata</tt> element: <tt>metadata.webauthn.authenticatorData</tt> and <tt>metadata.webauthn.clientDataJSON</tt> carry the browser's base64/base64url assertion components. These fields are already covered by the canonical metadata serialization; no preimage field ordering or hashing changes. Their absence in older exports means WebAuthn is not checkable. Verification MUST bind the assertion challenge to the exact <tt>signedPayload</tt>, pin the expected origin and RP ID from caller configuration, require UP and UV, and refuse cross-origin ceremonies. The reference ports accept caller-provisioned COSE P-256 keys mapped to signer DID. The embedded key alone MUST NOT establish trusted signer identity.</t>
          <t>The reference APIs report signature status (<tt>verified</tt>, <tt>invalid</tt>, <tt>not_checked</tt>, <tt>not_applicable</tt>), a reason and a separate <tt>trusted</tt> flag. An absent algorithm with remaining signature material is <tt>not_checked</tt>, not an unsigned event. Diagnostic mode preserves independent commitment/content verdicts even for an invalid signature. Caller-selected <tt>requireSignatures</tt> additionally requires a verified, caller-trusted signature on every selected entry; absent trust, unknown algorithms, redacted/unsigned entries and invalid signatures fail that gate. This does not verify the enclosing DIV approval policy, quorum, expiry, expected operation, hardware attestation or execution.</t>
        </section>
      </section>
      <section anchor="s-4-7" numbered="false">
        <name>4.7 Execution Evidence</name>
        <t>A pre-execution approval (DIV) establishes that a human approved exact bytes. A committed DEWP event establishes that a record was not altered after commitment. Neither says whether the approved action then took effect. This section defines how a producer records that question and its answer, and how a verifier evaluates them, without changing the Canonical Preimage, the leaf hash, any Application Profile layout, or the bundle <tt>version</tt> (<tt>"1.0"</tt>). Every record defined here is an ordinary leaf, so a verifier that does not implement this section still verifies its commitment and content.</t>
        <t>The answer is only as good as the party that observed it. §4.7 records <strong>what a caller-trusted evidence source reported</strong>, signed by that source. It does not prove what physically happened, and a verifier MUST NOT present an execution status as more than that (§4.7.6).</t>
        <section anchor="s-4-7-1" numbered="false">
          <name>4.7.1 Records</name>
          <t>Four event types are defined. Each is identified by the <tt>event</tt> element (§4.2 index 2); a producer writes the outcome and signature slots shown. Verifiers identify a Dispatch Record by its <tt>event</tt>, <tt>metadata</tt> and <tt>createdAt</tt> alone, and an Evidence Record as §4.7.3 says; the outcome of a Dispatch or Unconfirmed Record is a producer convention that §4.7.3–§4.7.4 do not read. The approval a record refers to is identified by the DIV <tt>nonce</tt> (DIV §4.3), which is unique per approval. In this revision an <strong>Approval Record</strong> is a leaf carrying an ordinary DIV Intent Payload (<tt>type</tt>
            <tt>div-intent-verification</tt>, located as §4.7.3 step 5 says). Offline approvals (DIV §5a), delegations, agent-authority seals and platform intents (DIV §5c) are not Approval Records: an action approved through them cannot be dispatched or confirmed under §4.7, and a statement naming one reads <tt>unbound</tt>.</t>
          <table>
            <thead>
              <tr>
                <th>
                  <tt>event</tt>
                </th>
                <th>
                  <tt>outcome</tt>
                </th>
                <th>Signature slots (indices 6..10)</th>
                <th>
                  <tt>metadata</tt> (index 5)</th>
              </tr>
            </thead>
            <tbody>
              <tr>
                <td>
                  <tt>EXECUTION_DISPATCHED</tt>
                </td>
                <td>
                  <tt>PENDING</tt>
                </td>
                <td>all <tt>null</tt>
                </td>
                <td>object containing <tt>nonce</tt>, <tt>payloadSha256</tt>, <tt>evidenceDeadline</tt>
                </td>
              </tr>
              <tr>
                <td>
                  <tt>EXECUTION_CONFIRMED</tt>
                </td>
                <td>
                  <tt>SUCCESS</tt>
                </td>
                <td>Execution Evidence Statement, <tt>ES256</tt>
                </td>
                <td>any</td>
              </tr>
              <tr>
                <td>
                  <tt>EXECUTION_CONTRADICTED</tt>
                </td>
                <td>
                  <tt>FAILURE</tt>
                </td>
                <td>Execution Evidence Statement, <tt>ES256</tt>
                </td>
                <td>any</td>
              </tr>
              <tr>
                <td>
                  <tt>EXECUTION_UNCONFIRMED</tt>
                </td>
                <td>
                  <tt>FAILURE</tt>
                </td>
                <td>all <tt>null</tt>
                </td>
                <td>any</td>
              </tr>
            </tbody>
          </table>
          <ul>
            <li>
              <t>A <strong>Dispatch Record</strong> states that the executing party sent the approved action to its target and expects evidence by <tt>evidenceDeadline</tt>. Its <tt>metadata</tt> MUST be an object whose <tt>nonce</tt> is the approval's DIV nonce, whose <tt>payloadSha256</tt> is the lowercase hex SHA-256 of the UTF-8 bytes of the approved DIV Intent Payload, and whose <tt>evidenceDeadline</tt> follows the §4.3 timestamp rule. Other members are permitted and are ignored by §4.7.3–§4.7.4; a leaf of this event type whose <tt>metadata</tt> does not satisfy these rules, or whose <tt>createdAt</tt> does not, is not a Dispatch Record. A Dispatch Record is written by or on behalf of the party that executes, which is the party least able to attest that execution succeeded; it is therefore never evidence of success. Several Dispatch Records MAY name one nonce (a retry, a re-dispatch). Because the executing party writes them, a verifier never lets one of them decide which statements bind (§4.7.3 step 5), bounds the window they open (§4.7.3 step 6), and reports records for one nonce that disagree on <tt>payloadSha256</tt> as a dispatch conflict (§4.7.4). A later record never reopens or extends the window: the lowest-seq matching record governs it, so a re-dispatch after the first window closed is recorded but cannot be confirmed — otherwise re-dispatching would keep a nonce <tt>pending</tt> at will.</t>
            </li>
            <li>
              <t>An <strong>Evidence Record</strong> carries one Execution Evidence Statement (§4.7.2) in its signature slots: <tt>signerDid</tt> is the statement's <tt>source</tt>, <tt>signerPublicKey</tt> the base64 SPKI of the P-256 key that signed it, <tt>signedPayload</tt> the statement bytes, <tt>signature</tt> the base64 ECDSA P-256 / SHA-256 signature over those bytes (IEEE P1363 or DER, as for <tt>ES256</tt> in §4.6.1), and <tt>sigAlg</tt>
                <tt>ES256</tt>. Its <tt>event</tt> and <tt>outcome</tt> MUST match the signed <tt>verdict</tt> as tabulated. A producer relays a statement; it cannot author one, because it does not hold the source's key.</t>
            </li>
            <li>
              <t>An <strong>Unconfirmed Record</strong> is the producer's own, unsigned note that <tt>evidenceDeadline</tt> passed without qualifying evidence. It is an alarm, not evidence: verifiers derive <tt>unconfirmed</tt> themselves (§4.7.4) and MUST NOT require or rely on this record. A negative statement needs no trusted signer, so this is the only §4.7 record a producer writes on its own authority.</t>
            </li>
          </ul>
          <t>All four are appended, never updated. Evidence that arrives after an Unconfirmed Record is still committed; the history keeps both.</t>
        </section>
        <section anchor="s-4-7-2" numbered="false">
          <name>4.7.2 Execution Evidence Statement</name>
          <t>The statement is the object an <strong>Evidence Source</strong> signs: a party independent of the one that sent the command — for example a protection relay reporting breaker position, or a measured-boot attestation reporting the digest of the firmware that actually booted.</t>
          <sourcecode type="json">NOTE: '\' line wrapping per RFC 8792

{
  "approval": {
    "nonce": "4f6c1c9e-1b8a-4e2f-9d3a-7c2b5e8f0a1d",
    "payloadSha256": "5d1f…(64 lowercase hex)…"
  },
  "observation": { "breaker": "open", "currentA": 0, "previousCurren\
  tA": 412 },
  "observedAt": "2026-10-05T14:02:11.000Z",
  "sampledAt": "2026-10-05T14:02:09.500Z",
  "sequence": 418,
  "source": "did:example:relay-7",
  "type": "dewp-execution-evidence",
  "v": 1,
  "verdict": "confirmed"
}</sourcecode>
          <t>Normative rules:</t>
          <ol type="1">
            <li>
              <t>The statement MUST be a JSON object with exactly the nine members shown, and <tt>approval</tt> MUST have exactly the two members shown.</t>
            </li>
            <li>
              <t>
                <tt>type</tt> MUST be the string <tt>dewp-execution-evidence</tt> and <tt>v</tt> the integer <tt>1</tt>. The type is distinct from every DIV payload <tt>type</tt>, so a statement can never verify as an approval and an approval can never verify as a statement; DIV approval verifiers MUST refuse it.</t>
            </li>
            <li>
              <t>
                <tt>approval.nonce</tt> MUST be a non-empty string: the DIV nonce of the approval the evidence is about. <tt>approval.payloadSha256</tt> MUST be 64 lowercase hexadecimal characters: the SHA-256 of the UTF-8 bytes of that approval's DIV Intent Payload. The hash binds the evidence to the exact approved bytes, so a statement cannot be moved to another approval.</t>
            </li>
            <li>
              <t>
                <tt>source</tt> MUST be a non-empty string naming the evidence source, and MUST equal the Evidence Record's <tt>signerDid</tt>.</t>
            </li>
            <li>
              <t>
                <tt>verdict</tt> MUST be <tt>confirmed</tt> (the source observed the approved effect) or <tt>contradicted</tt> (the source observed that it did not take effect, or took a different effect). A source that cannot decide MUST NOT sign; the absence of qualifying evidence is what produces <tt>unconfirmed</tt>.</t>
            </li>
            <li>
              <t>
                <tt>observedAt</tt> MUST follow the §4.3 timestamp rule. It is the source's claim of when it produced the statement; when the measurement behind it was taken is <tt>sampledAt</tt> (rule 11).</t>
            </li>
            <li>
              <t>
                <tt>observation</tt> MUST be a JSON object and MAY be empty. Its content is domain-specific, covered by the signature, and not interpreted by §4.7.3–§4.7.4. Appendix A gives non-normative guidance on what it should establish.</t>
            </li>
            <li>
              <t>Every number anywhere in the statement MUST be portable (§4.3.1) and MUST have a magnitude of at most 2^53 − 1 (9007199254740991), no string in it may contain an unpaired surrogate (DIV §4.1), and it MUST NOT nest arrays and objects more than 64 levels deep (the statement object itself is level 1). The integer bound is tighter than §4.3.1's because some parsers read every number as a double: above 2^53 they would read a different integer from the same digits, and the same bytes would be a different statement in different verifiers.</t>
            </li>
            <li>
              <t>The statement bytes MUST be the RFC 8785 serialization of the statement's own parsed value. A verifier MUST parse the bytes as described in §4.7.7, re-serialize them under RFC 8785 and compare, and MUST treat a parse failure or a mismatch as malformed. This makes duplicate member names, insignificant whitespace and non-canonical number or string spellings malformed, so every conformant parser reads the same statement from the same bytes.</t>
            </li>
            <li>
              <t>The statement bytes MUST NOT exceed 16,384 bytes.</t>
            </li>
            <li>
              <t>
                <tt>sampledAt</tt> MUST follow the §4.3 timestamp rule and MUST NOT be later than <tt>observedAt</tt>. It is the source's claim of when the measurement behind <tt>observation</tt> was taken. The evidence window (§4.7.3 step 6) is evaluated on <tt>sampledAt</tt>, so a statement produced inside the window over a measurement taken outside it is <tt>stale</tt>; a source that reports a measurement it took earlier says so by signing the earlier time.</t>
            </li>
            <li>
              <t>
                <tt>sequence</tt> MUST be an integer from 1 to 2^53 − 1: the source's own count of statements it has signed for this producer's log, incremented by exactly one per statement and never reused. The log is the unit an Evidence Bundle is exported for — on a multi-tenant producer, one tenant's log. A source that signs for several logs keeps a counter per log; one counter shared across logs shows as gaps in each. The counter is what lets a verifier see a statement that is missing from the bundle (§4.7.3, Source continuity).</t>
            </li>
          </ol>
        </section>
        <section anchor="s-4-7-3" numbered="false">
          <name>4.7.3 Statement Check</name>
          <t>A verifier reads only content-verified entries (§7.1): a redacted entry, or a preimage that is not bound to its leaf, is not read as any §4.7 record, as §7.2 already requires of every other field.</t>
          <t>For each Evidence Record — a content-verified leaf whose <tt>event</tt> is <tt>EXECUTION_CONFIRMED</tt> or <tt>EXECUTION_CONTRADICTED</tt>, or whose <tt>signedPayload</tt> parses (§4.7.7) as a JSON object with <tt>type</tt> equal to <tt>dewp-execution-evidence</tt>; either suffices, so a record cannot avoid evidence-source trust by mislabelling one side — a verifier MUST report a check with a <tt>status</tt>, a <tt>reason</tt> and a <tt>trusted</tt> flag, applying these steps in order. The first step that decides the status ends the check, except that <tt>trusted</tt> set in step 4 is retained by steps 5 and 6.</t>
          <ol type="1">
            <li>
              <t>
                <tt>sigAlg</tt> is not <tt>ES256</tt>, or <tt>signature</tt>, <tt>signerPublicKey</tt> or <tt>signedPayload</tt> is missing: <tt>invalid</tt>.</t>
            </li>
            <li>
              <t>The statement violates any rule of §4.7.2 other than rule 4: <tt>invalid</tt>.</t>
            </li>
            <li>
              <t>
                <tt>source</tt> differs from <tt>signerDid</tt>, or <tt>event</tt>/<tt>outcome</tt> do not match <tt>verdict</tt> (§4.7.1): <tt>invalid</tt>.</t>
            </li>
            <li>
              <t>Signature:</t>
              <ul>
                <li>
                  <t>Without an evidence-source trust policy, the signature is checked under the embedded <tt>signerPublicKey</tt>: <tt>invalid</tt> if it does not verify, otherwise <tt>trusted</tt> is false and the check continues. As in §4.6.1, an embedded key MUST NOT establish who the source is.</t>
                </li>
                <li>
                  <t>With a policy, the caller-provisioned keys for <tt>source</tt> are tried in turn. None provisioned: <tt>not_checked</tt>. None verifies: <tt>invalid</tt>. One verifies: the check continues, and <tt>trusted</tt> is true unless that key carries a validity interval (§4.7.5) and the record's anchored time is unknown or lies outside it; then <tt>trusted</tt> is false and the reason says so. An unbounded key needs no anchored time.</t>
                </li>
                <li>
                  <t>Evidence-source trust is separate from approver trust. A verifier MUST NOT accept an approver's key as an evidence source's, or the reverse (§4.7.5).</t>
                </li>
              </ul>
            </li>
            <li>
              <t>Binding, evaluated over the content-verified entries of the same bundle:</t>
              <ul>
                <li>
                  <t>the <strong>matching Dispatch Records</strong> for <tt>approval.nonce</tt> are those whose <tt>metadata.payloadSha256</tt> equals the statement's; there is none: <tt>unbound</tt>. The <strong>governing Dispatch Record</strong> is the matching one with the lowest <tt>seq</tt>. A Dispatch Record carrying another hash never decides this step: the executing party writes Dispatch Records, and letting its earliest one govern would let it unbind every genuine statement by committing one wrong record first;</t>
                </li>
                <li>
                  <t>no <strong>Approval Record</strong> exists — a leaf whose <tt>signedPayload</tt> parses (§4.7.7) as a JSON object with <tt>type</tt>
                    <tt>div-intent-verification</tt> and <tt>nonce</tt> equal to <tt>approval.nonce</tt>, and whose <tt>signedPayload</tt> bytes hash to <tt>approval.payloadSha256</tt>: <tt>unbound</tt>.</t>
                </li>
              </ul>
            </li>
            <li>
              <t>Window. The <strong>effective deadline</strong> is the earlier of the governing Dispatch Record's <tt>evidenceDeadline</tt> and its <tt>createdAt</tt> plus the verifier's maximum evidence window: the window is bounded by the verifier, never by the dispatch alone, or an executing party could keep a nonce <tt>pending</tt> forever. <tt>sampledAt</tt> earlier than the governing record's <tt>createdAt</tt> minus the skew allowance: <tt>stale</tt> (reason <tt>early</tt>); later than the effective deadline: <tt>stale</tt> (reason <tt>late</tt>). The skew allowance covers clock error between the source and the producer and nothing else — it also admits a measurement taken just before dispatch, which for an action with no visible change confirms a no-op — so it is small: this profile's default is 30 seconds. The maximum evidence window defaults to 86 400 seconds. Both are caller-overridable per verification and MAY be given other defaults by an Application Profile; a slow process is accommodated by raising them for that verification, never by leaving them loose.</t>
            </li>
            <li>
              <t>Otherwise: <tt>verified</tt>.</t>
            </li>
          </ol>
          <t>
            <strong>Source continuity.</strong> Over the Evidence Records whose check passed step 4 (the statement parsed, its <tt>source</tt> matches, and its signature verified under the embedded or a provisioned key), a verifier MUST also report, per <tt>source</tt>: the smallest and largest <tt>sequence</tt> seen, every integer strictly between them that no record carries (a <strong>gap</strong>), and every value carried by two records whose statement bytes differ (a <strong>divergence</strong>). A gap means a statement the source signed is absent from the bundle, whether withheld from commitment or outside the exported range; a divergence means the source, or someone holding its key, signed two statements under one counter value. Neither changes a statement's own check or an Execution Status; both are findings (§4.7.4). Only the range inside the bundle can be checked: a source that stops signing, or whose later statements were all withheld, leaves no gap. A gap closes when the missing statement is committed later: refusal commits nothing, so a statement lost in transit, or refused for a cause the relaying party can fix, can be relayed again. A statement the producer can never accept — malformed, or signed under a key it will never register — leaves a permanent gap, and a caller applying the require-all policy (§4.7.4) must expect it. A producer that refuses a statement it could parse SHOULD record the refusal where its operators can see it, naming the <tt>source</tt> and the <tt>sequence</tt>, so a gap can be explained from the log.</t>
          <t>A single Inclusion Proof carries one leaf, so only steps 1–4 can be evaluated for it; a verifier MUST NOT report a single proof's statement as <tt>verified</tt>, and reports <tt>unbound</tt> with that reason.</t>
          <t>A check other than <tt>verified</tt> MUST be reported and MUST NOT by itself invalidate the entry's commitment, content or signature verification: the statement is committed evidence that does not support the claim, not evidence that the bundle was altered.</t>
        </section>
        <section anchor="s-4-7-4" numbered="false">
          <name>4.7.4 Execution Status</name>
          <t>For every DIV nonce named by a content-verified Approval Record, Dispatch Record or Evidence Record in an Evidence Bundle, a verifier implementing this section MUST report an <strong>Execution Status</strong>. Approval Records are enumerated so that an approval with no dispatch and no evidence is reported rather than passed over: silence is the case a caller requiring confirmation most needs to see.</t>
          <table>
            <thead>
              <tr>
                <th>Status</th>
                <th>Condition, evaluated in this order</th>
              </tr>
            </thead>
            <tbody>
              <tr>
                <td>
                  <tt>contradicted</tt>
                </td>
                <td>Some statement for the nonce is <tt>verified</tt>, <tt>trusted</tt>, with verdict <tt>contradicted</tt>.</td>
              </tr>
              <tr>
                <td>
                  <tt>confirmed</tt>
                </td>
                <td>Some statement for the nonce is <tt>verified</tt>, <tt>trusted</tt>, with verdict <tt>confirmed</tt>.</td>
              </tr>
              <tr>
                <td>
                  <tt>not_reported</tt>
                </td>
                <td>There is no Dispatch Record for the nonce.</td>
              </tr>
              <tr>
                <td>
                  <tt>unconfirmed</tt>
                </td>
                <td>The governing Dispatch Record's effective deadline (§4.7.3 step 6) is earlier than the evaluation time.</td>
              </tr>
              <tr>
                <td>
                  <tt>pending</tt>
                </td>
                <td>Otherwise.</td>
              </tr>
            </tbody>
          </table>
          <t>Where no statement for the nonce reached §4.7.3 step 6, the governing Dispatch Record is the one with the lowest <tt>seq</tt> among those naming the nonce. <tt>contradicted</tt> takes precedence over <tt>confirmed</tt>: one trusted source that saw the action fail outweighs another that saw it succeed, and the disagreement is itself the finding. Untrusted, <tt>unbound</tt> and <tt>stale</tt> statements never decide a status; late evidence leaves a nonce <tt>unconfirmed</tt>.</t>
          <t>Beside the status, a verifier MUST report these <strong>findings</strong> for the nonce. None changes a status:</t>
          <ul>
            <li>
              <t>
                <strong>Dispatch conflict</strong> — Dispatch Records for the nonce that disagree on <tt>payloadSha256</tt>. The executing party wrote two accounts of what it dispatched; which statements bind is decided by §4.7.3 step 5, and the disagreement itself is reported.</t>
            </li>
            <li>
              <t>
                <strong>Late contradiction</strong> — a statement for the nonce that is <tt>trusted</tt>, bound (§4.7.3 step 5 passed) and <tt>contradicted</tt>, but <tt>stale</tt> because <tt>sampledAt</tt> is later than the effective deadline. The window rule stands, so the status is not <tt>contradicted</tt>; but a trusted source saying the action did not take effect is the case that most needs escalation, and it MUST be reported as its own alert rather than folded into <tt>unconfirmed</tt>.</t>
            </li>
            <li>
              <t>
                <strong>Window capped</strong> — the governing Dispatch Record's own <tt>evidenceDeadline</tt> lay beyond the verifier's maximum evidence window, so the effective deadline is earlier than the record claims.</t>
            </li>
            <li>
              <t>
                <strong>Untrusted contradiction</strong> — a <tt>contradicted</tt> statement for the nonce that is bound and sampled at or after the window's start (including its clock-skew allowance), whether inside the window or late, and whose signature verified under a caller-provisioned key, but which did not establish trust because that key's validity could not be placed in time or the record lay outside it (§4.7.5). It cannot decide the status, but a registered source's key signed a contradiction, and silence about it would let an unbounded source's <tt>confirmed</tt> stand unchallenged. A late statement still produces this finding when it meets those conditions; lateness and missing key trust MUST NOT cancel each other out. Early or unbound statements, and statements verified only under an embedded key rather than a caller-provisioned key, do not produce this finding.</t>
            </li>
            <li>
              <t>
                <strong>Source continuity</strong> — the per-source gaps and divergences of §4.7.3, reported per source.</t>
            </li>
          </ul>
          <t>
            <tt>unconfirmed</tt> and <tt>contradicted</tt> are both "not confirmed", but they call for different responses and an implementation should keep them distinguishable to its operators. <tt>unconfirmed</tt> means the outcome cannot be told — often benign, such as a lost or stale sensor — and calls for investigation. <tt>contradicted</tt> means a trusted source observed that the approved effect did not take place, and calls for escalation. Reporting both the same way would blunt the alarm in the case that matters.</t>
          <t>The evaluation time is the caller's to choose and defaults to the current time. It MUST be an input rather than a hidden clock so a status can be reproduced; a status computed at one time can change from <tt>pending</tt> to <tt>unconfirmed</tt> later, and a later export can change any status by adding evidence. An Unconfirmed Record does not change the status (§4.7.1).</t>
          <t>Execution Status is reported alongside the four §7.1 properties and does not affect them or the derived Verification Level. A caller MAY require every reported nonce to be <tt>confirmed</tt> as an acceptance policy. Under it a bundle that reports no nonce MUST fail rather than pass vacuously, and a dispatch conflict, a gap, a divergence, a late contradiction or an untrusted contradiction MUST fail it as well: the first three mean the bundle does not carry the whole account, and the last two mean a registered source signed a contradiction that the status could not count — an executing party that chose a short window, or a bundle handed over without the anchors that place a key in time, must not clear a policy that requires confirmation. An approval that was never dispatched — never consumed, or a ceremony the producer applies itself — is <tt>not_reported</tt> and fails that policy too; a caller applying it to a bundle containing such approvals must expect that.</t>
        </section>
        <section anchor="s-4-7-5" numbered="false">
          <name>4.7.5 Trust</name>
          <t>A verifier MUST take evidence-source trust from the caller, as a mapping from <tt>source</tt> to one or more base64 SPKI P-256 keys, separate from the approver trust of §4.6.1. The separation is load-bearing: the party that approved an action is not an independent observer of its effect, and a source key accepted as an approver key would let an observation device approve.</t>
          <t>Each key MAY carry a validity interval: <tt>notBefore</tt> and <tt>notAfter</tt>, each a §4.3 timestamp, either one optional, both inclusive. A key entry is either the bare key or an object with exactly the members <tt>key</tt>, <tt>notBefore</tt> and <tt>notAfter</tt> (the last two optional); an entry with any other member, or with a bound that is present but is not a §4.3 timestamp, verifies a signature but establishes no trust — a misspelt or mistyped bound must fail closed, never read as "no bound". A key with an interval establishes trust in a statement only when the Evidence Record's <strong>anchored time</strong> lies within it. The anchored time is the earliest external witness time (§5.3: Rekor <tt>integratedTime</tt>, TSA <tt>genTime</tt>) among the anchors that counted toward quorum for the checkpoint the record's own inclusion proof names, and only if the record's <tt>seq</tt> lies within the <tt>seqStart</tt>..<tt>seqEnd</tt> those anchors were held to; it is the latest the record can have been committed, and a witness cannot be backdated. A verifier MUST NOT take the anchored time from any other checkpoint — one listing an over-wide range would backdate every record under it — and MUST refuse a bundle that lists one root twice with differing fields. <tt>observedAt</tt> and <tt>sampledAt</tt> are the source's own claims — a compromised key could place a statement inside a period when the key was still valid — and a verifier MUST NOT use them for this. A record no counting anchor covers has no anchored time, and a bounded key then does not establish trust; the reason is reported, and a contradiction so refused is still a finding (§4.7.4). An unbounded key is unaffected. The same anchored time governs the §4.6.1 signature check of an Evidence Record, so the two reports agree. Because the anchored time is an upper bound on the commit time, the two bounds are not symmetric: <tt>notAfter</tt> is conservative — a record committed inside the interval but anchored after it is refused — while <tt>notBefore</tt> admits a record committed before it and anchored after it, by up to one checkpoint period. DEWP gives a verifier no lower bound on when a record was committed, so a caller for whom the start of a key's validity matters relies on the producer's registry having refused the key before that time, not on <tt>notBefore</tt> alone.</t>
          <t>For an Evidence Record, the §4.6.1 signature check (and any caller policy requiring trusted signatures) MUST therefore use evidence-source trust: when the caller supplies approver trust but no evidence-source trust, an Evidence Record's signature is not checked. A leaf of any other kind is never checked against evidence-source trust. When the caller supplies no trust at all, §4.6.1's embedded-key behaviour is unchanged.</t>
          <t>A producer MAY maintain its own registry of evidence sources and refuse statements from unregistered or revoked ones before committing them; that registry is producer policy and does not substitute for the verifier's own trust. Revoking a source's key does not invalidate statements already committed under it: a caller expresses the revocation as the key's <tt>notAfter</tt>. Because trust is decided at the anchored time, a <tt>notAfter</tt> equal to the revocation instant refuses every statement in the same checkpoint period, those committed before the revocation included. A caller that wants them to keep their trust sets <tt>notAfter</tt> to the earliest witness time of the first checkpoint anchored after the revocation — the latest any of them can have been committed. That bound also admits a statement committed between the revocation and that anchor; on a producer that refuses revoked sources at ingest there is none.</t>
        </section>
        <section anchor="s-4-7-6" numbered="false">
          <name>4.7.6 What Execution Status Does Not Establish</name>
          <ul>
            <li>
              <t>
                <strong>Physical truth.</strong>
                <tt>confirmed</tt> means a source the caller trusts signed an in-window statement saying the approved effect was observed. A compromised, misconfigured or mis-wired source produces a genuine signature over a false observation. Where that matters, the caller should trust only sources whose observations are independent physical measurements, and claim no more than they support.</t>
            </li>
            <li>
              <t>
                <strong>Completeness, beyond the counter.</strong> The per-source <tt>sequence</tt> exposes a statement missing from inside the range a bundle covers; it does not expose a source that stopped signing, or statements after the last one committed (§4.7.3, Source continuity). Within that limit a withheld <tt>contradicted</tt> is a gap, not a <tt>confirmed</tt>; beyond it the ledger still leaves no gap to detect (§3 Invariant 4).</t>
            </li>
            <li>
              <t>
                <strong>Content that was redacted.</strong> §4.7 reads content-verified entries only (§4.7.3). Once a Dispatch or Approval Record is redacted to commitment-only under retention (§7.1), the statements it bound read <tt>unbound</tt>; once an Evidence Record is, its statement no longer counts. A status computed after a redaction can therefore differ from one computed before it, exactly as a later export can change a status by adding evidence; a caller that needs the status as it stood keeps the earlier bundle.</t>
            </li>
            <li>
              <t>
                <strong>Approval validity.</strong> The Approval Record is located by its payload type, nonce and hash only. Whether it carries a valid, trusted approval under the caller's DIV policy is decided by §4.6.1 and DIV, not by this section.</t>
            </li>
            <li>
              <t>
                <strong>Who executed.</strong> A Dispatch Record is the executing party's own statement.</t>
            </li>
          </ul>
        </section>
        <section anchor="s-4-7-7" numbered="false">
          <name>4.7.7 Parsing Signed Payloads</name>
          <t>Every place §4.7 says a <tt>signedPayload</tt> or a statement "parses", a verifier MUST apply this one procedure, so that no two conformant verifiers classify the same bytes differently:</t>
          <ol type="1">
            <li>
              <t>The whole string is one RFC 8259 JSON text: no trailing content after the value (whitespace aside), and no non-standard literals such as <tt>NaN</tt> or <tt>Infinity</tt>.</t>
            </li>
            <li>
              <t>Arrays and objects nest at most 64 levels deep, the outermost value counting as level 1. A deeper text does not parse. (Parsers differ in their built-in depth limits; this bound is below all of them.)</t>
            </li>
            <li>
              <t>When a member name repeats, the last occurrence wins, as in the reference parsers. For a statement this is moot: §4.7.2 rule 9 refuses any repeated name, because re-serialization cannot reproduce it.</t>
            </li>
            <li>
              <t>Every number is finite when read as an IEEE 754 double: <tt>1e400</tt>, or an integer too long for any double, does not parse (some parsers refuse it, others would read infinity or an exact big integer).</t>
            </li>
            <li>
              <t>Timestamps (§4.3) consist of ASCII digits only, and year <tt>0000</tt> is a valid year.</t>
            </li>
          </ol>
          <t>A payload that does not parse is neither an Execution Evidence Statement nor an Approval Record. Its leaf is still an Evidence Record when its <tt>event</tt> says so (§4.7.3), and is then reported <tt>invalid</tt>.</t>
        </section>
      </section>
    </section>
    <section anchor="s-5" numbered="false">
      <name>5. Two-Tier Merkle Log Tree &amp; Anchor Specification</name>
      <t>DEWP structures audit logs into a two-tier Merkle tree hierarchy:</t>
      <artwork>                  [ Daily Checkpoint Root (Tier 2 Anchor) ]
                                 /        \
          checkpointProof       /          \       checkpointProof
                               /            \
                   [ Block Root 1 ]      [ Block Root 2 ]
                       /      \              /      \
        blockProof    /        \            /        \    blockProof
                     /          \          /          \
                [ Leaf 1 ]   [ Leaf 2 ] [ Leaf 3 ]  [ Leaf 4 ]</artwork>
      <section anchor="s-5-1" numbered="false">
        <name>5.1 Tree Construction &amp; Balancing Rules</name>
        <t>Each rule below is referenced elsewhere as <strong>§5.1.N</strong>.</t>
        <ol type="1">
          <li>
            <t>
              <strong>§5.1.1 — Empty Tree:</strong> An empty Merkle tree has <tt>merkleRoot([]) == sha256Hex(byte(0x02))</tt>.</t>
          </li>
          <li>
            <t>
              <strong>§5.1.2 — Single Leaf Tree:</strong> A tree with 1 leaf has <tt>merkleRoot([L1]) == L1</tt>.</t>
          </li>
          <li>
            <t>
              <strong>§5.1.3 — Odd Node Duplication:</strong> When an odd number of nodes exists at any level during pairwise iteration, the final node MUST be duplicated (<tt>nextParent = hashPair(level[i], level[i])</tt>). This rule is why inclusion proofs require bounds — see §11.1.</t>
          </li>
          <li>
            <t>
              <strong>§5.1.4 — Tier 1 (Block Merkle Tree):</strong> Witness events occurring within a short time window are hashed into Leaf Hashes (<tt>hashLeaf(preimage)</tt>) and balanced into a Tier 1 Block Merkle Tree.</t>
          </li>
          <li>
            <t>
              <strong>§5.1.5 — Tier 2 (Daily Checkpoint Tree):</strong> Block Roots are re-hashed with the leaf tag (<tt>hashLeaf(blockRoot)</tt>) and aggregated into a Tier 2 Daily Checkpoint Tree, yielding a final single <strong>Daily Checkpoint Root</strong>.</t>
          </li>
          <li>
            <t>
              <strong>§5.1.6 — Leaf-to-Root Ordering &amp; Sibling-Relative Naming:</strong> All proof step arrays (<tt>blockProof</tt> and <tt>checkpointProof</tt>) MUST be ordered strictly from <strong>leaf to root</strong> (bottom to top). Each step MUST contain <tt>siblingHash</tt> (the digest of the sibling node) and <tt>siblingPosition</tt> (<tt>"LEFT"</tt> or <tt>"RIGHT"</tt> — the side on which the <strong>sibling</strong> sits relative to the running node). The running node is combined as <tt>hashPair(siblingHash, running)</tt> when <tt>siblingPosition == "LEFT"</tt>, else <tt>hashPair(running, siblingHash)</tt>.</t>
          </li>
        </ol>
      </section>
      <section anchor="s-5-2" numbered="false">
        <name>5.2 Signed Anchor Preimage Specification (0x03 Domain Tag)</name>
        <t>To anchor a Daily Checkpoint Root, an anchor provider MUST publish a signed anchor object.</t>
        <t>The <strong>Canonical Anchor Preimage</strong> MUST be serialized as an ordered 7-element JSON array of strings formatted according to RFC 8785 (JCS):</t>
        <sourcecode type="json">[
  "e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8",
  "2026-07-24T23:59:00.000Z",
  "https://transparency.example.org",
  "ES256",
  "1048576",
  "1049600",
  "6ae54abac75d273317f9f369306f2fbe85f2fafc9dd30685dc4f8a375257ef2b"
]</sourcecode>
        <table>
          <thead>
            <tr>
              <th>Index</th>
              <th>Field</th>
              <th>Description</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>0</td>
              <td>
                <tt>dailyRoot</tt>
              </td>
              <td>64-character lowercase hex Daily Checkpoint Root string.</td>
            </tr>
            <tr>
              <td>1</td>
              <td>
                <tt>timestamp</tt>
              </td>
              <td>The checkpoint's own claimed commit time — the same instant as its <tt>anchoredAt</tt> and the one its §5.4 chain hash commits to — ISO 8601 UTC in exactly the §4.3 form (<tt>YYYY-MM-DDTHH:mm:ss.sssZ</tt>). Every anchor over one checkpoint, from any issuer, carries this same value; a witness's own time lives in its evidence (§5.3).</td>
            </tr>
            <tr>
              <td>2</td>
              <td>
                <tt>issuer</tt>
              </td>
              <td>HTTPS URI or DID of the anchor provider.</td>
            </tr>
            <tr>
              <td>3</td>
              <td>
                <tt>algorithm</tt>
              </td>
              <td>Signature algorithm (<tt>ES256</tt>, <tt>Ed25519</tt>, <tt>RSA-PSS</tt>).</td>
            </tr>
            <tr>
              <td>4</td>
              <td>
                <tt>seqStart</tt>
              </td>
              <td>First global <tt>seq</tt> the checkpoint commits (stringified integer).</td>
            </tr>
            <tr>
              <td>5</td>
              <td>
                <tt>seqEnd</tt>
              </td>
              <td>Last global <tt>seq</tt> the checkpoint commits (stringified integer).</td>
            </tr>
            <tr>
              <td>6</td>
              <td>
                <tt>chainHash</tt>
              </td>
              <td>The checkpoint's §5.4 continuity chain hash (64-character lowercase hex).</td>
            </tr>
          </tbody>
        </table>
        <t>The anchor signing input is the raw domain-separated digest:</t>
        <t>
          <tt>anchorDigest = SHA-256(byte(0x03) || UTF8Bytes(JCS([dailyRoot, timestamp, issuer, algorithm, seqStart, seqEnd, chainHash])))</tt>
        </t>
        <t>
          <strong>Why the position is bound.</strong> A root alone says nothing about where in the log it sits or when it existed. An anchor over the root alone lets a party able to rewrite the log recompute a root and have it witnessed at any later time, with nothing tying the witness to the checkpoint it is offered for. Binding <tt>seqStart</tt>/<tt>seqEnd</tt> names the range; binding <tt>chainHash</tt> — which commits to the checkpoint's root, range, entry count, commit time and its predecessor's chain hash (§5.4) — makes every anchor re-attest the entire sequence of roots before it, so an edit to any earlier checkpoint invalidates the anchors of every later one. An anchor object lacking any of the seven fields, or carrying one in another shape, MUST NOT verify. In particular <tt>algorithm</tt> MUST be one of <tt>ES256</tt>, <tt>Ed25519</tt> and <tt>RSA-PSS</tt> for every anchor <tt>kind</tt> — external anchors carry the producer's own anchor algorithm — and a verifier MUST NOT fall back to some default scheme for any other label.</t>
        <t>The signature MUST be computed over the <strong>raw 32-byte <tt>anchorDigest</tt> value</strong>, NOT over its 64-character lowercase-hex string encoding. (For algorithms that apply their own message hash — e.g. ECDSA-P256/SHA-256, RSA-PSS/SHA-256 — the <tt>anchorDigest</tt> bytes are the message input; verifiers MUST pass the 32 raw bytes, never the hex text.) A verifier that hex-encodes the digest before signature verification MUST be considered non-conformant, as it will interoperate with neither compliant signers nor the reference vectors.</t>
        <t>
          <strong>RSA-PSS profile.</strong> An <tt>RSA-PSS</tt> anchor is RSASSA-PSS <xref target="RFC8017" /> with SHA-256 as the message hash, MGF1 with SHA-256 as the mask generation function, and a salt length of exactly 32 bytes (the hash length), over the raw 32-byte <tt>anchorDigest</tt> like every other algorithm. The key's modulus MUST be at least 2048 bits. A verifier MUST NOT recover the salt length from the signature or accept any other salt length, and MUST refuse a smaller key: both are properties of the signature scheme, and a verifier that auto-detects them verifies anchors that another conformant verifier refuses. Note that common signing APIs default to the MAXIMUM salt length (Node.js, OpenSSL); a producer MUST set the salt length explicitly.</t>
        <t>
          <strong>ECDSA signature encoding.</strong> Producers MUST emit ES256 anchor signatures in ASN.1/DER, the encoding the reference producer emits and the golden vectors pin. Verifiers MUST accept <strong>both</strong> ASN.1/DER and raw IEEE-P1363 (<tt>r ‖ s</tt>, 64 bytes for P-256). Do NOT infer the encoding from length: a DER signature whose <tt>r</tt> and <tt>s</tt> both happen to be short is also 64 bytes, so a length switch mis-rejects valid DER. Attempt both encodings and accept if either verifies. Accepting only DER is non-conformant: §5.3 exists to admit anchors from issuers the producer does not control, and a WebCrypto-based issuer can emit only P1363 — refusing those makes a genuine third-party anchor read as an invalid signature in one implementation while counting toward quorum in another. Both encodings are pinned by the <tt>signedAnchor</tt> vectors.</t>
        <section anchor="s-5-2-1" numbered="false">
          <name>5.2.1 External Anchor Representation</name>
          <t>The published JSON Schema (<tt>docs/schemas/dewp/anchor.schema.json</tt>) governs the anchor object's wire shape. Its <tt>kind</tt> registry is <tt>SELF</tt>, <tt>REKOR</tt>, <tt>RFC3161</tt>, and <tt>WEBHOOK</tt>; an absent <tt>kind</tt> MUST be treated as the legacy DEWP-signed form (<tt>SELF</tt>). A <tt>SELF</tt> anchor carries a non-empty §5.2 <tt>signature</tt>, which is its evidence.</t>
          <t>External <tt>REKOR</tt> and <tt>RFC3161</tt> anchors carry <tt>signature: ""</tt>; their <tt>evidence</tt> field contains the third party's own self-contained attestation. Their trust does not derive from a DEWP signature by the producer, but from verification of that attestation under trust material selected by the verifier. A <tt>WEBHOOK</tt> receipt is deployment-specific and MUST NOT count unless the deployment has an explicit verifier for its evidence.</t>
          <t>For <tt>REKOR</tt>, a verifier checks the Signed Entry Timestamp (SET) over Rekor's canonical entry under a caller-pinned Rekor log key, and confirms that the logged <tt>hashedrekord</tt> SHA-256 value is the expected hash of the §5.2 <tt>anchorDigest</tt>. The log key comes from the verifier's own trust configuration (normally Sigstore TUF), never from the bundle. Both checks are required: a valid SET for an unrelated public entry is not evidence for this checkpoint. The caller MUST bind the pinned log key to a particular issuer; the producer-controlled issuer string inside a submitted digest does not establish which witness signed it. The reference implementations use <tt>rekorIssuer</tt> alongside the log key. A legacy unscoped key is accepted only under a policy with exactly one trusted issuer; otherwise it cannot count or establish divergence. This prevents one log from satisfying a multi-issuer quorum under several producer-chosen names.</t>
          <t>For <tt>RFC3161</tt>, <tt>evidence</tt> is the base64-encoded DER TimeStampToken. Its SHA-256 message imprint MUST equal the raw §5.2 <tt>anchorDigest</tt> recomputed from the exported anchor's own seven signed fields; hashing that digest a second time is incorrect. These fields MUST remain exactly as submitted. In particular, a producer MUST NOT replace the bound timestamp with a receipt time or replace the bound issuer with another identity after obtaining evidence. The token's authenticated <tt>genTime</tt> establishes existence by the TSA time, not occurrence of the underlying business action at the anchor's producer-supplied timestamp — which is why §5.3 bounds the distance between the two.</t>
          <t>A verifier MAY count this evidence only after validating the token signature, message imprint, TSA signer identity, timestamping certificate purpose, certificate path and its caller-selected time and revocation policy. Embedded certificates MUST NOT establish trust. Trust roots and a TSA signer identity binding MUST be selected by the caller for that issuer. A producer checking a fresh response MUST also verify the request nonce. A minimal verifier without the necessary backend or trust MUST report the token as present but unverified and MUST NOT count it. The JSON Schema remains authoritative where this prose and the object shape differ.</t>
        </section>
      </section>
      <section anchor="s-5-3" numbered="false">
        <name>5.3 Anchor Trust, Quorum &amp; Key Discovery</name>
        <t>A verifier evaluates anchors against a local <strong>anchor policy</strong>:</t>
        <sourcecode type="json">NOTE: '\' line wrapping per RFC 8792

{
  "requiredAnchors": 2,
  "trustedIssuers": ["https://transparency.example.org", "https://an\
  chor.example.net"],
  "quorum": "ALL_MUST_AGREE"
}</sourcecode>
        <table>
          <thead>
            <tr>
              <th>Field</th>
              <th>Requirement</th>
              <th>Description</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>
                <tt>requiredAnchors</tt>
              </td>
              <td>REQUIRED</td>
              <td>Minimum count of distinct, independently-issued valid anchors over the same <tt>dailyRoot</tt>. SHOULD be ≥ 2 for high-assurance non-repudiation.</td>
            </tr>
            <tr>
              <td>
                <tt>trustedIssuers</tt>
              </td>
              <td>REQUIRED</td>
              <td>Allowed anchor issuer identifiers; an anchor whose <tt>issuer</tt> is not listed does not count toward quorum.</td>
            </tr>
            <tr>
              <td>
                <tt>quorum</tt>
              </td>
              <td>REQUIRED</td>
              <td>
                <tt>ALL_MUST_AGREE</tt> (every trusted anchor present MUST sign the same root) or <tt>N_OF_M</tt> (at least <tt>requiredAnchors</tt> agree).</td>
            </tr>
            <tr>
              <td>
                <tt>maxAnchorLagSeconds</tt>
              </td>
              <td>OPTIONAL</td>
              <td>The time bound below. Absent ⇒ 86400 (one day).</td>
            </tr>
          </tbody>
        </table>
        <t>
          <tt>anchorVerified</tt> is true only if the policy is satisfied.</t>
        <t>
          <strong>Time bound.</strong> An external anchor proves only that its witness saw the digest at the witness's own authenticated time — Rekor's <tt>integratedTime</tt>, an RFC 3161 token's <tt>genTime</tt>. An anchor first witnessed long after the checkpoint claims to exist says nothing about whether the checkpoint existed then, and it is exactly what re-anchoring a rewritten old root produces. A verifier MUST therefore compare each external witness time with the anchor's signed <tt>timestamp</tt> (the checkpoint's claimed time) and MUST NOT count an anchor witnessed more than <tt>maxAnchorLagSeconds</tt> after it, or more than a small clock-skew tolerance before it (the reference implementations allow 300 seconds). The verdict MUST say why such an anchor did not count, and SHOULD expose each issuer's witness time whether or not it counted. <tt>SELF</tt> anchors carry no independent time and are not subject to the bound. A producer SHOULD obtain external anchors promptly after committing a checkpoint; one whose anchoring stalled beyond the bound stays verifiable only to a relying party that deliberately widens it.</t>
        <t>The bound is only as good as the checkpoint time it is measured against, and the anchor's own <tt>timestamp</tt> is chosen by the producer. A verifier MUST therefore take that time from the checkpoint the anchor is counted for (below), and MUST NOT count an external witness for a checkpoint whose claimed time it does not know: a checkpoint that states no <tt>anchoredAt</tt> or <tt>chainHash</tt> MUST NOT be reported as anchored (divergence is still evaluated), and a single inclusion proof, which carries no checkpoint (§6.2), MUST NOT count an external anchor unless the caller supplies the checkpoint's record.</t>
        <t>
          <strong>Position.</strong> Where the verifier knows the checkpoint an anchor is counted for — an evidence bundle's <tt>checkpoints[]</tt> entry, §6.3, or a caller-trusted checkpoint record — it MUST NOT count an anchor whose signed <tt>seqStart</tt>, <tt>seqEnd</tt>, <tt>chainHash</tt> or <tt>timestamp</tt> differs from that checkpoint's <tt>seqStart</tt>, <tt>seqEnd</tt>, <tt>chainHash</tt> or <tt>anchoredAt</tt>, and it MUST NOT read such an anchor as divergence: it names another checkpoint. (The chain hash and claimed time are the exception for divergence. Both commit to the root, so a checkpoint rewritten with a different root necessarily states a different chain hash and usually a different time; a caller-obtained anchor for the same seq range that differs in them is exactly the conflicting evidence divergence exists to expose, and a verifier MUST NOT discard it for that difference alone.) <strong>A caller-trusted checkpoint record</strong> — normally the chain-verified line of the roots file (§5.4.1) for the checkpoint's root — takes precedence over the bundle's copy: a verifier given one MUST hold anchors to the record's fields where it states them, and MUST fail a bundle whose checkpoint states a different <tt>seqStart</tt>, <tt>seqEnd</tt>, <tt>entryCount</tt>, <tt>anchoredAt</tt> or <tt>chainHash</tt> for the same root. A bundle alone can re-date a checkpoint with a self-consistent chain hash over an invented predecessor; only a record the verifier already holds exposes that.</t>
        <t>
          <strong>Rekor submitter.</strong> Rekor logs a <tt>hashedrekord</tt> under whatever key submits it, and the anchor digest is computed from public fields, so anyone can have any digest logged. A verifier that knows the producer's anchor key (§5.3 key discovery) SHOULD pin it and count a Rekor entry only when its <tt>publicKey</tt> is that key and its signature over the anchor digest verifies. The time bound, not the pin, is what defeats back-dated re-anchoring; the pin removes third parties from the picture.</t>
        <t>
          <strong>What this does and does not establish.</strong> With the time bound and position binding, an anchored checkpoint cannot be rewritten after the bound has passed without every later anchor failing, because each binds the chain hash that commits to it. A party able to rewrite the log AND re-date every checkpoint from the edited one onward to "now" can still produce a self-consistent export; what exposes it is a roots file or earlier export retained by anyone (§5.4.1), whose chain hashes the rewrite cannot reproduce, and the months-long gap between the re-dated checkpoints and the leaf-committed <tt>createdAt</tt> of the events under them. In either quorum mode, fatal divergence can be established only from anchors the verifier obtained itself per checkpoint (out of band or through discovery), never from a bundle-carried set; valid same-root bundle anchors may still count toward quorum under verifier-trusted keys (§6.3). Divergence evidence MUST also satisfy the rules an anchor must satisfy to count toward quorum: its signed seq range MUST be the checkpoint's, an external witness time MUST lie within the time bound above of the anchor's own signed <tt>timestamp</tt>, and a <tt>REKOR</tt> entry MUST NOT establish divergence unless the verifier pinned the producer's submission key (the Rekor submitter rule above) — without it, anyone can have a digest over a different root logged under a trusted issuer's name and raise a false alarm. A <tt>SELF</tt> anchor carries no witness time; its signature under the issuer's trusted key is the evidence. If two such trusted issuers sign <strong>different</strong> roots for the same checkpoint, the verdict is <tt>INVALID</tt> (divergence), never a silent pick. A single self-hosted anchor (<tt>requiredAnchors: 1</tt> from the platform's own issuer) provides tamper-evidence but NOT independent non-repudiation, and the verifier SHOULD surface that distinction.</t>
        <t>
          <strong>RFC 3161 reference implementation.</strong> All five ports provide an optional OpenSSL 3 adapter. The adapter accepts SHA-256, SHA-384 or SHA-512 CMS signer digests and requires the single declared CMS digest algorithm to match the single SignerInfo digest; SHA-1 and MD5 are refused. Its caller-owned trust configuration pins the DER signer certificate's SHA-256 fingerprint to an issuer, supplies trusted CA certificates, and explicitly chooses offline CRL checking or unchecked revocation. CRL mode requires fresh, valid CRLs for the certificate chain at the caller's evaluation time. Unchecked mode makes no revocation assertion. Both modes verify certificate validity at that evaluation time and at the signed <tt>genTime</tt>; a future <tt>genTime</tt> is refused. Evaluation defaults to current time rounded up by less than one second. A caller can supply an explicit historical Unix-second evaluation time and retained CRLs; the adapter does not choose historical trust from the token itself. No network trust discovery, OCSP fetching or archival evidence renewal occurs. Unavailable OpenSSL, absent trust, or failed checks contribute zero issuers and produce a diagnostic note. Only caller-attributed evidence can establish divergence, including RFC 3161 evidence.</t>
        <t>These adapters have no additional language-package dependencies but require the OpenSSL 3 executable. Their positive and negative cases are shared in <tt>rfc3161-vectors.json</tt>. They do not imply the complete Extended Profile: NDJSON evidence streaming remains outside the current implementation.</t>
        <t>Verifiers MUST discover trusted anchor public keys via:</t>
        <ol type="1">
          <li>
            <t>Static SPKI / JWKS public key configuration supplied to the verifier, OR</t>
          </li>
          <li>
            <t>HTTPS Well-Known discovery endpoint at <tt>{issuer}/.well-known/dewp-anchors.json</tt>.</t>
          </li>
        </ol>
        <t>Anchor objects MUST carry a <tt>keyId</tt> referencing the active SPKI public key.</t>
      </section>
      <section anchor="s-5-4" numbered="false">
        <name>5.4 Checkpoint Continuity Chain (0x04 Domain Tag)</name>
        <t>An inclusion proof binds an event to <strong>one</strong> daily root. It says nothing about whether the <em>sequence</em> of daily roots was rewritten: a published roots file with one day silently replaced or removed is internally consistent and verifies fine. Producers therefore SHOULD link each checkpoint to its predecessor.</t>
        <t>The canonical chain preimage is the RFC 8785 JCS serialization of a <strong>6-element array of strings</strong>:</t>
        <artwork>[prevChainHash, root, seqStart, seqEnd, entryCount, anchoredAt]</artwork>
        <t>
          <tt>chainHash = sha256Hex(byte(0x04) || UTF8Bytes(chainPreimage))</tt>.</t>
        <t>Rules:</t>
        <ul>
          <li>
            <t>Every element MUST be a JSON <strong>string</strong>, including <tt>seqStart</tt>, <tt>seqEnd</tt> and <tt>entryCount</tt>. For an array of strings JCS reduces to plain <tt>JSON.stringify</tt>, so no implementation has to reproduce RFC 8785 number canonicalization — the part of JCS where independent verifiers most often drift. §5.2 makes the same trade for the same reason.</t>
          </li>
          <li>
            <t>
              <tt>prevChainHash</tt> for the first checkpoint MUST be the empty string <tt>""</tt>. No real chain hash can collide with it, since those are always 64 hex characters, so a verifier that reaches a <tt>""</tt> predecessor knows it is at the genuine start of the log and not at a truncation point.</t>
          </li>
          <li>
            <t>
              <tt>anchoredAt</tt> MUST be the same instant recorded on the checkpoint, ISO 8601 UTC with millisecond precision and a literal <tt>Z</tt> (§4.3).</t>
          </li>
          <li>
            <t>Verifiers MUST check both that each entry's <tt>chainHash</tt> recomputes <strong>and</strong> that its <tt>prevChainHash</tt> equals its predecessor's <tt>chainHash</tt>. Recomputation alone does not detect a deleted entry.</t>
          </li>
        </ul>
        <t>
          <strong>Transport of the chain.</strong> The chain is verifiable only over a source that carries <tt>chainHash</tt> and <tt>prevChainHash</tt> per checkpoint — normally the published roots file (§5.4.1). The Evidence Bundle of §6.3 carries both, with <tt>entryCount</tt>, for each checkpoint it includes, so a bundle-only verifier can recompute each checkpoint's chain hash and hold its anchors to it (§5.3). It cannot check that the sequence of daily roots is unbroken: a bundle carries only the checkpoints its entries fall under, which are rarely consecutive. For continuity, an auditor MUST obtain the chain from the roots file.</t>
        <section anchor="s-5-4-1" numbered="false">
          <name>5.4.1 Published Roots File (roots.jsonl)</name>
          <t>The RECOMMENDED chain transport is an append-only JSON-Lines file of published daily roots, one object per line, ordered by <tt>seqEnd</tt> (the reference implementation's publication point is <tt>ledger/roots/roots.jsonl</tt> — not yet publishing at the time of writing — and its exporter is <tt>packages/db/src/scripts/export-roots.ts</tt>):</t>
          <sourcecode type="json">NOTE: '\' line wrapping per RFC 8792

{"seqStart":"1","seqEnd":"10000","entryCount":10000,"root":"&lt;64-hex&gt;\
  ","anchorRef":"&lt;publication receipt&gt;","anchoredAt":"2026-07-15T00:\
  00:00.000Z","prevChainHash":"","chainHash":"&lt;64-hex&gt;"}</sourcecode>
          <table>
            <thead>
              <tr>
                <th>field</th>
                <th>meaning</th>
              </tr>
            </thead>
            <tbody>
              <tr>
                <td>
                  <tt>seqStart</tt> / <tt>seqEnd</tt>
                </td>
                <td>Global <tt>seq</tt> range the daily root commits (stringified integers).</td>
              </tr>
              <tr>
                <td>
                  <tt>entryCount</tt>
                </td>
                <td>Number of committed events under the root (JSON number; the chain preimage stringifies it, §5.4).</td>
              </tr>
              <tr>
                <td>
                  <tt>root</tt>
                </td>
                <td>The Daily Checkpoint Root, lowercase hex.</td>
              </tr>
              <tr>
                <td>
                  <tt>anchorRef</tt>
                </td>
                <td>The producer's publication receipt — self-identifying, NOT an independence claim.</td>
              </tr>
              <tr>
                <td>
                  <tt>anchoredAt</tt>
                </td>
                <td>The checkpoint's commit instant (ISO 8601 UTC, ms precision) — part of the chain preimage.</td>
              </tr>
              <tr>
                <td>
                  <tt>prevChainHash</tt>
                </td>
                <td>The previous line's <tt>chainHash</tt>; <tt>""</tt> marks the genuine start of the log.</td>
              </tr>
              <tr>
                <td>
                  <tt>chainHash</tt>
                </td>
                <td>
                  <tt>SHA-256(0x04 ‖ JCS([prevChainHash, root, seqStart, seqEnd, String(entryCount), anchoredAt]))</tt>, hex.</td>
              </tr>
            </tbody>
          </table>
          <t>Rules:</t>
          <ul>
            <li>
              <t>A verifier consuming the file MUST run the §5.4 continuity checks over the WHOLE file before trusting any root in it. A file that mixes chained and unchained lines MUST be refused. A file with no chain fields at all cannot be continuity-checked — a verifier MAY proceed with it (a hand-built minimal root list is a legitimate workflow) but MUST surface that continuity was not checked; the published file is always chained.</t>
            </li>
            <li>
              <t>Publication MUST be <strong>prefix-only</strong>: a line may be appended only when every earlier checkpoint is already in the file. A checkpoint whose anchor quorum lags therefore DELAYS later roots rather than being skipped — a skipped predecessor would leave a <tt>prevChainHash</tt> pointing at an entry not in the file, and back-filling it later would break the append-only ordering. A line already published never blocks appending after it, whatever its live anchor set looks like.</t>
            </li>
            <li>
              <t>A producer MUST refuse to append when a published line's <tt>root</tt> — or, same root, its <tt>chainHash</tt> — differs from its own record: a published root (or its chain identity) that changed is the precise event the file exists to make detectable.</t>
            </li>
          </ul>
          <t>
            <strong>Scope of the guarantee — this is NOT an RFC 6962 consistency proof.</strong> On its own the chain is only internally consistent: a producer that rewrites history can recompute every chain hash from the edit onward, and the rewritten file verifies. What the chain adds is that any two copies of the file can be compared at a single point — the chain hash of the latest line both contain commits to every root before it. So against a copy retained or published earlier (a relying party's saved file, the publication repository's history), the chain shows that no root up to that copy's last line was edited, reordered or removed; it says nothing about lines after it, or about any history nobody kept. Nor does it prove that the leaves committed under an earlier root are unchanged: that comes from each root being externally anchored, within the §5.3 time bound, under a preimage that binds its chain hash (§5.2). Neither mechanism is sufficient alone, and implementations MUST NOT describe the chain as a consistency proof or as tamper-proof.</t>
          <t>
            <strong>Global sequence continuity.</strong> Verifiers MUST NOT require <tt>seqStart(N+1) == seqEnd(N) + 1</tt> across checkpoints. Global <tt>seq</tt> is an ordering counter (§4 rule 5) and is commonly allocated from a non-transactional database sequence, where a rolled-back write burns its value permanently — gaps are therefore normal in a healthy log, and a strict check would raise false alarms that train operators to ignore the signal. Verifiers MUST instead require that ranges advance without overlapping (<tt>seqStart(N+1) &gt; seqEnd(N)</tt>). Gapless completeness is carried by the transactionally allocated per-tenant <tt>tenantSeq</tt> (§4 rule 4), not by global <tt>seq</tt>.</t>
          <t>
            <strong>Sealing a closed prefix (producer requirement).</strong> The gap tolerance above covers values that were never committed, such as those burned by a rolled-back write. It does not cover a value that commits <em>after</em> a block over its range was sealed. A producer MUST NOT seal a block over <tt>[seqStart, seqEnd]</tt> while any entry that could still commit with a <tt>seq</tt> in that range is uncommitted. Where <tt>seq</tt> is allocated at insert but becomes visible at commit, the two orders differ. A sealer that simply seals every row it can see will then close blocks over transactions still in flight. The late row either invalidates its block's stored root, or falls between two blocks and is never sealed; an evidence export then presents it as a per-tenant omission.</t>
          <t>
            <em>Implementation note (non-normative).</em> The reference producer enforces this with a commit barrier. Every insert takes a shared lock and draws its <tt>seq</tt> only while holding it. The sealer takes the same lock exclusively, so it waits for in-flight writers to finish, and no <tt>seq</tt> can be drawn while it holds the lock. The sealer's wait is bounded: on timeout it seals nothing that pass and retries on the next.</t>
        </section>
      </section>
    </section>
    <section anchor="s-6" numbered="false">
      <name>6. Proof Bundle Schemas</name>
      <t>Normative machine-readable JSON Schemas (Draft 2020-12) for the containers in this section are published alongside this document and are authoritative for structural validation:</t>
      <ul>
        <li>
          <t>
            <tt>docs/schemas/dewp/inclusion-proof.schema.json</tt> — <tt>dewp.audit.inclusion-proof</tt>
          </t>
        </li>
        <li>
          <t>
            <tt>docs/schemas/dewp/evidence-bundle.schema.json</tt> — <tt>dewp.audit.evidence-bundle</tt>
          </t>
        </li>
        <li>
          <t>
            <tt>docs/schemas/dewp/anchor.schema.json</tt> — signed anchor object (§5.2)</t>
        </li>
        <li>
          <t>
            <tt>docs/schemas/dewp/execution-evidence-statement.schema.json</tt> — Execution Evidence Statement (§4.7.2)</t>
        </li>
      </ul>
      <t>The JSON examples below are illustrative; where an example and a schema disagree, the schema governs. Each container schema accepts only the canonical <tt>dewp.audit.*</tt>
        <tt>kind</tt> (§6.5); the statement schema describes a signed object, not a container, and has no <tt>kind</tt>.</t>
      <section anchor="s-6-1" numbered="false">
        <name>6.1 Algorithm Registry Object</name>
        <t>All DEWP proof containers SHOULD carry an <tt>algorithmRegistry</tt> metadata block:</t>
        <sourcecode type="json">{
  "hashAlgorithm": "SHA-256",
  "serialization": "RFC8785-JCS",
  "merkleVersion": 1,
  "signatureAlgorithms": ["ES256", "WEBAUTHN", "AUTO_APPROVED"]
}</sourcecode>
      </section>
      <section anchor="s-6-2" numbered="false">
        <name>6.2 Single Inclusion Proof Bundle Schema (dewp.audit.inclusion-proof)</name>
        <t>The <tt>event.canonical</tt> object carries the raw JSON values exported by the producer. In particular, <tt>metadata</tt> is the parsed JSON value (or <tt>null</tt> when absent), not a pre-serialized JCS string. When rebuilding the ordered preimage, the verifier converts that raw value into the compact JCS string required at §4.2 index 5.</t>
        <t>The example uses the registered <tt>trust.intyga.audit.v1</tt> profile and therefore carries its six extension fields before the final <tt>tenantSeq</tt>, for an 18-element preimage.</t>
        <sourcecode type="json">NOTE: '\' line wrapping per RFC 8792

{
  "protocol": "DEWP",
  "version": "1.0",
  "kind": "dewp.audit.inclusion-proof",
  "profile": "trust.intyga.audit.v1",
  "exportedAt": "2026-07-24T12:30:00.000Z",
  "verificationLevel": "CONTENT_VERIFIED",
  "algorithmRegistry": {
    "hashAlgorithm": "SHA-256",
    "serialization": "RFC8785-JCS",
    "merkleVersion": 1
  },
  "event": {
    "seq": "1048576",
    "id": "0190e3f2-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
    "createdAt": "2026-07-24T12:00:00.000Z",
    "type": "ACTION_APPROVED",
    "outcome": "SUCCESS",
    "detail": "Database drop approved",
    "actorDid": "did:example:human:alice",
    "subjectDid": null,
    "signerDid": "did:example:human:alice",
    "signature": "base64-sig",
    "sigAlg": "ES256",
    "canonical": {
      "seq": "1048576",
      "createdAt": "2026-07-24T12:00:00.000Z",
      "event": "ACTION_APPROVED",
      "outcome": "SUCCESS",
      "detail": "Database drop approved",
      "metadata": { "target": "users" },
      "signerDid": "did:example:human:alice",
      "signerPublicKey": "base64-spki",
      "signedPayload": "{\"actionType\":\"db:dropTable\",\"display\"\
  :\"...\",\"type\":\"div-intent-verification\",\"v\":1}",
      "signature": "base64-sig",
      "sigAlg": "ES256",
      "isBillable": true,
      "tenantId": "tenant-uuid-01",
      "actorNodeId": "node-uuid-alice",
      "subjectNodeId": null,
      "edgeId": null,
      "challengeId": "challenge-uuid-01",
      "tenantSeq": "42"
    }
  },
  "proof": {
    "seq": "1048576",
    "leaf": "d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a\
  1b2c3d4e5",
    "blockIndex": "0",
    "blockRoot": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1\
  b2c3d4e5f6a1b2",
    "blockProof": [
      { "siblingHash": "f1e2d3c4b5a6f1e2d3c4b5a6f1e2d3c4b5a6f1e2d3c4\
  b5a6f1e2d3c4b5a6f1e2", "siblingPosition": "RIGHT" }
    ],
    "leafIndex": 0,
    "blockLeafCount": 2,
    "checkpointId": "cp-uuid-01",
    "checkpointRoot": "e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e1f0a9b8c\
  7d6e5f4a3b2c1d0e9f8",
    "checkpointProof": [
      { "siblingHash": "c4b3a2f1e5d6c4b3a2f1e5d6c4b3a2f1e5d6c4b3a2f1\
  e5d6c4b3a2f1e5d6c4b3", "siblingPosition": "LEFT" }
    ],
    "checkpointLeafIndex": 1,
    "checkpointLeafCount": 2,
    "anchorRef": "https://transparency.example.org/roots/2026-07-24.\
  json",
    "anchored": true,
    "externallyAnchored": true,
    "externallyAnchoredRequired": 2
  },
  "anchor": {
    "dailyRoot": "e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e1f0a9b8c7d6e5\
  f4a3b2c1d0e9f8",
    "timestamp": "2026-07-24T23:59:00.000Z",
    "issuer": "https://transparency.example.org",
    "keyId": "key-2026-01",
    "algorithm": "ES256",
    "seqStart": "1048576",
    "seqEnd": "1049600",
    "chainHash": "6ae54abac75d273317f9f369306f2fbe85f2fafc9dd30685dc\
  4f8a375257ef2b",
    "signature": "base64-anchor-signature",
    "anchorRef": "https://transparency.example.org/roots/2026-07-24.\
  json",
    "anchored": true
  },
  "anchors": [
    {
      "dailyRoot": "e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e1f0a9b8c7d6\
  e5f4a3b2c1d0e9f8",
      "timestamp": "2026-07-24T23:59:00.000Z",
      "issuer": "https://transparency.example.org",
      "keyId": "key-2026-01",
      "algorithm": "ES256",
      "seqStart": "1048576",
      "seqEnd": "1049600",
      "chainHash": "6ae54abac75d273317f9f369306f2fbe85f2fafc9dd30685\
  dc4f8a375257ef2b",
      "signature": "base64-anchor-signature"
    }
  ]
}</sourcecode>
        <t>Two anchoring flags appear in a proof, and they mean different things — both are <strong>producer claims</strong> a verifier reports for display and never treats as proof (the §5.3 quorum over the signed anchors is the check):</t>
        <ul>
          <li>
            <t>
              <tt>anchored</tt> — a publication receipt (<tt>anchorRef</tt>) exists. The reference producer writes one for every checkpoint at commit time, so this is COMMITMENT only and structurally always true for a checkpointed proof; it MUST NOT be read as independence.</t>
          </li>
          <li>
            <t>
              <tt>externallyAnchored</tt> — the producer claims a §5.3 external anchor quorum exists for this root: at least <tt>externallyAnchoredRequired</tt> DISTINCT INDEPENDENT issuers hold an anchor over it. Distinct <strong>issuers</strong>, not anchor rows — re-anchoring under a rotated key is one opinion. "Independent" excludes both the producer's own <tt>SELF</tt> anchor and any <tt>WEBHOOK</tt> receipt, because a webhook endpoint is operated by the producer and is therefore not a second opinion.</t>
          </li>
          <li>
            <t>
              <tt>externallyAnchoredRequired</tt> — the quorum size the claim above was evaluated against. Producers SHOULD emit it whenever they emit <tt>externallyAnchored</tt>, and verifiers SHOULD surface it wherever they surface the flag: without it the boolean cannot be read, since a deployment requiring one issuer and a deployment requiring three publish the same <tt>true</tt>. Counting the <tt>anchors</tt> array is not a substitute — the array may be absent, and its length is the count ACHIEVED rather than the count REQUIRED. Both fields remain producer claims; <tt>anchorVerified</tt> (§7.1) is the check.</t>
          </li>
        </ul>
        <t>The single <tt>anchor</tt> field alongside the <tt>anchors</tt> array is a legacy convenience (the first entry of the set); the array is what the §5.3 quorum rule is evaluated over, and the published JSON Schema governs where this example and the schema disagree (§6).</t>
        <t>A single proof carries no checkpoint record, so it gives the verifier no position or claimed time to hold an anchor to. An external (<tt>REKOR</tt>, <tt>RFC3161</tt>) anchor therefore counts toward <tt>anchorVerified</tt> only when the caller supplies the proof's checkpoint record — the roots-file line for its root (§5.4.1) — and the anchor is then held to that record's range, chain hash and <tt>anchoredAt</tt> (§5.3). A record naming a different root than the caller's <tt>trustedRoot</tt> MUST fail the proof. Where the record carries <tt>entryCount</tt>, the proof's leaf counts MUST be consistent with it (§17.3).</t>
      </section>
      <section anchor="s-6-3" numbered="false">
        <name>6.3 Multi-Entry Evidence Bundle Schema (dewp.audit.evidence-bundle)</name>
        <sourcecode type="json">NOTE: '\' line wrapping per RFC 8792

{
  "protocol": "DEWP",
  "version": "1.0",
  "kind": "dewp.audit.evidence-bundle",
  "exportedAt": "2026-07-24T12:30:00.000Z",
  "tenant": { "id": "tenant-uuid-01", "name": "Acme Corp" },
  "range": { "from": "2026-07-01T00:00:00.000Z", "to": "2026-07-24T2\
  3:59:59.000Z" },
  "tenantSequenceCommitment": {
    "tenantId": "tenant-uuid-01",
    "firstTenantSeq": "1",
    "lastTenantSeq": "42"
  },
  "entries": [
    {
      "event": {
        "seq": "1048576",
        "createdAt": "2026-07-24T12:00:00.000Z",
        "type": "ACTION_APPROVED",
        "outcome": "SUCCESS",
        "redaction": { "mode": "NONE" },
        "signerDid": "did:example:human:alice",
        "sigAlg": "ES256",
        "canonical": { "seq": "1048576", "tenantSeq": "42" }
      },
      "proof": { "seq": "1048576", "leaf": "d4e5f6...", "blockRoot"\
  : "...", "checkpointRoot": "..." }
    },
    {
      "event": {
        "seq": "1048577",
        "createdAt": "2026-07-24T12:05:00.000Z",
        "type": "USER_DELETED",
        "outcome": "SUCCESS",
        "redaction": {
          "mode": "COMMITMENT_ONLY",
          "removedFields": ["detail", "metadata", "signedPayload", "\
  signature", "signerPublicKey"],
          "redactedAt": "2027-01-15T09:00:00.000Z",
          "reason": "gdpr-erasure",
          "commitment": {
            "leaf": "b9a8c7d6e5f4b9a8c7d6e5f4b9a8c7d6e5f4b9a8c7d6e5f\
  4b9a8c7d6e5f4b9a8",
            "tenantSeq": "43"
          }
        },
        "signerDid": null,
        "sigAlg": null
      },
      "proof": { "seq": "1048577", "leaf": "b9a8c7d6e5f4b9a8c7d6e5f4\
  b9a8c7d6e5f4b9a8c7d6e5f4b9a8c7d6e5f4b9a8", "blockRoot": "...", "ch\
  eckpointRoot": "..." }
    }
  ],
  "checkpoints": [
    {
      "id": "cp-uuid-01",
      "root": "e9f8a7...",
      "anchorRef": "https://transparency.example.org/roots/2026-07-2\
  4.json",
      "anchoredAt": "2026-07-24T23:59:00.000Z",
      "seqStart": "1048576",
      "seqEnd": "1049600",
      "entryCount": 1025,
      "prevChainHash": "",
      "chainHash": "6ae54abac75d273317f9f369306f2fbe85f2fafc9dd30685\
  dc4f8a375257ef2b",
      "externallyAnchored": true,
      "externallyAnchoredRequired": 2,
      "anchors": [
        {
          "dailyRoot": "e9f8a7...",
          "timestamp": "2026-07-24T23:59:00.000Z",
          "issuer": "https://rekor.sigstore.dev",
          "keyId": "rekor-2026",
          "algorithm": "ES256",
          "seqStart": "1048576",
          "seqEnd": "1049600",
          "chainHash": "6ae54abac75d273317f9f369306f2fbe85f2fafc9dd3\
  0685dc4f8a375257ef2b",
          "signature": "",
          "kind": "REKOR",
          "evidence": "base64-rekor-set-and-inclusion-proof"
        }
      ]
    }
  ]
}</sourcecode>
        <t>Each checkpoint entry carries <tt>seqStart</tt> / <tt>seqEnd</tt>, the global sequence range it commits, its §5.4 chain fields <tt>entryCount</tt> / <tt>prevChainHash</tt> / <tt>chainHash</tt>, and its own <tt>anchors</tt> array — the per-checkpoint anchor set the §5.3 quorum rule is evaluated over. A bundle that reports only a single top-level anchor cannot express quorum, so the array is where independence is actually assessed. A verifier supplied with an anchor policy and key resolver but no anchors of its own MAY evaluate quorum over this array — the signatures still verify only under keys the VERIFIER trusts, so a bundle cannot vouch for itself — but bundle-carried anchors MUST NOT be able to establish the fatal divergence verdict of §5.3, which requires anchors the verifier fetched per checkpoint itself (the producer chooses what a bundle carries). A verifier MUST recompute each checkpoint's <tt>chainHash</tt> from its <tt>prevChainHash</tt>, <tt>root</tt>, <tt>seqStart</tt>, <tt>seqEnd</tt>, <tt>entryCount</tt> and <tt>anchoredAt</tt> and fail the bundle when it does not recompute, and MUST hold every anchor to that checkpoint's position and claimed time (§5.3). <tt>externallyAnchored</tt> is the producer's §5.3 quorum claim for the checkpoint (distinct INDEPENDENT issuers — neither <tt>SELF</tt> nor <tt>WEBHOOK</tt> — ≥ <tt>externallyAnchoredRequired</tt>, which SHOULD be emitted beside it) — display only, per the §6.2 note; <tt>anchoredAt</tt> is the checkpoint's commit instant (self-commitment time), not evidence of external anchoring. A checkpoint that carries anchors but states no <tt>chainHash</tt> or <tt>anchoredAt</tt> — and has no caller-trusted record supplying them — MUST NOT be reported as anchored (§5.3); stripping those fields is exactly how a producer would switch off the time bound.</t>
        <t>Entry-level rules, in addition to §7 and §7.2:</t>
        <ul>
          <li>
            <t>Each committed event appears at most once. A verifier MUST fail an entry whose <tt>proof.leaf</tt> or <tt>seq</tt> already appeared in the bundle; otherwise one genuine event can fill two positions.</t>
          </li>
          <li>
            <t>
              <tt>blockLeafCount</tt> and <tt>checkpointLeafCount</tt> are properties of a tree, so every proof into the same <tt>blockRoot</tt> (respectively checkpoint root) MUST state the same value, and both MUST be consistent with the checkpoint's <tt>entryCount</tt> (caller record first, then the bundle's chain-verified value; §17.3).</t>
          </li>
          <li>
            <t>An entry that ships a preimage under an unrecognised <tt>profile</tt> MUST fail (§4.5); its counter is not read.</t>
          </li>
          <li>
            <t>The tenant rule of §7.2 applies.</t>
          </li>
        </ul>
        <t>A bundle also carries <tt>profile</tt>, <tt>algorithmRegistry</tt> and <tt>howToVerify</tt> at the top level. <tt>profile</tt> names the leaf profile in force (§4.5), <tt>algorithmRegistry</tt> pins the algorithm identifiers (§12), and <tt>howToVerify</tt> is a human-readable instruction string carrying no normative weight.</t>
      </section>
      <section anchor="s-6-4" numbered="false">
        <name>6.4 Streaming Evidence Export Schema (dewp.audit.evidence-stream)</name>
        <blockquote>
          <t>
            <strong>Specified, not implemented.</strong> As of 31 Jul 2026 no producer emits this form and no verifier in this repository reads it — including the TypeScript reference implementation. The format below is normative for anyone who chooses to implement it, but nothing here claims DEWP Extended Profile conformance (§9.2), and no INTYGA surface streams evidence. Use <tt>dewp.audit.evidence-bundle</tt> (§6.3), which is implemented end to end.</t>
        </blockquote>
        <t>For multi-gigabyte log exports, implementations SHOULD stream evidence formatted as JSON Lines (NDJSON).</t>
        <t>Line 1 (Header Object):</t>
        <sourcecode type="json">NOTE: '\' line wrapping per RFC 8792

{"protocol":"DEWP","version":"1.0","kind":"dewp.audit.evidence-strea\
  m","exportedAt":"2026-07-24T12:30:00.000Z","tenantId":"tenant-uuid\
  -01"}</sourcecode>
        <t>Line 2..N (Entry Objects):</t>
        <sourcecode type="json">NOTE: '\' line wrapping per RFC 8792

{"type":"entry","seq":"1048576","tenantSeq":"42","leaf":"d4e5f6...",\
  "canonical":[...],"proof":{...}}</sourcecode>
        <t>Line N+1 (Trailer Commitment Object):</t>
        <sourcecode type="json">NOTE: '\' line wrapping per RFC 8792

{"type":"trailer","seqStart":"1","seqEnd":"42","entryCount":42,"chec\
  kpointRoot":"e9f8a7..."}</sourcecode>
      </section>
      <section anchor="s-6-5" numbered="false">
        <name>6.5 Kind Values</name>
        <t>The canonical <tt>kind</tt> values are <tt>dewp.audit.inclusion-proof</tt>, <tt>dewp.audit.evidence-bundle</tt>, and <tt>dewp.audit.evidence-stream</tt>. A compliant DEWP producer MUST emit these forms and a compliant verifier MUST reject any other value. There are no vendor-prefixed aliases: <tt>kind</tt> names the protocol, never the implementer.</t>
      </section>
    </section>
    <section anchor="s-7" numbered="false">
      <name>7. Verification Procedure &amp; Verification Levels</name>
      <t>An auditor or Relying Party verifying a DEWP Proof Bundle MUST perform the following steps:</t>
      <sourcecode>NOTE: '\' line wrapping per RFC 8792

                          [ Start Verification ]
                                     |
                                     v
                [ Check 1: Verify Domain &amp; Bundle Version ]
                                     |
                                     v
           [ Check 2: Recompute Leaf Hash from Canonical Preimage ]
             hashLeaf(canonical) == proof.leaf (if unredacted)
                                     |
                                     v
            [ Check 3: Hop 1 - Verify Event Leaf -&gt; Block Root ]
   verifyMerkleProof(leaf, blockProof, blockRoot,
                     {leafIndex, blockLeafCount}) == true
                                     |
                                     v
          [ Check 4: Hop 2 - Verify Block Root -&gt; Daily Root ]
   verifyMerkleProof(hashLeaf(blockRoot), checkpointProof, dailyRoot,
                     {checkpointLeafIndex, checkpointLeafCount}) == \
  true
                                     |
                                     v
              [ Check 5: Compare Against Trusted Signed Anchor ]
      verifyAnchorSignature(anchor, 0x03) &amp;&amp; dailyRoot == anchor.dai\
  lyRoot ?
                                     |
                                     v
            [ Check 6: Verify Tenant Sequence Continuity ]
                 tenantSeq_N+1 == tenantSeq_N + 1 ?
                                     |
                                     v
          [ Verdict: CONTENT / COMMITMENT / SIGNATURE / FULLY PASSE\
  D ]</sourcecode>
      <t>Check 6 is defined over one specific copy of <tt>tenantSeq</tt> and not the others; §7.2 states which.</t>
      <section anchor="s-7-1" numbered="false">
        <name>7.1 Verification Properties and Derived Level</name>
        <t>A verifier MUST evaluate four <strong>independent</strong> boolean properties. They are orthogonal — in particular, anchor verification is NOT a strict superset of signature verification (a content-only event with no signer can still be anchor-verified; a signed event can be content-and-signature-verified without any anchor):</t>
        <table>
          <thead>
            <tr>
              <th>Property</th>
              <th>Meaning</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>
                <tt>commitmentVerified</tt>
              </td>
              <td>The two-hop Merkle inclusion (leaf → block root → daily root) recomputes to the bundle's <tt>checkpointRoot</tt>. Holds for redacted entries where only the leaf is retained.</td>
            </tr>
            <tr>
              <td>
                <tt>contentVerified</tt>
              </td>
              <td>
                <tt>commitmentVerified</tt> AND the full Canonical Preimage is present AND <tt>hashLeaf(canonical) == proof.leaf</tt>. Not achievable for <tt>COMMITMENT_ONLY</tt> entries (content purged).</td>
            </tr>
            <tr>
              <td>
                <tt>signatureVerified</tt>
              </td>
              <td>The embedded event signature material (<tt>signedPayload</tt>, <tt>signature</tt>, <tt>signerPublicKey</tt>) verifies offline. Requires <tt>contentVerified</tt> (you must have the signed bytes). N/A when no signer is present.</td>
            </tr>
            <tr>
              <td>
                <tt>anchorVerified</tt>
              </td>
              <td>The <tt>checkpointRoot</tt> satisfies the verifier's anchor quorum policy (§5.3): ≥ <tt>requiredAnchors</tt> trusted issuers signed the SAME root under the <tt>0x03</tt> domain tag, each anchor binding the checkpoint's position and — for an external witness — witnessed within the §5.3 time bound.</td>
            </tr>
          </tbody>
        </table>
        <t>A bundle MAY carry a summary <tt>verificationLevel</tt> for display, but conformant verifiers MUST also expose the four properties so a Relying Party can apply its own policy. The summary level is derived as:</t>
        <ul>
          <li>
            <t>
              <strong>
                <tt>INVALID</tt>
              </strong>: <tt>commitmentVerified == false</tt> (inclusion or leaf-hash recomputation failed), OR anchor divergence was detected.</t>
          </li>
          <li>
            <t>
              <strong>
                <tt>COMMITMENT_VERIFIED</tt>
              </strong>: <tt>commitmentVerified</tt> only (redacted / commitment-only entry).</t>
          </li>
          <li>
            <t>
              <strong>
                <tt>CONTENT_VERIFIED</tt>
              </strong>: <tt>commitmentVerified &amp;&amp; contentVerified</tt>.</t>
          </li>
          <li>
            <t>
              <strong>
                <tt>SIGNATURE_VERIFIED</tt>
              </strong>: <tt>contentVerified &amp;&amp; signatureVerified</tt>.</t>
          </li>
          <li>
            <t>
              <strong>
                <tt>FULLY_VERIFIED</tt>
              </strong>: all required properties for the entry passed <strong>and</strong>
              <tt>anchorVerified</tt>. For a signed event that is <tt>contentVerified &amp;&amp; signatureVerified &amp;&amp; anchorVerified</tt>; for an unsigned event that is <tt>contentVerified &amp;&amp; anchorVerified</tt> (no signature is required to exist).</t>
          </li>
        </ul>
        <t>A Relying Party MUST NOT treat <tt>SIGNATURE_VERIFIED</tt> as implying independent non-repudiation: without <tt>anchorVerified</tt> the root is still only vendor-asserted.</t>
        <t>A verifier implementing §4.7 additionally reports, for an Evidence Bundle, an Execution Status per approval nonce and a check per Evidence Record. These sit beside the four properties: they never change a property or the derived level, and an Execution Status is only as meaningful as the entries it was computed from — a <tt>confirmed</tt> status over entries that are not anchor-verified rests on a vendor-asserted root.</t>
      </section>
      <section anchor="s-7-2" numbered="false">
        <name>7.2 Check 6 — Tenant Sequence Continuity: Source, Disclosure, Declared Range</name>
        <t>An Evidence Bundle carries <tt>tenantSeq</tt> in up to three places per entry, and only one of them is covered by the Merkle commitment:</t>
        <table>
          <thead>
            <tr>
              <th>Location</th>
              <th>Covered by the leaf?</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>
                <tt>entries[].event.canonical.tenantSeq</tt> — the final Canonical Preimage element (§4.1)</td>
              <td>
                <strong>Yes.</strong>
                <tt>hashLeaf(canonical)</tt> is what the two-hop proof binds to the anchored root.</td>
            </tr>
            <tr>
              <td>
                <tt>entries[].event.redaction.commitment.tenantSeq</tt> — retained through <tt>COMMITMENT_ONLY</tt> redaction (§15)</td>
              <td>No. It is the producer's redaction record.</td>
            </tr>
            <tr>
              <td>
                <tt>entries[].event.tenantSeq</tt> — the display copy carried on every entry</td>
              <td>No. It is a sibling of <tt>canonical</tt>, and nothing signs it.</td>
            </tr>
          </tbody>
        </table>
        <ol type="1">
          <li>
            <t>A verifier MUST read the counter from <tt>canonical.tenantSeq</tt> for every entry that carries a Canonical Preimage, and from nowhere else: a <tt>null</tt> there means the entry has no counter (rule 5), never "fall back". Preferring the display copy is a conformance failure, not an implementation shortcut: a producer that drops the incriminating events, keeps the honest ones with their genuine proofs, and renumbers the display copies to close the hole yields a bundle that passes such a check reporting no gap — which forfeits §17.2 outright. Falling back to the redaction record for a preimage whose counter is <tt>null</tt> is the same failure: a genuine tenant-less leaf, tagged with an unsigned <tt>redaction.commitment.tenantSeq</tt>, then fills the hole while counting as content-verified. (A preimage the verifier could not bind to its leaf — an entry under an unrecognised Application Profile, §4.5 — is not leaf-bound either; in an Evidence Bundle such an entry fails, and its counter is not read.)</t>
          </li>
          <li>
            <t>Only where an entry has no preimage — a <tt>COMMITMENT_ONLY</tt> entry, whose content was lawfully purged — the verifier MAY fall back to <tt>redaction.commitment.tenantSeq</tt>, and then to <tt>event.tenantSeq</tt>. Without a fallback the position lawful redaction leaves behind would read as an omission.</t>
          </li>
          <li>
            <t>Where a preimage IS present, the verifier MUST compare <tt>event.tenantSeq</tt> and <tt>redaction.commitment.tenantSeq</tt>, when present, against <tt>canonical.tenantSeq</tt> and fail the entry when either differs. The same applies to every other field duplicated alongside the preimage (<tt>seq</tt>, <tt>createdAt</tt>, <tt>type</tt>, <tt>outcome</tt>, <tt>signerDid</tt>, <tt>sigAlg</tt>): the displayed copy is what a human, a console or a SIEM reads, so an unchecked one is a caption over the evidence rather than part of it.</t>
          </li>
          <li>
            <t>A verifier that read any counter under rule 2 MUST disclose it in its verdict. Continuity across values that are not leaf-bound rests on the producer's redaction record, not on the anchored log, and a Relying Party cannot tell that from a bare pass.</t>
          </li>
          <li>
            <t>An entry carrying no <tt>tenantSeq</tt> in any of the three locations — or a preimage whose <tt>tenantSeq</tt> is <tt>null</tt> — MUST NOT be treated as continuous with its neighbours: it occupies no counter position, and the verifier MUST disclose that gaplessness could not be checked across it.</t>
          </li>
          <li>
            <t>
              <tt>tenantSeq</tt> is a per-tenant counter, so contiguity is meaningful only over one tenant's entries. Every content-verified entry whose <tt>canonical.tenantId</tt> is non-null MUST equal <tt>tenant.id</tt>; a bundle whose <tt>tenant</tt> is absent or has a <tt>null</tt> id declares no tenant, and any entry naming one MUST fail. (Otherwise one tenant's genuine, leaf-bound counters splice into another's gap.)</t>
          </li>
        </ol>
        <section numbered="false">
          <name>Cross-checking tenantSequenceCommitment</name>
          <t>Contiguity alone proves only that the entries <em>present</em> are consecutive — not that the producer exported the range it says it exported. <tt>tenantSequenceCommitment</tt> (§6.3) declares the closed range a bundle covers; the published JSON Schema requires a producer to emit it whenever any entry carries a <tt>tenantSeq</tt>, so a bundle with counters and no declared range is already non-conformant. A verifier MUST check the entries against it:</t>
          <ul>
            <li>
              <t>The counter read from the first entry that carries one MUST equal <tt>firstTenantSeq</tt>, and the counter read from the last such entry MUST equal <tt>lastTenantSeq</tt>. Either mismatch is a verification failure — the producer narrowed the range it declared.</t>
            </li>
            <li>
              <t>
                <tt>tenantSequenceCommitment.tenantId</tt> SHOULD equal <tt>tenant.id</tt>; a mismatch MUST be disclosed.</t>
            </li>
            <li>
              <t>If no entry carries a readable counter, the cross-check is unsatisfiable; the verifier MUST disclose that the declared range went unchecked rather than treat it as met.</t>
            </li>
          </ul>
          <t>Two limits of the cross-check, both structural: <tt>tenantSequenceCommitment</tt> is itself an unsigned producer claim, so it catches only a producer whose truncation contradicts its own declaration; and it says nothing about counters outside the declared range (§3 Invariant 4).</t>
        </section>
      </section>
    </section>
    <section anchor="s-8" numbered="false">
      <name>8. Security Considerations</name>
      <t>DEWP provides cryptographic protection against:</t>
      <ul>
        <li>
          <t>
            <strong>Post-hoc Modification of Committed Events:</strong> Altering any attribute of a committed event breaks its <tt>leafHash</tt> and invalidates the Merkle inclusion proof against the published root.</t>
        </li>
        <li>
          <t>
            <strong>Deletion of Committed Events:</strong> Deleting an already-committed event breaks the contiguous <tt>tenantSeq</tt> chain, letting auditors detect that a committed event is missing. This does NOT prove a producer never withheld an event from commitment in the first place (see §3 Invariant 4): DEWP evidences the integrity and completeness of the <em>committed, anchored</em> history, not of all real-world events.</t>
        </li>
        <li>
          <t>
            <strong>Cross-Domain Signature &amp; Second-Preimage Collisions:</strong> Domain separation prefixes (<tt>0x00</tt> leaves, <tt>0x01</tt> nodes, <tt>0x02</tt> empty root, <tt>0x03</tt> anchors, <tt>0x04</tt> checkpoint chain) prevent attackers from presenting interior node hashes, anchor signatures, or chain links as valid leaf preimages.</t>
        </li>
        <li>
          <t>
            <strong>Vendor Log Forgery:</strong> Requiring daily checkpoint roots to be signed under domain <tt>0x03</tt> and published to independent public anchors prevents platform operators from quietly rewriting historical audit logs.</t>
        </li>
        <li>
          <t>
            <strong>Privacy Retention Compliance:</strong> Commitment-Only verification allows data retention ladders to redact sensitive user data while leaving the immutable Merkle cryptographic commitment intact.</t>
        </li>
      </ul>
    </section>
    <section anchor="s-9" numbered="false">
      <name>9. Conformance Requirements &amp; Profiles</name>
      <t>A compliant implementation MUST claim conformance to one of two profiles:</t>
      <section anchor="s-9-1" numbered="false">
        <name>9.1 DEWP Core Profile</name>
        <t>Requires implementation of:</t>
        <ul>
          <li>
            <t>RFC 8785 JCS 12-element canonical array serialization (Section 4.1).</t>
          </li>
          <li>
            <t>Domain-separated Merkle leaf and node hashing (Section 4.4).</t>
          </li>
          <li>
            <t>Single inclusion proof verification (<tt>dewp.audit.inclusion-proof</tt>).</t>
          </li>
        </ul>
        <t>The <tt>0x04</tt> checkpoint continuity chain (§5.4) is <strong>outside Core</strong>: it belongs to the roots-file transport, not to inclusion verification. The repository's TypeScript, Python, Go, Rust and Java verifiers implement it alongside the Core primitives. External implementations that omit it SHOULD state that limit explicitly.</t>
      </section>
      <section anchor="s-9-2" numbered="false">
        <name>9.2 DEWP Extended Profile</name>
        <t>Requires implementation of <strong>DEWP Core</strong> plus:</t>
        <ul>
          <li>
            <t>Multi-entry evidence bundle verification with <tt>tenantSeq</tt> gapless validation (<tt>dewp.audit.evidence-bundle</tt>).</t>
          </li>
          <li>
            <t>NDJSON evidence streaming (<tt>dewp.audit.evidence-stream</tt>) — see the §6.4 note: specified but unimplemented, so no implementation claims this profile.</t>
          </li>
          <li>
            <t>Signed multi-anchor verification with <tt>0x03</tt> domain tag (Section 5.2 &amp; 6.2).</t>
          </li>
          <li>
            <t>Offline event-signature verification of embedded intent-proof material (Sections 1.3 &amp; 7.1), evaluated <strong>per entry</strong> in a multi-entry bundle as well as for a single inclusion proof. Only signature material a leaf carries in full is checkable offline: <tt>ES256</tt> over <tt>signedPayload</tt>, or a <tt>WEBAUTHN</tt> assertion when the Application Profile carries <tt>authenticatorData</tt> and <tt>clientDataJSON</tt> and the caller supplies signer trust, expected origin and RP ID (§4.6.1). Missing material/trust is reported as not checked; <tt>AUTO_APPROVED</tt> has no human signature and is not applicable. A caller may require trusted signatures as an additional acceptance policy. An entry whose committed signature does not verify MUST be reported, but MUST NOT by itself invalidate the entry's commitment or content verification — the signature bytes are themselves committed, so a failure there means the producer anchored proof material that does not check out, not that the bundle was altered.</t>
          </li>
          <li>
            <t>Support for Application Profile Extensions (Section 4.5).</t>
          </li>
        </ul>
      </section>
      <section anchor="s-9-3" numbered="false">
        <name>9.3 Execution Evidence (optional, version 1.1)</name>
        <t>An Extended implementation MAY additionally claim Execution Evidence conformance, which requires:</t>
        <ul>
          <li>
            <t>Recognition of Evidence Records and the §4.7.3 statement check, with evidence-source trust separate from approver trust (§4.7.5) in both the statement check and the §4.6.1 signature check.</t>
          </li>
          <li>
            <t>The §4.7.4 Execution Status for every nonce in an Evidence Bundle — Approval Records included — with a caller-supplied evaluation time, skew allowance and maximum evidence window; the §4.7.4 findings (dispatch conflict, late contradiction, untrusted contradiction, window capped, source continuity); evidence-source key validity intervals checked at the anchored time (§4.7.5); and an optional caller policy requiring every nonce to be <tt>confirmed</tt>.</t>
          </li>
          <li>
            <t>The §4.7.7 parsing procedure wherever §4.7 parses a signed payload, so that no two verifiers classify the same bytes differently.</t>
          </li>
          <li>
            <t>The <tt>executionEvidence</tt> cases of <tt>verifier-parity-vectors.json</tt> (§10).</t>
          </li>
        </ul>
        <t>The repository's TypeScript, Python, Go, Rust and Java verifiers claim it.</t>
      </section>
    </section>
    <section anchor="s-10" numbered="false">
      <name>10. Reference Test Vectors</name>
      <t>Compliant implementations MUST pass the official cross-language golden test vectors, published in this repository as <tt>packages/mcp-schemas/vectors/ledger-vectors.json</tt> (DEWP) alongside <tt>canonical-vectors.json</tt> (the companion DIV set). <strong>Those files are the normative source.</strong>
        <tt>verifier-parity-vectors.json</tt> additionally pins executable receipt, bundle, anchor-quorum, Rekor, redaction, sequence-range and continuity cases across all five repository verifiers; its self-contained <tt>dewpEvidenceHardening</tt> section (own keys) pins the September 2026 evidence rules — counter source, tenant binding, unknown profiles, caller-trusted checkpoint records, duplicate entries, leaf-count binding and the anchor algorithm registry — and its <tt>verifierInputHardening</tt> section (own keys) pins the §5.2 RSA-PSS profile (salt length, key size) and the §5.3 admission rules for divergence evidence. Its <tt>executionEvidence</tt> section (own keys) pins §4.7: every statement-check outcome, every Execution Status, the precedence, binding, window and window-bound rules, the enumeration of Approval Records, the late-contradiction, untrusted-contradiction, dispatch-conflict and source-continuity findings, key validity at the anchored time, and the separation of evidence-source trust from approver trust.</t>
      <t>The cases below are <strong>not</strong> drawn from those files. They are independent worked examples over the <strong>bare 12-element Core</strong> array (no Application Profile), provided so an implementer can check a fresh SHA-256 implementation against known digests before wiring up the full vector suite. The normative vectors use the 18-element <tt>trust.intyga.audit.v1</tt> profile layout (§4.5) and therefore produce different digests — passing the examples below is necessary but not sufficient for conformance.</t>
      <t>All digests are lowercase hex.</t>
      <sourcecode type="json">NOTE: '\' line wrapping per RFC 8792

{
  "leafPreimageCase1": {
    "input": [
      "1048576", "2026-07-24T12:00:00.000Z", "ACTION_APPROVED", "SUC\
  CESS",
      "Database drop approved", "{\"target\":\"users\"}", "did:examp\
  le:human:alice",
      "base64-spki", "{\"actionType\":\"db:dropTable\",\"type\":\"di\
  v-intent-verification\",\"v\":1}", "base64-sig", "ES256", "42"
    ],
    "expectedCanonicalJCS": "[\"1048576\",\"2026-07-24T12:00:00.000Z\
  \",\"ACTION_APPROVED\",\"SUCCESS\",\"Database drop approved\",\"{\\
  \\"target\\\":\\\"users\\\"}\",\"did:example:human:alice\",\"base6\
  4-spki\",\"{\\\"actionType\\\":\\\"db:dropTable\\\",\\\"type\\\":\\
  \\"div-intent-verification\\\",\\\"v\\\":1}\",\"base64-sig\",\"ES2\
  56\",\"42\"]",
    "expectedLeafHash": "0d82acddaf17b6fecccffa03e13d97174d0d0aaf698\
  93251dcd3d79ba48385fe"
  },
  "anchorPreimageCase1": {
    "input": [
      "e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e\
  9f8",
      "2026-07-24T23:59:00.000Z",
      "https://transparency.example.org",
      "ES256",
      "1048576",
      "1049600",
      "6ae54abac75d273317f9f369306f2fbe85f2fafc9dd30685dc4f8a375257e\
  f2b"
    ],
    "expectedAnchorPreimage": "[\"e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3\
  d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8\",\"2026-07-24T23:59:00.000Z\",\"ht\
  tps://transparency.example.org\",\"ES256\",\"1048576\",\"1049600\"\
  ,\"6ae54abac75d273317f9f369306f2fbe85f2fafc9dd30685dc4f8a375257ef2\
  b\"]",
    "expectedAnchorDigest": "058a3dbd2ad910d9e06597eab8350b9e4df2e94\
  985dd09f7dfb5144df433ed77"
  },
  "emptyTreeRoot": "dbc1b4c900ffe48d575b5da5c638040125f65db0fe3e2449\
  4b76ea986457d986"
}</sourcecode>
      <t>The <tt>chainHash</tt> in <tt>anchorPreimageCase1</tt> is the genesis §5.4 chain hash of that checkpoint: <tt>chainHash([ "", "e9f8…e9f8", "1048576", "1049600", "1025", "2026-07-24T23:59:00.000Z" ])</tt>.</t>
      <t>
        <tt>emptyTreeRoot</tt> is <tt>SHA-256(0x02)</tt> — the empty-tree domain tag hashed alone, not the hash of an empty byte string. An implementation returning <tt>SHA-256("")</tt> for an empty tree is non-conformant.</t>
      <t>Note that <tt>expectedAnchorDigest</tt> is the value that gets <strong>signed as 32 raw bytes</strong> (§5.2); its hex form above is for comparison only. An implementation that signs the 64-character hex text will match this vector's digest and still fail to interoperate.</t>
    </section>
    <section anchor="s-11" numbered="false">
      <name>11. Reference Verification Algorithm</name>
      <section anchor="s-11-1" numbered="false">
        <name>11.1 Why Bounds Are Mandatory</name>
        <t>Recomputing the root from a leaf and a sibling path is <strong>not</strong> sufficient to establish membership in this tree, and an implementation that stops there is non-conformant.</t>
        <t>§5.1.3 pads an unpaired trailing node by hashing it against itself. Therefore <tt>merkleRoot([a,b,c])</tt> and <tt>merkleRoot([a,b,c,c])</tt> are equal, and a path constructed for the nonexistent index 3 recomputes the 3-leaf root exactly. A verifier with no index and no leaf count has nothing to reject it with, which falsifies §17.1's claim that forging an inclusion path requires a SHA-256 second preimage.</t>
        <t>A verifier MUST therefore be given, and MUST check, the leaf's <tt>index</tt> and the tree's <tt>leafCount</tt>. Three checks follow, and all three are required:</t>
        <ol type="1">
          <li>
            <t>
              <strong>Range</strong> — <tt>0 ≤ index &lt; leafCount</tt>.</t>
          </li>
          <li>
            <t>
              <strong>Path length</strong> — <tt>proof.length</tt> MUST equal <tt>ceil(log2(leafCount))</tt>, or <tt>0</tt> when <tt>leafCount ≤ 1</tt>.</t>
          </li>
          <li>
            <t>
              <strong>Self-pairing</strong> — a node hashed against itself is legitimate <strong>only</strong> at the unpaired end of an odd-sized level. Any other self-paired step MUST be rejected.</t>
          </li>
        </ol>
        <t>Check 3 is what actually closes the forgery. <tt>leafCount</tt> arrives inside the proof, so a prover can inflate it: claiming <tt>leafCount = 4</tt> on a 3-leaf tree puts index 3 in range and makes the path length correct, and the duplicate-last root is identical — so range and length alone still accept it. Padding is observable, however: a sibling equal to the running node anywhere except the unpaired end of an odd level is the signature of an index pointing into padding.</t>
        <t>Sibling side MUST also be derived from the index rather than read from the proof. Letting the prover choose each side freely restores the flexibility the length check just removed.</t>
      </section>
      <section anchor="s-11-2" numbered="false">
        <name>11.2 Reference Implementation</name>
        <sourcecode type="typescript">NOTE: '\' line wrapping per RFC 8792

export interface ProofStep {
  siblingHash: string                 // digest of the sibling node
  siblingPosition: "LEFT" | "RIGHT"   // side the SIBLING sits on, r\
  elative to the running node
}

/** Position of the leaf this proof is for, and how many leaves its \
  tree had. REQUIRED. */
export interface ProofBounds {
  index: number
  leafCount: number
}

export function expectedPathLength(leafCount: number): number {
  return leafCount &lt;= 1 ? 0 : Math.ceil(Math.log2(leafCount))
}

export function verifyMerkleProof(
  leafHex: string,
  proof: ProofStep[],
  rootHex: string,
  bounds: ProofBounds
): boolean {
  const { index, leafCount } = bounds
  if (!Number.isInteger(index) || !Number.isInteger(leafCount)) retu\
  rn false
  if (leafCount &lt; 1 || index &lt; 0 || index &gt;= leafCount) return false
  if (proof.length !== expectedPathLength(leafCount)) return false

  let idx = index
  let levelSize = leafCount
  let node = leafHex
  for (const step of proof) {
    // The side follows from the index; a prover-chosen side is not \
  accepted.
    const expectedSide = idx % 2 === 1 ? "LEFT" : "RIGHT"
    if (step.siblingPosition !== expectedSide) return false

    // Self-pairing is legitimate ONLY at the unpaired end of an odd\
  -sized level.
    const selfPaired = step.siblingHash === node
    const legitimatelyUnpaired = idx === levelSize - 1 &amp;&amp; levelSize \
  % 2 === 1
    if (selfPaired &amp;&amp; !legitimatelyUnpaired) return false

    const siblingIsLeft = step.siblingPosition === "LEFT"
    const left = siblingIsLeft ? hexDecode(step.siblingHash) : hexDe\
  code(node)
    const right = siblingIsLeft ? hexDecode(node) : hexDecode(step.s\
  iblingHash)
    node = hexEncode(sha256(concat(byte(0x01), left, right)))

    idx = Math.floor(idx / 2)
    levelSize = Math.ceil(levelSize / 2)
  }
  return node === rootHex
}</sourcecode>
      </section>
      <section anchor="s-11-3" numbered="false">
        <name>11.3 Two-Hop Inclusion</name>
        <t>An event is committed under a daily root in two hops: event leaf → block root, then block root → daily root. The daily tree's leaves are <tt>hashLeaf(blockRoot)</tt>, so the block root MUST be re-wrapped with the leaf tag (<tt>0x00</tt>) before the second hop.</t>
        <sourcecode type="typescript">NOTE: '\' line wrapping per RFC 8792

export function verifyInclusionProof(proof: InclusionProof, dailyRoo\
  t: string): boolean {
  if (
    !Number.isInteger(proof.leafIndex) ||
    !Number.isInteger(proof.blockLeafCount) ||
    !Number.isInteger(proof.checkpointLeafIndex) ||
    !Number.isInteger(proof.checkpointLeafCount)
  ) return false

  if (!verifyMerkleProof(proof.leaf, proof.blockProof, proof.blockRo\
  ot, {
    index: proof.leafIndex, leafCount: proof.blockLeafCount,
  })) return false

  return verifyMerkleProof(hashLeaf(proof.blockRoot), proof.checkpoi\
  ntProof, dailyRoot, {
    index: proof.checkpointLeafIndex, leafCount: proof.checkpointLea\
  fCount,
  })
}</sourcecode>
        <t>For any verdict a Relying Party acts on, <tt>dailyRoot</tt> MUST be a root the Relying Party obtained <strong>independently</strong> of the proof — one it recorded earlier, or one from the published roots file whose chain it has verified (§5.4.1). It cannot be taken from an external anchor, which holds only a digest committing to the root (§3 Invariant 6). Trusting the root carried inside the proof lets a forged bundle vouch for itself, which defeats the entire construction, so a result computed against <tt>proof.checkpointRoot</tt> MUST NOT be reported as trusted, anchored, or non-repudiable, and MUST NOT on its own produce an overall pass. Running this same function against <tt>proof.checkpointRoot</tt> to establish <tt>commitmentVerified</tt> (§7.1) — internal consistency, when no root was supplied — is permitted, provided the verifier reports the root's provenance alongside the result; the reference implementations expose it as <tt>rootSource: "self-asserted"</tt>, and a supplied root as <tt>rootSource: "caller-supplied"</tt> — never "independent", since the verifier cannot know where the caller got it.</t>
      </section>
    </section>
    <section anchor="s-12" numbered="false">
      <name>12. Algorithm Registry &amp; Migration Rules</name>
      <table>
        <thead>
          <tr>
            <th>Registry Key</th>
            <th>Algorithm / Specification</th>
            <th>Status</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>
              <tt>hashAlgorithm</tt>
            </td>
            <td>SHA-256 (FIPS 180-4)</td>
            <td>MANDATORY</td>
          </tr>
          <tr>
            <td>
              <tt>serialization</tt>
            </td>
            <td>RFC 8785 (JSON Canonicalization Scheme / JCS)</td>
            <td>MANDATORY</td>
          </tr>
          <tr>
            <td>
              <tt>merkleVersion</tt>
            </td>
            <td>1 (Two-tier block + checkpoint hierarchy)</td>
            <td>MANDATORY</td>
          </tr>
          <tr>
            <td>
              <tt>signatureAlgorithms</tt>
            </td>
            <td>
              <tt>ES256</tt> (P-256 + SHA-256), <tt>WEBAUTHN</tt>, <tt>AUTO_APPROVED</tt>
            </td>
            <td>EXTENDED</td>
          </tr>
        </tbody>
      </table>
      <blockquote>
        <t>
          <tt>Ed25519</tt> is an <strong>anchor</strong> signing algorithm (§14), not an event <tt>sigAlg</tt>. It is deliberately absent from the emitted <tt>algorithmRegistry</tt> (<tt>ALGORITHM_REGISTRY</tt>, <tt>packages/db/src/checkpoint.ts</tt>) and from the §6.1 example — the registry above and the emitted value MUST agree.</t>
      </blockquote>
      <section anchor="s-12-1" numbered="false">
        <name>12.1 Hash Algorithm Migration Procedure</name>
        <t>If <tt>hashAlgorithm</tt> is upgraded to <tt>SHA3-256</tt> or <tt>BLAKE3</tt>:</t>
        <ol type="1">
          <li>
            <t>The new algorithm identifier MUST be announced in the <tt>algorithmRegistry</tt>.</t>
          </li>
          <li>
            <t>Historical blocks signed under SHA-256 MUST remain verifiable without re-hashing past checkpoint roots.</t>
          </li>
        </ol>
      </section>
    </section>
    <section anchor="s-13" numbered="false">
      <name>13. Version Migration Rules</name>
      <ol type="1">
        <li>
          <t>
            <strong>Preimage Array Format:</strong> The 12-element Canonical Preimage array MUST NOT be reordered. Future protocol extensions MUST append new fields to the end of the array (index 11+ before <tt>tenantSeq</tt>) accompanied by a <tt>version</tt> bump or Application Profile.</t>
        </li>
        <li>
          <t>
            <strong>Backward Compatibility:</strong> Verifiers encountering a <tt>version: 2</tt> bundle MUST process the fixed core fields <tt>0..10</tt>, and <tt>tenantSeq</tt> at the <strong>final</strong> index, as defined in this specification — "last", not a fixed numeric position, is <tt>tenantSeq</tt>'s only location rule (§4.1/§4.5).</t>
        </li>
        <li>
          <t>
            <strong>Additive Revisions:</strong> A revision that adds only new event types or new signed statements carried in the existing slots — as version 1.1 does (§4.7) — keeps the bundle <tt>version</tt> at <tt>"1.0"</tt> and the profile unchanged, so earlier verifiers keep verifying commitment and content. Such a revision MUST NOT change the meaning of an existing event type or of existing slot contents.</t>
        </li>
      </ol>
      <t>The §5.2 signed input binds the checkpoint's position (seq range and chain hash) since the September 2026 pre-release correction. That makes a genuine anchor for another checkpoint distinguishable from a conflicting one by its own signed fields, which a future revision could use to let bundle-carried anchors establish divergence; this revision does not, because a bundle is still the producer's selection of anchors.</t>
    </section>
    <section anchor="s-14" numbered="false">
      <name>14. Key Lifecycle Management</name>
      <ul>
        <li>
          <t>
            <strong>Anchor Signing Keys:</strong> Anchor providers MUST sign daily checkpoint objects with a <strong>signature</strong> algorithm — ECDSA P-256 (<tt>ES256</tt>), Ed25519, or RSA-PSS (SHA-256, 32-byte salt, modulus of at least 2048 bits; §5.2). RSA-OAEP MUST NOT be used: it is an encryption padding, not a signature scheme.</t>
        </li>
        <li>
          <t>
            <strong>Rotation &amp; Revocation:</strong> Anchor signatures MUST include a <tt>keyId</tt> and timestamp. Revoked keys MUST NOT invalidate historic daily roots signed prior to the key's revocation timestamp.</t>
        </li>
      </ul>
    </section>
    <section anchor="s-15" numbered="false">
      <name>15. Privacy and Retention Model</name>
      <ul>
        <li>
          <t>
            <strong>Redaction Policy:</strong> When a tenant triggers data retention or GDPR right-to-be-forgotten deletion, private fields MAY be purged from storage. The purgeable set is all five of <tt>detail</tt>, <tt>metadata</tt>, <tt>signedPayload</tt>, <tt>signature</tt> and <tt>signerPublicKey</tt> — matching the <tt>removedFields</tt> in the §6.3 example and what the retention sweep actually writes (<tt>packages/db/src/evidence.ts</tt>). The proof material (<tt>signature</tt>, <tt>signerPublicKey</tt>) is purgeable because it is personal data about the approver; the retained <tt>leaf</tt> is what preserves the commitment.</t>
        </li>
        <li>
          <t>
            <strong>Commitment Preservation:</strong> A <tt>COMMITMENT_ONLY</tt> redaction MUST preserve, and a compliant bundle MUST carry, all three of:</t>
          <ol type="1">
            <li>
              <t>
                <strong>
                  <tt>leaf</tt>
                </strong> — the original <tt>leafHash</tt> (the domain-separated digest of the pre-redaction Canonical Preimage). It MUST NOT be recomputed or altered; it is what still verifies against the anchored Merkle root.</t>
            </li>
            <li>
              <t>
                <strong>Commitment metadata</strong> — the non-sensitive positional fields needed to keep the entry meaningful and gapless: <tt>seq</tt>, <tt>tenantSeq</tt>, <tt>createdAt</tt>, <tt>type</tt>, <tt>outcome</tt>, and the inclusion <tt>proof</tt>.</t>
            </li>
            <li>
              <t>
                <strong>A redaction proof</strong> — a <tt>redaction</tt> object recording <tt>mode: "COMMITMENT_ONLY"</tt>, <tt>removedFields</tt>, <tt>redactedAt</tt>, and <tt>reason</tt>, so an auditor can see <em>that</em> content was purged and <em>why</em>, distinguishing lawful redaction from tampering.</t>
            </li>
          </ol>
        </li>
        <li>
          <t>A verifier encountering a <tt>COMMITMENT_ONLY</tt> entry MUST verify inclusion against the retained <tt>leaf</tt> (yielding <tt>commitmentVerified</tt>) and MUST NOT attempt <tt>contentVerified</tt>/<tt>signatureVerified</tt> (the preimage is intentionally absent).</t>
        </li>
      </ul>
    </section>
    <section anchor="s-16" numbered="false">
      <name>16. Threat Model</name>
      <t>What an attacker gains by compromising each component:</t>
      <table>
        <thead>
          <tr>
            <th>Compromised Component</th>
            <th>Consequence &amp; Mitigation</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>
              <strong>Audited Application / DB</strong>
            </td>
            <td>Can delete local DB rows. <strong>Mitigation:</strong> Deleting rows breaks <tt>tenantSeq</tt> gaplessness and leaves un-matched leaf hashes under the public anchor.</td>
          </tr>
          <tr>
            <td>
              <strong>Witness Platform Server</strong>
            </td>
            <td>Can withhold evidence exports (denial of service). <strong>Mitigation:</strong> Cannot rewrite past events without breaking published daily roots.</td>
          </tr>
          <tr>
            <td>
              <strong>Single Anchor Provider</strong>
            </td>
            <td>Could sign an alternate root. <strong>Mitigation:</strong> Multi-anchor verification (<tt>anchors: [...]</tt>) detects anchor divergence.</td>
          </tr>
          <tr>
            <td>
              <strong>Executing Party</strong>
            </td>
            <td>Can claim dispatch, write misleading Dispatch Records and withhold contrary evidence. <strong>Mitigation:</strong> It cannot author an Execution Evidence Statement (it lacks the source's key), so it cannot produce <tt>confirmed</tt>; an early Dispatch Record with another hash cannot unbind genuine statements and is reported as a conflict, and a far-out deadline is cut to the verifier's window (§4.7.3); a statement withheld from inside the exported range shows as a gap in the source's <tt>sequence</tt>, one after the last committed statement does not (§4.7.6).</td>
          </tr>
          <tr>
            <td>
              <strong>Evidence Source</strong>
            </td>
            <td>Can sign a false observation. <strong>Mitigation:</strong> None within DEWP. Trust sources whose observations are independent physical measurements, prefer several, and note that one trusted <tt>contradicted</tt> outweighs any number of <tt>confirmed</tt> (§4.7.4).</td>
          </tr>
        </tbody>
      </table>
    </section>
    <section anchor="s-17" numbered="false">
      <name>17. Formal Security Properties</name>
      <t>DEWP's guarantees hold under standard cryptographic assumptions — collision resistance and second-preimage resistance of SHA-256 (FIPS 180-4), and existential unforgeability of the chosen signature scheme. They are conditional on those assumptions, not unconditional:</t>
      <t>Each property below is referenced elsewhere as <strong>§17.N</strong>.</t>
      <ol type="1">
        <li>
          <t>
            <strong>§17.1 — Committed-Event Tamper-Evidence:</strong> Producing a distinct event that collides to an already-committed <tt>leafHash</tt>, or forging an inclusion path to a published root, requires a SHA-256 collision/second-preimage — assumed infeasible (~2⁻²⁵⁶ for a random target under the random-oracle heuristic). <strong>This property is conditional on the verifier enforcing proof bounds (§11.1).</strong> Against an unbounded path check it does not hold: §5.1.3 padding lets a path to a nonexistent index recompute the real root with no second-preimage at all.</t>
        </li>
        <li>
          <t>
            <strong>§17.2 — Committed-Omission Detectability:</strong> Deleting a <strong>committed</strong> event whose <tt>tenantSeq_N</tt> lies between retained <tt>tenantSeq</tt> values yields a detectable gap. This is a structural property of the contiguous counter over the committed set; it says nothing about events never committed (§3 Invariant 4). <strong>The property is conditional on the verifier reading the leaf-bound counter (§7.2).</strong> Against a check run over the unsigned display copy it does not hold at all: the omitted range is closed by renumbering, with no hash broken.</t>
        </li>
        <li>
          <t>
            <strong>§17.3 — Cross-Domain / Subtree-Substitution Resistance:</strong> Distinct domain tags (<tt>0x00</tt> leaf, <tt>0x01</tt> node, <tt>0x02</tt> empty root, <tt>0x03</tt> anchor, <tt>0x04</tt> checkpoint chain) make a leaf preimage, an interior-node input, an anchor-signing input, and a chain-link input mutually non-interchangeable, so a subtree root cannot be presented as a leaf (nor a tree hash as an anchor signature input, nor a chain hash as any of them) except via a SHA-256 second-preimage. <strong>This property holds where the verifier hashes the leaf from its preimage.</strong> For a <tt>COMMITMENT_ONLY</tt> entry the leaf is taken from the proof, and the proof also supplies <tt>blockLeafCount</tt>, so an interior node of a larger block can be offered as a leaf of a claimed smaller one. A verifier MUST therefore bind the leaf counts where it can: proofs into one block or checkpoint in a bundle MUST agree on them, and, where a checkpoint <tt>entryCount</tt> is known (the sum of its blocks' leaf counts), <tt>checkpointLeafCount ≤ entryCount</tt>, <tt>blockLeafCount + checkpointLeafCount − 1 ≤ entryCount</tt>, and <tt>blockLeafCount = entryCount</tt> when the checkpoint has a single block. A redacted single proof verified with no checkpoint record remains exposed to this and is reported only as <tt>COMMITMENT_VERIFIED</tt>.</t>
        </li>
      </ol>
    </section>
    <section numbered="false">
      <name>Appendix A. Evidence Criteria for Physical Actions (Non-Normative)</name>
      <t>What a statement should establish depends on the action. These criteria, from control-system practice, illustrate the bar for an independent observation:</t>
      <ul>
        <li>
          <t>
            <strong>Separation from the command path.</strong> The party that sent a command should not be able to write the evidence about its effect. Evidence produced by the same controller, service or credential that issued the command is that party's own account.</t>
        </li>
        <li>
          <t>
            <strong>Cross-checked state, not reported intent.</strong> For a circuit breaker, position from the auxiliary contacts (52a/52b) cross-checked against independently measured current or voltage within a defined window. Account for prior conditions: if no current flowed before, zero current afterwards proves nothing, and the observation should record the prior state alongside the new one.</t>
        </li>
        <li>
          <t>
            <strong>What actually runs, not what was installed.</strong> For firmware, the digest of the image that actually booted, from measured boot or remote attestation (RFC 9334 <xref target="RFC9334" />) — never a hash the updated application reports about itself.</t>
        </li>
        <li>
          <t>
            <strong>Missing, stale or contradictory evidence is not success — and the two are not the same.</strong> A source should sign <tt>contradicted</tt> when its measurements disagree with the approved effect, and sign nothing when it cannot decide. Neither outcome defaults to success, but they call for different responses: <tt>unconfirmed</tt> (the source could not tell — often a lost or stale sensor) calls for investigation, <tt>contradicted</tt> (a trusted source saw the action not take effect) for escalation.</t>
        </li>
        <li>
          <t>
            <strong>A compromised source limits the claim.</strong> If the source itself may be compromised, claim only what independent physical measurements support.</t>
        </li>
        <li>
          <t>
            <strong>Read-back is not independence.</strong> Evidence read back through the controller under test — the PLC that executed the command reporting its own outputs, the updated device reporting its own firmware hash — is that controller's account, not an observation of it.</t>
        </li>
        <li>
          <t>
            <strong>Independent means separately sensed.</strong> Sources that share a sensing path (one transducer, one I/O card, one network tap) or one time source are not independent of each other; "prefer several" means several that fail separately, not several readings of one signal.</t>
        </li>
        <li>
          <t>
            <strong>Observe after settling.</strong> The evidence window must open after the process has settled: a breaker takes tens of milliseconds, a reboot minutes. A source that samples before settling sees the old state and signs <tt>contradicted</tt> for an action that then succeeded; the executing party sizes the window for the process, and the source signs <tt>sampledAt</tt> so a verifier can tell when it looked. This is also why the pre-dispatch allowance is small and covers clock error only (§4.7.3 step 6): a measurement from just before dispatch, of a state that was already in place, confirms a no-op.</t>
        </li>
        <li>
          <t>
            <strong>One-way from source to log.</strong> Where possible the path from an evidence source to the log should be one-way (a data diode, a push-only forwarder), so that neither the party being observed nor the log can reach back into the source.</t>
        </li>
      </ul>
      <t>These criteria are offered as review criteria, not as field-validated results.</t>
    </section>
    <section anchor="iana" numbered="false">
      <name>18. IANA Considerations</name>
      <t>This document requests no IANA actions. The registries and application profiles described here are specification-local, not IANA registries.</t>
    </section>
  </middle>
  <back>
    <references>
      <name>References</name>
      <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 fullname="S. Bradner" initials="S." surname="Bradner" />
            <date month="March" year="1997" />
          </front>
          <seriesInfo name="BCP" value="14" />
          <seriesInfo name="RFC" value="2119" />
          <seriesInfo name="DOI" value="10.17487/RFC2119" />
        </reference>
        <reference anchor="RFC8174" target="https://www.rfc-editor.org/info/rfc8174">
          <front>
            <title>Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words</title>
            <author fullname="B. Leiba" initials="B." surname="Leiba" />
            <date month="May" year="2017" />
          </front>
          <seriesInfo name="BCP" value="14" />
          <seriesInfo name="RFC" value="8174" />
          <seriesInfo name="DOI" value="10.17487/RFC8174" />
        </reference>
        <reference anchor="RFC8785" target="https://www.rfc-editor.org/info/rfc8785">
          <front>
            <title>JSON Canonicalization Scheme (JCS)</title>
            <author fullname="A. Rundgren" initials="A." surname="Rundgren" />
            <author fullname="B. Jordan" initials="B." surname="Jordan" />
            <author fullname="S. Erdtman" initials="S." surname="Erdtman" />
            <date month="June" year="2020" />
          </front>
          <seriesInfo name="RFC" value="8785" />
          <seriesInfo name="DOI" value="10.17487/RFC8785" />
        </reference>
        <reference anchor="RFC6962" target="https://www.rfc-editor.org/info/rfc6962">
          <front>
            <title>Certificate Transparency</title>
            <author fullname="B. Laurie" initials="B." surname="Laurie" />
            <author fullname="A. Langley" initials="A." surname="Langley" />
            <author fullname="E. Kasper" initials="E." surname="Kasper" />
            <date month="June" year="2013" />
          </front>
          <seriesInfo name="RFC" value="6962" />
          <seriesInfo name="DOI" value="10.17487/RFC6962" />
          <annotation>§2.1 — Certificate Transparency. Cited specifically for the Merkle audit-path leaf/node domain-separation technique (the <tt>0x00</tt>/<tt>0x01</tt> prefixing) that DEWP adopts and extends; not cited as a general second-preimage authority.</annotation>
        </reference>
        <reference anchor="RFC9562" target="https://www.rfc-editor.org/info/rfc9562">
          <front>
            <title>Universally Unique IDentifiers (UUIDs)</title>
            <author fullname="K. Davis" initials="K." surname="Davis" />
            <author fullname="B. Peabody" initials="B." surname="Peabody" />
            <author fullname="P. Leach" initials="P." surname="Leach" />
            <date month="May" year="2024" />
          </front>
          <seriesInfo name="RFC" value="9562" />
          <seriesInfo name="DOI" value="10.17487/RFC9562" />
          <annotation>Universally Unique Identifiers (UUIDs), including UUIDv7. Cited for the optional time-ordered event <tt>id</tt>, not for the numeric <tt>seq</tt> counter.</annotation>
        </reference>
        <reference anchor="FIPS180-4" target="https://doi.org/10.6028/NIST.FIPS.180-4">
          <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 PUB" value="180-4" />
        </reference>
        <reference anchor="RFC8017" target="https://www.rfc-editor.org/info/rfc8017">
          <front>
            <title>PKCS #1: RSA Cryptography Specifications Version 2.2</title>
            <author fullname="K. Moriarty" initials="K." role="editor" surname="Moriarty" />
            <author fullname="B. Kaliski" initials="B." surname="Kaliski" />
            <author fullname="J. Jonsson" initials="J." surname="Jonsson" />
            <author fullname="A. Rusch" initials="A." surname="Rusch" />
            <date month="November" year="2016" />
          </front>
          <seriesInfo name="RFC" value="8017" />
          <seriesInfo name="DOI" value="10.17487/RFC8017" />
        </reference>
        <reference anchor="Artifacts" target="https://www.intyga.com/specs/v1.2.0/CHECKSUMS.sha256">
          <front>
            <title>DIV and DEWP version 1.2.0 schemas and conformance vectors</title>
            <author>
              <organization>Janbjer Technologies AB</organization>
            </author>
            <date year="2026" month="October" day="19" />
          </front>
        </reference>
        <reference anchor="DIV" target="https://datatracker.ietf.org/doc/html/draft-janbjer-div-01">
          <front>
            <title>Deterministic Intent Verification (DIV) Protocol Specification</title>
            <author fullname="Christian Janbjer" initials="C." surname="Janbjer" />
            <date year="2026" month="October" />
          </front>
          <seriesInfo name="Internet-Draft" value="draft-janbjer-div-01" />
        </reference>
        <reference anchor="WebAuthn" target="https://www.w3.org/TR/2026/REC-webauthn-3-20260825/">
          <front>
            <title>Web Authentication: An API for accessing Public Key Credentials - Level 3</title>
            <author>
              <organization>W3C</organization>
            </author>
            <date year="2026" month="August" day="25" />
          </front>
        </reference>
      </references>
      <references>
        <name>Informative References</name>
        <reference anchor="RFC9334" target="https://www.rfc-editor.org/info/rfc9334">
          <front>
            <title>Remote ATtestation procedureS (RATS) Architecture</title>
            <author fullname="H. Birkholz" initials="H." surname="Birkholz" />
            <author fullname="D. Thaler" initials="D." surname="Thaler" />
            <author fullname="M. Richardson" initials="M." surname="Richardson" />
            <author fullname="N. Smith" initials="N." surname="Smith" />
            <author fullname="W. Pan" initials="W." surname="Pan" />
            <date month="January" year="2023" />
          </front>
          <seriesInfo name="RFC" value="9334" />
          <seriesInfo name="DOI" value="10.17487/RFC9334" />
        </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 fullname="C. Adams" initials="C." surname="Adams" />
            <author fullname="P. Cain" initials="P." surname="Cain" />
            <author fullname="D. Pinkas" initials="D." surname="Pinkas" />
            <author fullname="R. Zuccherato" initials="R." surname="Zuccherato" />
            <date month="August" year="2001" />
          </front>
          <seriesInfo name="RFC" value="3161" />
          <seriesInfo name="DOI" value="10.17487/RFC3161" />
        </reference>
        <reference anchor="I-D.williams-intent-token" target="https://datatracker.ietf.org/doc/html/draft-williams-intent-token-02">
          <front>
            <title>The Intent Token: A Cryptographic Authorization Primitive for Autonomous Agents</title>
            <author fullname="Jeffrey Williams" initials="J." surname="Williams">
              <organization>Independent</organization>
            </author>
            <date day="4" month="September" year="2026" />
          </front>
          <seriesInfo name="Internet-Draft" value="draft-williams-intent-token-02" />
          <annotation>Describes a JWT-based pre-execution authorization token paired with a hash-chained audit structure ("Fractal Crypto-Temporal Graph"); DEWP differs by providing two-hop Merkle inclusion proofs and independently-anchored quorum verification (§3, §5) rather than a single unanchored hash chain.</annotation>
        </reference>
        <reference anchor="RFC8792" target="https://www.rfc-editor.org/info/rfc8792">
          <front>
            <title>Handling Long Lines in Content of Internet-Drafts and RFCs</title>
            <author fullname="K. Watsen" initials="K." surname="Watsen" />
            <author fullname="E. Auerswald" initials="E." surname="Auerswald" />
            <author fullname="A. Farrel" initials="A." surname="Farrel" />
            <author fullname="Q. Wu" initials="Q." surname="Wu" />
            <date month="June" year="2020" />
          </front>
          <seriesInfo name="RFC" value="8792" />
          <seriesInfo name="DOI" value="10.17487/RFC8792" />
        </reference>
      </references>
    </references>
  </back>
</rfc>
