<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE rfc [
  <!ENTITY nbsp "&#160;">
]>
<rfc xmlns:xi="http://www.w3.org/2001/XInclude"
     docName="draft-cowles-ward-00"
     ipr="trust200902"
     submissionType="independent"
     category="exp"

     xml:lang="en"
     version="3">

  <front>
    <title abbrev="WARD Protocol">Write-once Append-only Receipt Digests (WARD): A Content-Free Hash-Chain Witnessing Protocol for Agentic AI Systems</title>

    <seriesInfo name="Internet-Draft" value="draft-cowles-ward-00"/>

    <author fullname="Adam Cowles" initials="A." surname="Cowles">
      <organization>Quox Ltd</organization>
      <address>
        <postal>
          <city>London</city>
          <country>United Kingdom</country>
        </postal>
        <email>adam@quox.ai</email>
        <uri>https://quox.ai</uri>
      </address>
    </author>

    <date day="31" month="August" year="2026"/>

    <area>Security</area>
    <workgroup>Independent Submission</workgroup>

    <keyword>AI agents</keyword>
    <keyword>hash chain</keyword>
    <keyword>witnessing</keyword>
    <keyword>tamper evidence</keyword>
    <keyword>content-free</keyword>
    <keyword>receipt digests</keyword>
    <keyword>audit trail</keyword>

    <abstract>
      <t>
        Write-once Append-only Receipt Digests (WARD) defines a minimal,
        interoperable protocol for producing tamper-evident, content-free
        hash-chain witnesses over events from agentic AI systems.  WARD
        observes events from companion protocols (Agent Envelope Exchange
        (AEE) messages, Agent Orchestration Control Layers (AOCL) decisions,
        and Verifiable Operations Ledger and Trace (VOLT) evidence records)
        and produces cryptographically linked receipts that prove specific
        events existed at specific points in time, without storing any
        event content.
      </t>
      <t>
        WARD entries record only source identifiers and payload hashes,
        never raw payloads, secrets, or personally identifiable information.
        Entries are linked via SHA-256 hash chains anchored by deterministic
        genesis hashes.  Periodic checkpoints called "tips" may be signed
        with Ed25519 and published to external append-only stores (sinks)
        for independent verification.  A meta-chain pattern allows
        witnessing tips from multiple sub-chains, providing deployment-wide
        integrity from a single verification point.
      </t>
      <t>
        The protocol is designed as a passive observer: witnessing never
        blocks, delays, or modifies the source event pipeline.  WARD
        failures are logged but never disrupt AEE transport, AOCL
        decisions, or VOLT recording.  This fire-and-forget integration
        model ensures that witnessing adds tamper-evidence guarantees
        without introducing operational risk.
      </t>
    </abstract>
  </front>

  <middle>

    <!-- ============================================================ -->
    <!-- Section 1: Introduction                                       -->
    <!-- ============================================================ -->
    <section anchor="introduction">
      <name>Introduction</name>
      <t>
        Evidence systems for autonomous AI agents need an independent
        witness.  Even a well-designed audit trail, such as the hash-chained
        traces produced by the VOLT protocol <xref target="VOLT"/>, can be
        rewritten if an attacker controls the host and no external
        checkpoints exist.  Operators need a way to answer questions such as:
      </t>
      <ul>
        <li>"Was this AEE envelope witnessed before it was disputed?"</li>
        <li>"Does the AOCL decision hash match what was recorded at the time?"</li>
        <li>"Has anyone tampered with the VOLT evidence bundle since it was created?"</li>
        <li>"Can we prove chain state at a point in time to an external auditor?"</li>
      </ul>
      <t>
        WARD answers these questions without storing a single byte of event
        content.  It maintains a separate, content-free hash chain that
        observes events from the Agent Envelope Exchange (AEE)
        <xref target="AEE"/>, Agent Orchestration Control Layers (AOCL)
        <xref target="AOCL"/>, and VOLT <xref target="VOLT"/> protocols,
        producing tamper-evident receipts that can be verified independently.
      </t>
      <t>
        The key design principle is separation of concerns.  AEE defines
        how agents communicate.  AOCL defines how they are controlled.
        VOLT proves what happened.  WARD proves that the proof has not been
        tampered with.  Each protocol operates independently; WARD adds a
        witnessing layer without requiring changes to the protocols it
        observes.
      </t>
      <t>
        WARD is intentionally minimal.  A conformant witness implementation
        requires only SHA-256 hashing, append-only storage, and the ability
        to receive (source_kind, source_id, payload_hash) tuples.  Optional
        features (Ed25519 tip signing, external sink publication, and
        meta-chains) provide progressively stronger guarantees for
        deployments that require them.
      </t>
    </section>

    <!-- ============================================================ -->
    <!-- Section 2: Conventions and Definitions                        -->
    <!-- ============================================================ -->
    <section anchor="conventions">
      <name>Conventions and Definitions</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 3: Terminology                                        -->
    <!-- ============================================================ -->
    <section anchor="terminology">
      <name>Terminology</name>
      <dl>
        <dt>Witness</dt>
        <dd>The act of observing a source event and recording a
            content-free receipt.</dd>

        <dt>Ward Entry</dt>
        <dd>A single receipt in the chain, referencing a source event by
            kind, identifier, and payload hash, but never storing the
            source content.</dd>

        <dt>Ward Chain</dt>
        <dd>An append-only sequence of ward entries linked by
            chain hashes.</dd>

        <dt>Chain Hash</dt>
        <dd>A deterministic SHA-256 hash linking each entry to its
            predecessor, computed over the canonical JSON of the
            entry's hash body.</dd>

        <dt>Genesis Hash</dt>
        <dd>A deterministic SHA-256 hash derived from the chain
            identifier, anchoring the chain's origin.</dd>

        <dt>Tip</dt>
        <dd>A periodic checkpoint summarizing chain state at a given
            sequence number.  Tips may be signed and published to
            external sinks.</dd>

        <dt>Meta-chain</dt>
        <dd>A WARD chain that witnesses tips from other WARD chains,
            creating a chain-of-chains.</dd>

        <dt>Source Event</dt>
        <dd>The AEE envelope, AOCL decision, VOLT transition, or
            external event being witnessed.</dd>

        <dt>Payload Hash</dt>
        <dd>The SHA-256 digest of the source event's canonical content,
            computed by the caller and passed to WARD.</dd>

        <dt>Sink</dt>
        <dd>An external append-only store where tips are published
            (e.g., Gitea signed tags, S3 Object Lock).</dd>

        <dt>Source Kind</dt>
        <dd>An enumerated value identifying the type of source event:
            AEE, AOCL, VOLT, WARD, or EXTERNAL.</dd>
      </dl>
    </section>

    <!-- ============================================================ -->
    <!-- Section 4: Design Constraints                                 -->
    <!-- ============================================================ -->
    <section anchor="design-constraints">
      <name>Design Constraints</name>

      <section anchor="content-free">
        <name>Content-Free by Design</name>
        <t>
          Ward entries MUST NOT store source event content, payloads, or
          attachments.  Ward entries MUST reference source events only by
          identifier and payload hash.  This constraint is non-negotiable
          and constitutes WARD's defining property.  An implementation
          that stores event content is not a WARD implementation.
        </t>
      </section>

      <section anchor="one-way-observation">
        <name>One-Way Observation</name>
        <t>
          WARD MUST be a passive observer.  Witnessing MUST NOT block,
          delay, or modify the source event pipeline.  WARD failures
          MUST NOT disrupt AEE transport, AOCL decisions, or VOLT
          recording.  This fire-and-forget integration model ensures
          that witnessing adds guarantees without introducing
          operational risk.
        </t>
      </section>

      <section anchor="minimal-extensible">
        <name>Minimal Schema, Extensible Evolution</name>
        <t>
          Every ward entry MUST include a <tt>ward_version</tt> field.
          Unknown fields MUST be ignored by verifiers to ensure forward
          compatibility.  Breaking changes MUST increment
          <tt>ward_version</tt>; while the major version is 0, a minor
          increment MAY be a breaking change (the standard pre-1.0
          convention).  The <tt>ward_version</tt> value stored in each
          entry selects the chain hash scheme a verifier MUST apply to
          that entry (see <xref target="chain-hash"/>).
        </t>
      </section>

      <section anchor="deterministic">
        <name>Deterministic and Reproducible</name>
        <t>
          All hashes MUST be deterministically reproducible from the same
          inputs.  No randomness is permitted in hash computation; only
          identifier generation may use random sources.
        </t>
      </section>
    </section>

    <!-- ============================================================ -->
    <!-- Section 5: Transport and Encoding                             -->
    <!-- ============================================================ -->
    <section anchor="transport-encoding">
      <name>Transport and Encoding</name>
      <t>
        Ward entries are JSON objects <xref target="RFC8259"/>.  All JSON
        MUST be encoded as UTF-8 <xref target="RFC3629"/>.  The storage
        format is implementation-defined; common choices include SQLite
        rows, PostgreSQL tables, and NDJSON files.
      </t>
      <t>
        WARD does not define a transport protocol.  Implementations
        receive source event references via implementation-specific
        mechanisms (function calls, IPC, HTTP endpoints, message queues).
        The protocol specifies only the data model, hash computation,
        and verification algorithm.
      </t>
    </section>

    <!-- ============================================================ -->
    <!-- Section 6: Canonicalization                                    -->
    <!-- ============================================================ -->
    <section anchor="canonicalization">
      <name>Canonicalization</name>
      <t>
        This section normatively defines canonicalization profile
        WARD-CANON-1, used for every hash computed by this
        specification.  It aligns with the VOLT protocol's
        canonicalization <xref target="VOLT"/>, but this document is
        authoritative for WARD.  An implementation MUST produce
        canonical byte representations as follows:
      </t>
      <ol>
        <li>Serialize as JSON <xref target="RFC8259"/> with UTF-8
            encoding <xref target="RFC3629"/> and no insignificant
            whitespace, using minimal string escaping: escape only the
            characters JSON requires (quotation mark, reverse solidus,
            and control characters U+0000 to U+001F, using the
            two-character forms where defined and otherwise
            <tt>\u00xx</tt> with lowercase hexadecimal digits).  Every
            other character, including <tt>/</tt>, MUST appear
            literally as UTF-8; optional escapes such as <tt>\/</tt>
            are non-canonical and MUST NOT be produced.  This makes the
            serialization of any given string unique.</li>
        <li>Object keys MUST be sorted lexicographically by their UTF-8
            byte sequences, compared byte by byte as unsigned values, at
            every nesting level.  This differs from UTF-16 code-unit
            order for supplementary-plane characters (U+E000 sorts
            before U+10000 in UTF-8 byte order); sorting by a language's
            native string comparison is not conformant unless it matches
            UTF-8 byte order.</li>
        <li>Strings MUST be well-formed Unicode (no unpaired
            surrogates) and MUST already be in Normalization Form C.
            Canonicalization MUST reject, not repair, a string that is
            not well-formed or not in NFC, so the stored form and the
            hashed form are byte-identical and two visually identical
            but differently-normalized identifiers cannot silently hash
            to the same commitment.</li>
        <li>Integers MUST be within the range +/-(2^53 - 1) and
            serialized in exact decimal form: no leading zeros, no plus
            sign, no fraction part, no exponent.  Values outside that
            range, and non-integer numbers, MUST be rejected wherever
            this specification calls for an integer.</li>
        <li>A parser feeding canonicalization or verification MUST
            reject JSON objects containing duplicate member names;
            last-wins or first-wins acceptance would allow one byte
            sequence to carry two different assertions.</li>
      </ol>
      <t>
        Canonicalization is used when computing payload hashes over
        source events and when computing chain hashes over entry fields.
        WARD's own hash bodies contain no non-integer numbers, and this
        profile deliberately defines no serialization for them: a caller
        computing a payload hash over a source event containing
        non-integer numbers MUST define and document its own canonical
        number form, and WARD treats the resulting payload hash as an
        opaque commitment either way.
        Future revisions that change any of these rules will define a
        new named profile; <tt>ward_version</tt> binds each entry to the
        profile in force when it was written.
      </t>
    </section>

    <!-- ============================================================ -->
    <!-- Section 7: Cryptographic Primitives                           -->
    <!-- ============================================================ -->
    <section anchor="crypto-primitives">
      <name>Cryptographic Primitives</name>

      <section anchor="hash-algorithm">
        <name>Hash Algorithm</name>
        <t>
          All hashing in WARD v0.2 MUST use SHA-256
          <xref target="RFC6234"/> <xref target="FIPS180-4"/>.
        </t>
      </section>

      <section anchor="hash-encoding">
        <name>Hash Encoding</name>
        <t>
          Hash values MUST be encoded as lowercase hexadecimal strings
          of 64 characters (256 bits).
        </t>
      </section>

      <section anchor="genesis-hash">
        <name>Genesis Hash</name>
        <t>
          The genesis hash anchors a chain's origin deterministically:
        </t>
        <artwork><![CDATA[
genesis_hash = SHA-256("WARD-GENESIS|" + chain_id)
]]></artwork>
        <t>
          The input is the literal UTF-8 string <tt>WARD-GENESIS|</tt>
          concatenated with the <tt>chain_id</tt>.  The genesis hash
          serves as <tt>prev_chain_hash</tt> for the first entry
          (seq = 1).  Unlike the chain hash, this construction is not
          exposed to delimiter ambiguity: the input has exactly one
          variable field after a fixed literal prefix, so no two
          distinct inputs can collide by boundary confusion.
        </t>
      </section>

      <section anchor="chain-hash">
        <name>Chain Hash</name>
        <t>
          Each entry's <tt>chain_hash</tt> links it to its predecessor.
          It is computed over the canonical JSON (see
          <xref target="canonicalization"/>) of the entry's hash body:
          the ten required fields of the entry schema excluding
          <tt>chain_hash</tt> itself.
        </t>
        <artwork><![CDATA[
hash_body  = { chain_id, issuer_id, payload_hash,
               prev_chain_hash, seq, source_id, source_kind,
               ward_entry_id, ward_version, witnessed_at }

chain_hash = SHA-256(canonical_json(hash_body))
]]></artwork>
        <t>
          The hash body MUST contain exactly these ten members: no
          extras, none absent, none null.  The <tt>seq</tt> value is
          hashed as a JSON integer, not a string.  Optional fields
          (<tt>source_ts</tt>, <tt>tags</tt>, <tt>sig</tt>) MUST NOT be
          included in the hash body; they are annotations, not
          commitments, and are not tamper-protected by the chain.
        </t>
        <t>
          Canonical JSON is used rather than delimiter concatenation
          because identifier fields carry no character-set restriction:
          with concatenation, a crafted identifier containing the
          delimiter character can make two distinct field tuples produce
          an identical hash input, so a single chain hash would commit
          to an ambiguous statement.  JSON string escaping makes field
          boundaries unambiguous for every possible input.  A legacy
          concatenation scheme used by ward_version 0.1 deployments is
          preserved for verification only in
          <xref target="legacy-chain-hash"/>.
        </t>
        <t>
          Because <tt>ward_version</tt> is part of the hash body, the
          scheme an entry declares is itself tamper-protected: an
          attacker cannot downgrade an entry to the legacy scheme
          without breaking its hash.
        </t>
      </section>

      <section anchor="signatures">
        <name>Signatures</name>
        <t>
          Signatures are OPTIONAL in WARD v0.2 but the schema reserves
          fields for them.  If used:
        </t>
        <ul>
          <li>The signature algorithm SHOULD be Ed25519
              <xref target="RFC8032"/>.</li>
          <li>Public key identifiers SHOULD be stable (DID or key
              fingerprint).</li>
          <li>Tips SHOULD be signed; individual entries MAY be signed.</li>
          <li>Signing every entry is permitted but not expected, as it
              is computationally expensive for most deployments.</li>
        </ul>
        <t>
          When signing a tip, the signature is computed over the raw
          UTF-8 bytes of the <tt>tip_chain_hash</tt> hexadecimal string.
        </t>
        <t>
          Key resolution and trust are deployment-defined in this
          version.  This specification does not define a key-resolution
          mechanism, trust anchor, or issuer-authorization rule for
          <tt>key_id</tt>: a verified signature therefore proves
          possession of whichever key the deployment resolved for that
          identifier, and nothing more.  Deployments MUST document how
          <tt>key_id</tt> values resolve to keys and which keys are
          authorized to sign for a chain's issuer, and auditors SHOULD
          treat signature-verification counts as meaningful only
          relative to that documented policy.
        </t>
      </section>
    </section>

    <!-- ============================================================ -->
    <!-- Section 8: Identifiers and Time                               -->
    <!-- ============================================================ -->
    <section anchor="identifiers-time">
      <name>Identifiers and Time</name>

      <section anchor="identifiers">
        <name>Identifiers</name>
        <t>
          The <tt>ward_entry_id</tt> MUST be unique within a chain.
          The <tt>chain_id</tt> MUST be unique within a deployment.
          Identifiers SHOULD be UUIDv4 <xref target="RFC9562"/> or ULID.
        </t>
        <t>
          Chain identifiers follow a scoping convention:
        </t>
        <ul>
          <li>Per organisation/environment:
              <tt>ward:org/&lt;org&gt;/env/&lt;env&gt;</tt></li>
          <li>Meta-chain:
              <tt>ward:meta/&lt;deployment&gt;</tt></li>
        </ul>
        <t>
          Examples: <tt>ward:org/quox/env/production</tt>,
          <tt>ward:org/quox/env/staging</tt>,
          <tt>ward:meta/quox-global</tt>.
        </t>
        <t>
          A <tt>chain_id</tt> MUST NOT contain the sequence
          <tt>::</tt>, which is reserved as the separator in meta-chain
          <tt>source_id</tt> values (<xref target="meta-chain-source-id"/>).
          Identifiers SHOULD be limited to printable ASCII without
          whitespace; with canonical-JSON hashing this is hygiene rather
          than a hashing-safety requirement.
        </t>
      </section>

      <section anchor="timestamps">
        <name>Timestamps</name>
        <t>
          The <tt>witnessed_at</tt> field MUST be an ISO 8601 timestamp
          with UTC offset <tt>Z</tt>, for example
          <tt>2026-03-05T14:30:00.000Z</tt>.  This records when WARD
          observed the source event, not when the source event occurred.
          The optional <tt>source_ts</tt> field MAY carry the source
          event's own timestamp for correlation.
        </t>
      </section>
    </section>

    <!-- ============================================================ -->
    <!-- Section 9: Ward Entry Schema                                  -->
    <!-- ============================================================ -->
    <section anchor="entry-schema">
      <name>Ward Entry Schema</name>
      <t>
        A ward entry is a JSON object with 11 REQUIRED and 3 OPTIONAL
        fields.
      </t>

      <section anchor="required-fields">
        <name>Required Fields</name>
        <t>
          The two field groups differ in trust, not just in
          cardinality.  Every required field except <tt>chain_hash</tt>
          is committed: it is inside the hash body of
          <xref target="chain-hash"/>, so the chain proves it.  Every
          optional field is an uncommitted annotation: it can be
          altered later without breaking any hash, and no claim of the
          form "WARD says this event was tagged X" is ever valid.
        </t>
        <table anchor="entry-required-table">
          <name>Ward Entry Required Fields (Committed)</name>
          <thead>
            <tr>
              <th>Field</th>
              <th>Type</th>
              <th>Description</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td><tt>ward_version</tt></td>
              <td>string</td>
              <td>Protocol version (e.g., "0.2"); selects the chain
                  hash scheme</td>
            </tr>
            <tr>
              <td><tt>ward_entry_id</tt></td>
              <td>string</td>
              <td>Unique identifier within the chain</td>
            </tr>
            <tr>
              <td><tt>chain_id</tt></td>
              <td>string</td>
              <td>Identifies the chain this entry belongs to</td>
            </tr>
            <tr>
              <td><tt>seq</tt></td>
              <td>integer</td>
              <td>Monotonically increasing, starting at 1</td>
            </tr>
            <tr>
              <td><tt>witnessed_at</tt></td>
              <td>string</td>
              <td>ISO 8601 UTC timestamp of when WARD observed the event</td>
            </tr>
            <tr>
              <td><tt>source_kind</tt></td>
              <td>string</td>
              <td>Enumerated source type (see <xref target="source-kinds"/>)</td>
            </tr>
            <tr>
              <td><tt>source_id</tt></td>
              <td>string</td>
              <td>Identifier of the source event</td>
            </tr>
            <tr>
              <td><tt>payload_hash</tt></td>
              <td>string</td>
              <td>SHA-256 hex of source event's canonical content</td>
            </tr>
            <tr>
              <td><tt>prev_chain_hash</tt></td>
              <td>string</td>
              <td>Previous entry's chain_hash, or genesis_hash for seq=1</td>
            </tr>
            <tr>
              <td><tt>chain_hash</tt></td>
              <td>string</td>
              <td>This entry's computed chain hash (see <xref target="chain-hash"/>)</td>
            </tr>
            <tr>
              <td><tt>issuer_id</tt></td>
              <td>string</td>
              <td>Identifier of the WARD instance that created this entry</td>
            </tr>
          </tbody>
        </table>
      </section>

      <section anchor="optional-fields">
        <name>Optional Fields</name>
        <table anchor="entry-optional-table">
          <name>Ward Entry Optional Fields (Uncommitted Annotations)</name>
          <thead>
            <tr>
              <th>Field</th>
              <th>Type</th>
              <th>Description</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td><tt>source_ts</tt></td>
              <td>string</td>
              <td>ISO 8601 UTC timestamp from the source event itself</td>
            </tr>
            <tr>
              <td><tt>tags</tt></td>
              <td>array</td>
              <td>String tags for filtering and categorisation</td>
            </tr>
            <tr>
              <td><tt>sig</tt></td>
              <td>string</td>
              <td>Ed25519 signature over chain_hash (base64)</td>
            </tr>
          </tbody>
        </table>
      </section>

      <section anchor="entry-example">
        <name>Entry Example</name>
        <t>
          The hash values below are real: the payload hash is the
          SHA-256 of a canonical example envelope (reproduced in the
          protocol repository's examples directory), the previous chain
          hash is the chain's genesis hash, and the chain hash
          recomputes from this entry's hash body under
          <xref target="chain-hash"/>.  Long values are folded per
          <xref target="RFC8792"/>.
        </t>
        <artwork type="json"><![CDATA[
NOTE: '\' line wrapping per RFC 8792

{
  "ward_version": "0.2",
  "ward_entry_id": "01WARD-E001",
  "chain_id": "ward:org/quox/env/production",
  "seq": 1,
  "witnessed_at": "2026-03-05T14:30:00.000Z",
  "source_kind": "AEE",
  "source_id": "aee-env-550e8400",
  "payload_hash": "eead9b7ee8f898c3b17f5913989ab6d048f92a1368d27f\
9b1fad15d7bb3f45d2",
  "prev_chain_hash": "4228bb654b660969b96b84d502e1615de0ac7bffaeb\
37c52a7007b310127f481",
  "chain_hash": "6078eafecfd309ca8aa078f4d90017f17d65e53e1c9bdf8d\
3ac068d05ce4a1b4",
  "issuer_id": "ward-issuer-prod-01"
}
]]></artwork>
      </section>

      <section anchor="uniqueness">
        <name>Uniqueness Constraint</name>
        <t>
          Within a single chain, the combination of
          (<tt>chain_id</tt>, <tt>source_kind</tt>, <tt>source_id</tt>)
          MUST be unique.  A chain MUST NOT contain two entries
          witnessing the same source event.  Different chains MAY
          independently witness the same source event.
        </t>
      </section>

      <section anchor="ordering">
        <name>Sequence Ordering</name>
        <t>
          The <tt>seq</tt> field MUST start at 1 and increase
          monotonically by exactly 1.  Gaps in <tt>seq</tt> MUST NOT
          occur.  Implementations MUST guarantee atomic, gap-free
          sequence assignment under concurrent access using a mechanism
          appropriate to their storage (database row or advisory locks,
          serializable transactions, or a single-writer process for
          file-based stores).
        </t>
      </section>
    </section>

    <!-- ============================================================ -->
    <!-- Section 10: Source Kinds                                       -->
    <!-- ============================================================ -->
    <section anchor="source-kinds">
      <name>Source Kinds</name>
      <t>
        WARD defines five source kinds:
      </t>
      <table anchor="source-kinds-table">
        <name>Source Kind Enumeration</name>
        <thead>
          <tr>
            <th>Kind</th>
            <th>Description</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td><tt>AEE</tt></td>
            <td>An AEE envelope (agent-to-agent message)</td>
          </tr>
          <tr>
            <td><tt>AOCL</tt></td>
            <td>An AOCL decision or policy evaluation</td>
          </tr>
          <tr>
            <td><tt>VOLT</tt></td>
            <td>A VOLT trace event or bundle commitment</td>
          </tr>
          <tr>
            <td><tt>WARD</tt></td>
            <td>A WARD tip from another chain (meta-chain pattern)</td>
          </tr>
          <tr>
            <td><tt>EXTERNAL</tt></td>
            <td>Any non-protocol event (external audit log, webhook)</td>
          </tr>
        </tbody>
      </table>
      <t>
        Implementations MUST reject entries with unrecognised
        <tt>source_kind</tt> values.
      </t>
    </section>

    <!-- ============================================================ -->
    <!-- Section 11: Ward Chain Descriptor                             -->
    <!-- ============================================================ -->
    <section anchor="chain-descriptor">
      <name>Ward Chain Descriptor</name>
      <t>
        A ward chain descriptor is a convenience object for chain
        management with 7 REQUIRED fields.  It is not part of the hash
        chain itself.
      </t>
      <table anchor="chain-descriptor-table">
        <name>Ward Chain Descriptor Fields</name>
        <thead>
          <tr>
            <th>Field</th>
            <th>Type</th>
            <th>Description</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td><tt>chain_id</tt></td>
            <td>string</td>
            <td>Unique chain identifier</td>
          </tr>
          <tr>
            <td><tt>genesis_hash</tt></td>
            <td>string</td>
            <td>SHA-256 hex, computed per <xref target="genesis-hash"/></td>
          </tr>
          <tr>
            <td><tt>scope</tt></td>
            <td>string</td>
            <td>Human-readable scope description</td>
          </tr>
          <tr>
            <td><tt>created_at</tt></td>
            <td>string</td>
            <td>ISO 8601 UTC timestamp of chain creation</td>
          </tr>
          <tr>
            <td><tt>entry_count</tt></td>
            <td>integer</td>
            <td>Total entries in the chain</td>
          </tr>
          <tr>
            <td><tt>head_seq</tt></td>
            <td>integer</td>
            <td>Sequence number of the latest entry</td>
          </tr>
          <tr>
            <td><tt>head_chain_hash</tt></td>
            <td>string</td>
            <td>Chain hash of the latest entry</td>
          </tr>
        </tbody>
      </table>
    </section>

    <!-- ============================================================ -->
    <!-- Section 12: Tips and Checkpointing                            -->
    <!-- ============================================================ -->
    <section anchor="tips">
      <name>Tips and Checkpointing</name>
      <t>
        A tip is a checkpoint summarizing chain state at a point in time.
      </t>

      <section anchor="tip-schema">
        <name>Tip Schema</name>
        <table anchor="tip-required-table">
          <name>Tip Required Fields</name>
          <thead>
            <tr>
              <th>Field</th>
              <th>Type</th>
              <th>Description</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td><tt>tip_id</tt></td>
              <td>string</td>
              <td>Unique tip identifier</td>
            </tr>
            <tr>
              <td><tt>chain_id</tt></td>
              <td>string</td>
              <td>The chain being checkpointed</td>
            </tr>
            <tr>
              <td><tt>tip_seq</tt></td>
              <td>integer</td>
              <td>The seq of the entry being checkpointed</td>
            </tr>
            <tr>
              <td><tt>tip_chain_hash</tt></td>
              <td>string</td>
              <td>Chain hash at tip_seq</td>
            </tr>
            <tr>
              <td><tt>entry_count</tt></td>
              <td>integer</td>
              <td>Entries in chain up to and including tip_seq</td>
            </tr>
            <tr>
              <td><tt>created_at</tt></td>
              <td>string</td>
              <td>ISO 8601 UTC timestamp</td>
            </tr>
          </tbody>
        </table>
        <table anchor="tip-optional-table">
          <name>Tip Optional Fields</name>
          <thead>
            <tr>
              <th>Field</th>
              <th>Type</th>
              <th>Description</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td><tt>sig</tt></td>
              <td>string</td>
              <td>Ed25519 signature over tip_chain_hash (base64)</td>
            </tr>
            <tr>
              <td><tt>key_id</tt></td>
              <td>string</td>
              <td>Stable identifier for signing key; REQUIRED when
                  sig is present</td>
            </tr>
            <tr>
              <td><tt>sink_ref</tt></td>
              <td>string</td>
              <td>Reference to external sink publication</td>
            </tr>
            <tr>
              <td><tt>notes</tt></td>
              <td>string</td>
              <td>Human-readable annotation</td>
            </tr>
          </tbody>
        </table>
        <t>
          Tips SHOULD be signed.  Unsigned tips provide checkpoint
          convenience but weaker non-repudiation guarantees.
        </t>
      </section>

      <section anchor="tip-creation">
        <name>When to Create Tips</name>
        <t>Tips SHOULD be created:</t>
        <ul>
          <li>Periodically (e.g., every N entries or every T minutes)</li>
          <li>At significant boundaries (end of a workflow run, end of
              day)</li>
          <li>Before chain sealing</li>
        </ul>
      </section>

      <section anchor="tip-signing">
        <name>Tip Signing</name>
        <t>
          Tips SHOULD be signed with Ed25519 <xref target="RFC8032"/>.
          The signature is computed over the raw UTF-8 bytes of the
          <tt>tip_chain_hash</tt> hexadecimal string.  A tip carrying
          <tt>sig</tt> MUST also carry <tt>key_id</tt>: a signature
          with no key identifier cannot be resolved and verified, so it
          would be an annotation posing as evidence.  The
          <tt>key_id</tt> value SHOULD be a stable public key
          identifier (DID or key fingerprint) to enable key lookup
          during verification.
        </t>
      </section>

      <section anchor="tip-sinks">
        <name>Tip Sinks</name>
        <t>
          Tips MAY be published to external stores.  Sinks differ in
          what they actually guarantee, and a deployment SHOULD document
          which property each configured sink provides:
        </t>
        <ul>
          <li><strong>Git signed tags</strong>: replication plus a
              signature.  A repository administrator can delete or
              replace tags, so this sink protects against attackers
              without that access; deletion is detectable only by a
              verifier who already knows the tip existed.</li>
          <li><strong>Object storage with immutable retention</strong>
              (for example S3 Object Lock in compliance mode): genuine
              write-once retention for the configured period.</li>
          <li><strong>Trusted timestamping</strong>: an RFC 3161
              authority adds third-party time.</li>
          <li><strong>Transparency services</strong>: append-only logs
              with third-party observability, such as services
              implementing the SCITT architecture
              <xref target="RFC9943"/>.  WARD does not compete with
              transparency infrastructure; a tip is a natural signed
              statement to register in it, and defining such bindings
              is planned work.</li>
        </ul>
        <t>
          The <tt>sink_ref</tt> field records where the tip was published,
          for example <tt>gitea:owner/repo#ward-tip/prod/42</tt>.
          Absence of an expected tip at its <tt>sink_ref</tt> is itself
          evidence and SHOULD surface as a verification warning, not be
          silently ignored.
        </t>
      </section>

      <section anchor="tip-example">
        <name>Tip Example</name>
        <t>
          This tip is real: <tt>tip_chain_hash</tt> is the head of the
          three-entry example chain in the protocol repository, and
          <tt>sig</tt> is a genuine Ed25519 signature over the raw
          UTF-8 bytes of that hash by a documentation key whose seed is
          published (SHA-256 of the string
          <tt>WARD-EXAMPLE-KEY-v0.2</tt>; public key
          <tt>pDN4/yZmzhkKfzDCHS5uc6seNgrcnei2+cJ3liDPwrc=</tt>), so
          both are independently verifiable.  That key is for
          documentation only: its private half is public by
          construction.  Long values are folded per
          <xref target="RFC8792"/>.
        </t>
        <artwork type="json"><![CDATA[
NOTE: '\' line wrapping per RFC 8792

{
  "tip_id": "tip-stg-002",
  "chain_id": "ward:org/quox/env/staging",
  "tip_seq": 3,
  "tip_chain_hash": "88893edd241f630801a44bb4b839f85200a1e47ac38b\
5d6ec895c85e3a937791",
  "entry_count": 3,
  "created_at": "2026-03-05T15:01:00.000Z",
  "sig": "lHFDS97QRZSwAcmtxDyJxSNDutvZLJmcQIPyX4UeKYSPK+IsVie5xWQk\
TOrfs0ioVmQHcUPTJpZOmtM0XIOCDA==",
  "key_id": "ed25519:sha256:765fe10404e81eb5",
  "sink_ref": "https://gitea.example.com/ward-tips/tags/stg-tip-002",
  "notes": "End-of-batch tip for staging, March 5 2026"
}
]]></artwork>
      </section>
    </section>

    <!-- ============================================================ -->
    <!-- Section 13: Witnessing Rules                                  -->
    <!-- ============================================================ -->
    <section anchor="witnessing-rules">
      <name>Witnessing Rules</name>

      <section anchor="what-to-witness">
        <name>What to Witness</name>
        <t>
          WARD witnesses references, not content:
        </t>
        <ul>
          <li>For AEE: the envelope identifier and the SHA-256 of the
              canonical envelope JSON.</li>
          <li>For AOCL: the decision identifier and the SHA-256 of the
              canonical decision JSON.</li>
          <li>For VOLT: the event identifier or bundle commitment hash,
              and the SHA-256 of the relevant object.</li>
          <li>For WARD: a tip from a sub-chain (meta-chain pattern).</li>
          <li>For EXTERNAL: a caller-provided identifier and payload
              hash.</li>
        </ul>
      </section>

      <section anchor="what-not-to-witness">
        <name>What NOT to Witness</name>
        <t>WARD MUST NOT witness:</t>
        <ul>
          <li>Raw message content or payloads</li>
          <li>Secrets, tokens, or credentials</li>
          <li>Personally identifiable information (PII)</li>
          <li>Attachment contents (reference by hash only)</li>
        </ul>
      </section>

      <section anchor="witness-timing">
        <name>Witness Timing</name>
        <t>
          WARD SHOULD witness events as soon as possible after they occur.
          WARD MUST NOT block or delay source event processing.  If
          witnessing fails, the failure MUST be logged but MUST NOT
          affect the source pipeline.
        </t>
      </section>

      <section anchor="selective-witnessing">
        <name>Selective Witnessing</name>
        <t>
          Implementations MAY witness a subset of source events based on
          configuration: by source kind, by event type (e.g., only AOCL
          denials), or by tag or scope.  Selective witnessing SHOULD be
          documented in the chain's <tt>scope</tt> field.
        </t>
      </section>

      <section anchor="per-protocol-rules">
        <name>Per-Protocol Witnessing Guidance</name>
        <t>
          <strong>AEE Envelopes.</strong>  Witness after the envelope is
          persisted or delivered, not while in-flight.  The payload hash
          is computed over the whole canonical envelope JSON, including
          its payload fields, so any later modification of the stored
          envelope is detectable.  WARD stores only the hash; the
          envelope content itself never enters the ward entry.
        </t>
        <t>
          <strong>AOCL Decisions.</strong>  Witness after the decision is
          finalised and persisted.  Both allow and deny decisions SHOULD
          be witnessed.  Implementations MAY filter to witness only
          terminal decisions (completions, rejections, bypasses) and skip
          layer-start events, which are high volume and low signal.
        </t>
        <t>
          <strong>VOLT Transitions.</strong>  Witness VOLT at boundaries,
          not every event: VOLT already chains events internally.
          Witness after a bundle is finalised or a run is completed or
          failed.  Individual VOLT events MAY also be witnessed for
          higher-resolution audit trails.
        </t>
        <t>
          <strong>EXTERNAL Events.</strong>  The caller provides both
          <tt>source_id</tt> and <tt>payload_hash</tt>.  WARD trusts the
          caller's hash; it does not verify against the source content
          because it never sees the content.  This allows anchoring
          non-protocol events such as external audit logs, webhooks,
          and compliance records.
        </t>
      </section>
    </section>

    <!-- ============================================================ -->
    <!-- Section 14: Chain Lifecycle                                    -->
    <!-- ============================================================ -->
    <section anchor="chain-lifecycle">
      <name>Chain Lifecycle</name>

      <section anchor="chain-creation">
        <name>Chain Creation</name>
        <ol>
          <li>Choose a <tt>chain_id</tt> following the conventions in
              <xref target="identifiers"/>.</li>
          <li>Compute <tt>genesis_hash</tt> per
              <xref target="genesis-hash"/>.</li>
          <li>Record the ward chain descriptor.</li>
        </ol>
      </section>

      <section anchor="appending-entries">
        <name>Appending Entries</name>
        <ol>
          <li>Receive a source event reference (kind, identifier,
              payload hash).</li>
          <li>Enforce the uniqueness constraint
              (<xref target="uniqueness"/>).</li>
          <li>Assign <tt>seq</tt> = previous <tt>seq</tt> + 1 (or 1 for
              the first entry).</li>
          <li>Set <tt>prev_chain_hash</tt> to the previous entry's
              <tt>chain_hash</tt> (or <tt>genesis_hash</tt> for
              seq=1).</li>
          <li>Compute <tt>chain_hash</tt> per
              <xref target="chain-hash"/>.</li>
          <li>Persist the ward entry.</li>
          <li>Update the chain descriptor (<tt>entry_count</tt>,
              <tt>head_seq</tt>, <tt>head_chain_hash</tt>).</li>
        </ol>
      </section>

      <section anchor="chain-sealing">
        <name>Chain Sealing</name>
        <t>
          A chain MAY be sealed (made immutable).  Sealing MUST create
          and sign a final tip covering the whole chain, and SHOULD
          publish that tip to an external sink; without external
          publication, the seal proves nothing to a party who does not
          already trust the store.  Sealed chains MUST NOT accept new
          entries.
        </t>
        <t>
          Chain lifecycle states are: <tt>active</tt> (accepting
          entries), <tt>sealed</tt> (final tip signed, no new entries),
          and <tt>archived</tt> (moved to cold storage).
        </t>
      </section>
    </section>

    <!-- ============================================================ -->
    <!-- Section 15: Meta-Chain Pattern                                -->
    <!-- ============================================================ -->
    <section anchor="meta-chain">
      <name>Meta-Chain Pattern</name>
      <t>
        A meta-chain is a WARD chain that witnesses tips from other WARD
        chains, creating a chain-of-chains.
      </t>

      <section anchor="meta-chain-operation">
        <name>How Meta-Chains Work</name>
        <ol>
          <li>Create a meta-chain with identifier following
              <tt>ward:meta/&lt;deployment&gt;</tt>.</li>
          <li>When a sub-chain creates a tip, witness it in the
              meta-chain with <tt>source_kind</tt> = <tt>WARD</tt>,
              <tt>source_id</tt> =
              <tt>&lt;sub_chain_id&gt;::&lt;tip_seq&gt;</tt>, and
              <tt>payload_hash</tt> = SHA-256 of the canonical tip
              JSON.</li>
        </ol>
      </section>

      <section anchor="meta-chain-source-id">
        <name>Meta-Chain Source Identifier Format</name>
        <t>
          The double colon (<tt>::</tt>) separates the sub-chain
          identifier from the tip sequence number:
        </t>
        <artwork><![CDATA[
ward:org/quox/env/production::42
]]></artwork>
        <t>
          This means: "tip at sequence 42 from chain
          <tt>ward:org/quox/env/production</tt>".  The double colon is
          chosen to avoid ambiguity with single colons in chain
          identifiers.
        </t>
        <t>
          Normative grammar: for entries with <tt>source_kind</tt>
          <tt>WARD</tt>, the <tt>source_id</tt> MUST consist of the
          sub-chain identifier, then <tt>::</tt>, then the tip sequence
          as a positive decimal integer with no leading zeros.  Because
          a chain identifier MUST NOT contain <tt>::</tt>
          (<xref target="identifiers"/>), splitting on the first
          occurrence of <tt>::</tt> is unambiguous.  A consumer MUST
          reject a WARD-kind <tt>source_id</tt> that does not match
          this grammar, including values where the separator region
          contains three or more consecutive colons.
        </t>
      </section>

      <section anchor="meta-chain-purpose">
        <name>Why Meta-Chains Exist</name>
        <t>Meta-chains provide:</t>
        <ul>
          <li>A single chain-of-chains for a deployment</li>
          <li>Cross-chain integrity verification</li>
          <li>A single tip to publish externally instead of one per
              sub-chain</li>
          <li>Compact proof that multiple chains existed at a point in
              time</li>
        </ul>
      </section>
    </section>

    <!-- ============================================================ -->
    <!-- Section 16: Verification                                      -->
    <!-- ============================================================ -->
    <section anchor="verification">
      <name>Verification</name>

      <section anchor="verification-guarantees">
        <name>What Verification Guarantees</name>
        <t>A successful verification proves:</t>
        <ul>
          <li>The genesis hash is correctly derived from the chain
              identifier.</li>
          <li>Every entry's chain hash recomputes correctly from its
              inputs.</li>
          <li>Each entry links correctly to its predecessor.</li>
          <li>Sequence numbers are monotonically increasing with no
              gaps.</li>
          <li>No duplicate source events exist within the chain.</li>
          <li>Tips reference valid chain state.</li>
          <li>Signatures (if present) are valid.</li>
        </ul>
        <t>
          In short: the chain has integrity and no entries have been
          modified, inserted, or deleted.
        </t>
      </section>

      <section anchor="verification-limitations">
        <name>What Verification Does NOT Guarantee</name>
        <t>Verification does not prove:</t>
        <ul>
          <li>The source events were truthful.</li>
          <li>The issuer was uncompromised when it created entries.</li>
          <li>The payload hashes correspond to events that still
              exist.</li>
          <li>The witnessed events were complete (coverage is an
              integration concern).</li>
        </ul>
        <t>
          WARD is a tamper-evidence protocol, not an oracle.
        </t>
      </section>

      <section anchor="verification-algorithm">
        <name>Verification Algorithm</name>
        <t>
          A WARD verifier MUST execute the following six steps in order:
        </t>

        <t>
          <strong>Step 0: Validate Entry Well-Formedness.</strong>
          Before any hash is recomputed, parse each entry with a JSON
          parser that rejects duplicate member names
          (<xref target="canonicalization"/>), validate it against the
          entry schema (all eleven required fields present, correct
          types, hash fields 64-character lowercase hexadecimal,
          <tt>seq</tt> within [1, 2^53 - 1], no null hash-body members),
          and, for entries declaring version 0.2 or later, confirm
          every hash-body string is well-formed Unicode in NFC and that
          <tt>chain_id</tt> does not contain <tt>::</tt> (version 0.1
          entries predate those rules: a violation there MUST produce a
          warning, not a failure).
          Any failure produces failure code <tt>SCHEMA_INVALID</tt>.
          Skipping this step and verifying hashes alone can "verify" an
          entry whose displayed fields differ from its hashed fields.
        </t>

        <t>
          <strong>Step 1: Validate Genesis.</strong>
          Compute <tt>expected_genesis = SHA-256("WARD-GENESIS|" +
          chain_id)</tt>.  Read the first entry (seq=1).  Confirm that
          the entry's <tt>prev_chain_hash</tt> equals
          <tt>expected_genesis</tt>.  Mismatch produces failure code
          <tt>GENESIS_MISMATCH</tt>.
        </t>

        <t>
          <strong>Step 2: Validate Chain Hashes.</strong>
          For each entry in sequence order, select the hash scheme from
          the entry's <tt>ward_version</tt>: <tt>"0.2"</tt> uses the
          canonical-JSON hash body of <xref target="chain-hash"/>;
          <tt>"0.1"</tt> uses the legacy concatenation of
          <xref target="legacy-chain-hash"/>; any other value produces
          failure code <tt>CHAIN_HASH_MISMATCH</tt> with a detail naming
          the unrecognized version.  Recompute <tt>chain_hash</tt> under
          that scheme and confirm it equals the stored value; mismatch
          produces <tt>CHAIN_HASH_MISMATCH</tt>.  Declared
          <tt>ward_version</tt> values MUST be non-decreasing in
          <tt>seq</tt> order, compared numerically as (major, minor);
          an entry declaring a lower version than any earlier entry
          produces failure code <tt>VERSION_DOWNGRADE</tt>.  This makes
          "no new legacy entries" enforceable from stored data for
          upgraded chains.  For a chain
          that has never upgraded, a verifier reading stored data cannot
          distinguish a legacy entry appended recently from one appended
          before the current version existed; the mitigation, as for any
          append to a compromised chain, is externally published signed
          tips, which bound the exposure to entries after the most
          recent tip.  If a version 0.1
          entry's <tt>ward_entry_id</tt> or <tt>source_id</tt> contains
          the <tt>|</tt> character, the verifier MUST append a warning
          to the result (delimiter-injection exposure,
          <xref target="legacy-chain-hash"/>).
        </t>

        <t>
          <strong>Step 3: Validate Chain Linkage.</strong>
          For each entry where seq &gt; 1, confirm that the entry's
          <tt>prev_chain_hash</tt> equals the <tt>chain_hash</tt> of the
          entry with seq - 1.  Mismatch produces failure code
          <tt>CHAIN_LINK_BROKEN</tt>.
        </t>

        <t>
          <strong>Step 4: Validate Ordering and Uniqueness.</strong>
          Confirm <tt>seq</tt> starts at 1, increases monotonically by 1
          with no gaps, and that no duplicate
          (<tt>chain_id</tt>, <tt>source_kind</tt>, <tt>source_id</tt>)
          tuples exist.  Sequence violation produces
          <tt>SEQ_INVALID</tt>; duplicate source produces
          <tt>DUPLICATE_SOURCE</tt>.
        </t>

        <t>
          <strong>Step 5: Validate Tips.</strong>
          For each tip, locate the entry at <tt>tip_seq</tt> and confirm
          <tt>tip_chain_hash</tt> equals that entry's
          <tt>chain_hash</tt>.  If <tt>sig</tt> is present, resolve
          <tt>key_id</tt> to a public key using the deployment's
          documented key-resolution policy (key resolution and trust
          are deployment-defined, see <xref target="signatures"/>) and
          verify the Ed25519 signature over the <tt>tip_chain_hash</tt>
          hex bytes.  A valid signature proves possession of the
          resolved key, not that the key was authorized.  Hash mismatch
          produces <tt>TIP_MISMATCH</tt>; signature failure produces
          <tt>TIP_SIGNATURE_INVALID</tt>.
        </t>
      </section>

      <section anchor="failure-codes">
        <name>Failure Reason Codes</name>
        <table anchor="failure-codes-table">
          <name>Verification Failure Codes</name>
          <thead>
            <tr>
              <th>Code</th>
              <th>Meaning</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td><tt>SCHEMA_INVALID</tt></td>
              <td>Entry failed Step 0 well-formedness validation
                  (schema, duplicate members, non-NFC strings, reserved
                  "::" in chain_id)</td>
            </tr>
            <tr>
              <td><tt>VERSION_DOWNGRADE</tt></td>
              <td>An entry declares a lower ward_version than an
                  earlier entry (version monotonicity)</td>
            </tr>
            <tr>
              <td><tt>GENESIS_MISMATCH</tt></td>
              <td>Computed genesis hash does not match entry seq=1
                  prev_chain_hash</td>
            </tr>
            <tr>
              <td><tt>CHAIN_HASH_MISMATCH</tt></td>
              <td>Recomputed chain_hash does not match stored value</td>
            </tr>
            <tr>
              <td><tt>CHAIN_LINK_BROKEN</tt></td>
              <td>Entry's prev_chain_hash does not match previous
                  entry's chain_hash</td>
            </tr>
            <tr>
              <td><tt>SEQ_INVALID</tt></td>
              <td>Sequence numbers are not monotonically increasing
                  from 1</td>
            </tr>
            <tr>
              <td><tt>DUPLICATE_SOURCE</tt></td>
              <td>Same (chain_id, source_kind, source_id) appears
                  more than once</td>
            </tr>
            <tr>
              <td><tt>TIP_MISMATCH</tt></td>
              <td>Tip's chain_hash does not match the entry at
                  tip_seq</td>
            </tr>
            <tr>
              <td><tt>TIP_SIGNATURE_INVALID</tt></td>
              <td>Tip signature does not verify against the declared
                  key</td>
            </tr>
            <tr>
              <td><tt>INCOMPLETE_CHAIN</tt></td>
              <td>(PARTIAL) Chain verified up to a point, but later
                  entries are unavailable</td>
            </tr>
          </tbody>
        </table>
        <t>
          All codes except <tt>INCOMPLETE_CHAIN</tt> accompany the
          <tt>BROKEN</tt> status; <tt>INCOMPLETE_CHAIN</tt> accompanies
          <tt>PARTIAL</tt>.
          Implementations MAY define additional reason codes; a consumer
          that encounters an unknown code MUST treat it according to the
          <tt>status</tt> field, never as a success.
        </t>
      </section>

      <section anchor="result-statuses">
        <name>Verification Result Statuses</name>
        <table anchor="result-statuses-table">
          <name>Verification Result Statuses</name>
          <thead>
            <tr>
              <th>Status</th>
              <th>Meaning</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td><tt>INTACT</tt></td>
              <td>Chain verified successfully: all hashes, linkage,
                  ordering, and tips are correct</td>
            </tr>
            <tr>
              <td><tt>BROKEN</tt></td>
              <td>Verification failed: at least one check did not
                  pass</td>
            </tr>
            <tr>
              <td><tt>PARTIAL</tt></td>
              <td>Chain is valid up to a point but incomplete</td>
            </tr>
          </tbody>
        </table>
      </section>

      <section anchor="result-examples">
        <name>Verification Result Examples</name>
        <t>
          The results below are illustrative: values in angle brackets
          are placeholders describing what belongs in each field, not
          computed hashes.  Fully real, recomputable example chains and
          verification results are published in the protocol
          repository's examples directory.
        </t>
        <t>INTACT result:</t>
        <artwork type="json"><![CDATA[
{
  "status": "INTACT",
  "chain_id": "ward:org/quox/env/production",
  "ward_version": "0.2",
  "entry_count": 1042,
  "head_seq": 1042,
  "head_chain_hash": "d4e5f6a7b8c9d0e1...a1b2c3d4",
  "genesis_verified": true,
  "tips_verified": 3,
  "signatures_verified": 2,
  "warnings": []
}
]]></artwork>
        <t>BROKEN result:</t>
        <artwork type="json"><![CDATA[
{
  "status": "BROKEN",
  "chain_id": "ward:org/quox/env/production",
  "reason": "CHAIN_HASH_MISMATCH",
  "details": {
    "seq": 417,
    "ward_entry_id": "01WARD-E417",
    "expected_chain_hash": "c3d4e5f6...recomputed",
    "found_chain_hash": "ff00ff00...stored"
  }
}
]]></artwork>
      </section>

      <section anchor="exit-codes">
        <name>CLI Exit Codes</name>
        <t>
          Implementations providing a command-line verifier SHOULD use
          the following exit codes:
        </t>
        <ul>
          <li><tt>0</tt> = INTACT</li>
          <li><tt>1</tt> = BROKEN (verification failed)</li>
          <li><tt>2</tt> = ERROR (storage not readable, invalid
              arguments)</li>
          <li><tt>3</tt> = PARTIAL (chain incomplete)</li>
        </ul>
      </section>
    </section>

    <!-- ============================================================ -->
    <!-- Section 17: Conformance Levels                                -->
    <!-- ============================================================ -->
    <section anchor="conformance-levels">
      <name>Conformance Levels</name>

      <section anchor="ward-w">
        <name>WARD-W (Witness)</name>
        <t>
          An implementation is WARD-W conformant if it:
        </t>
        <ul>
          <li>Produces ward entries matching
              <xref target="entry-schema"/></li>
          <li>Computes <tt>genesis_hash</tt> and <tt>chain_hash</tt>
              correctly per <xref target="crypto-primitives"/></li>
          <li>Enforces the uniqueness constraint per
              <xref target="uniqueness"/></li>
          <li>Respects content-free constraints per
              <xref target="content-free"/></li>
        </ul>
      </section>

      <section anchor="ward-v">
        <name>WARD-V (Verifier)</name>
        <t>
          An implementation is WARD-V conformant if it:
        </t>
        <ul>
          <li>Implements the six-step verification algorithm per
              <xref target="verification-algorithm"/></li>
          <li>Produces verification results with the defined status
              codes and failure reasons</li>
        </ul>
      </section>

      <section anchor="ward-t">
        <name>WARD-T (Tipper)</name>
        <t>
          An implementation is WARD-T conformant if it:
        </t>
        <ul>
          <li>Creates valid tips per <xref target="tip-schema"/></li>
          <li>Optionally signs tips with Ed25519</li>
          <li>Optionally publishes tips to external sinks</li>
        </ul>
      </section>
    </section>

    <!-- ============================================================ -->
    <!-- Section 18: Storage Guidance                                   -->
    <!-- ============================================================ -->
    <section anchor="storage-guidance">
      <name>Storage Guidance</name>
      <t>
        This section is non-normative.  It provides implementation
        guidance for common storage backends.
      </t>

      <section anchor="sqlite-storage">
        <name>SQLite (Single-Node)</name>
        <t>
          SQLite with WAL mode is the recommended storage for
          single-node deployments.  The following schema is suggested:
        </t>
        <artwork><![CDATA[
CREATE TABLE ward_entries (
    ward_entry_id TEXT PRIMARY KEY,
    chain_id      TEXT NOT NULL,
    seq           INTEGER NOT NULL,
    witnessed_at  TEXT NOT NULL,
    source_kind   TEXT NOT NULL
      CHECK (source_kind IN
        ('AEE','AOCL','VOLT','WARD','EXTERNAL')),
    source_id     TEXT NOT NULL,
    payload_hash  TEXT NOT NULL,
    prev_chain_hash TEXT NOT NULL,
    chain_hash    TEXT NOT NULL,
    issuer_id     TEXT NOT NULL,
    ward_version  TEXT NOT NULL DEFAULT '0.2',
    source_ts     TEXT,
    tags          TEXT,
    sig           TEXT,
    UNIQUE(chain_id, seq),
    UNIQUE(chain_id, source_kind, source_id)
);
]]></artwork>
        <t>
          Storage sizing is approximately 500 bytes per SQLite row
          including indexes; 10,000 entries require approximately 5 MB.
        </t>
      </section>

      <section anchor="postgres-storage">
        <name>PostgreSQL (Multi-Node)</name>
        <t>
          For multi-node or high-availability deployments, use the same
          schema with appropriate type mappings (UUID, BIGINT,
          TIMESTAMPTZ, JSONB).  Implementations SHOULD configure
          append-only triggers to prevent UPDATE and DELETE on entry
          rows, ensuring the write-once property at the storage layer.
        </t>
      </section>
    </section>

    <!-- ============================================================ -->
    <!-- Section 19: Threat Model                                      -->
    <!-- ============================================================ -->
    <section anchor="threat-model">
      <name>Threat Model</name>

      <section anchor="threats-mitigated">
        <name>Threats WARD Mitigates</name>
        <t>
          <strong>T1: Post-Hoc Tampering.</strong>
          An attacker modifies a ward entry after it has been recorded.
          The chain hash recomputation during verification detects the
          modification (<tt>CHAIN_HASH_MISMATCH</tt>).
        </t>
        <t>
          <strong>T2: Entry Deletion.</strong>
          An attacker removes an entry from the middle of the chain.
          Chain linkage breaks at the entry after the gap
          (<tt>CHAIN_LINK_BROKEN</tt>) and the sequence gap is detected
          (<tt>SEQ_INVALID</tt>).
        </t>
        <t>
          <strong>T3: Entry Insertion.</strong>
          An attacker inserts a fabricated entry.  Unless they rehash all
          subsequent entries, chain linkage breaks.  If they do rehash,
          signed tips published to external sinks detect the discrepancy.
        </t>
        <t>
          <strong>T4: Backdating.</strong>
          An attacker assigns an earlier <tt>witnessed_at</tt> timestamp.
          If tips are published to external sinks with their own
          timestamps, the inconsistency is detectable.  Without external
          sinks, backdating is harder to detect.
        </t>
        <t>
          <strong>T5: Double-Witnessing.</strong>
          An attacker attempts to witness the same source event twice.
          The uniqueness constraint
          (<tt>chain_id</tt>, <tt>source_kind</tt>, <tt>source_id</tt>)
          prevents this at the storage layer.
        </t>
      </section>

      <section anchor="threats-not-mitigated">
        <name>Threats WARD Does Not Fully Mitigate</name>
        <t>
          <strong>T6: Compromised Issuer.</strong>
          If the WARD issuer itself is compromised, the attacker can
          produce a valid but false chain with consistent hashes, or
          present an alternative branch (fork) built from the same
          predecessor entry while withholding the original; a verifier
          shown only one branch cannot detect the fork from chain data
          alone.
          Mitigation: signed tips published to external sinks prevent
          post-hoc modification and anchor the branch that existed at
          publication time; meta-chains from independent issuers
          provide cross-verification; periodic external audits detect
          inconsistencies.
        </t>
        <t>
          <strong>T7: Pre-Witness Modification.</strong>
          WARD witnesses what it receives.  If the source event is
          modified before WARD observes it, WARD cannot detect this.
          Mitigation: VOLT's internal hash chain detects VOLT
          modifications; AEE carries envelope-level integrity; AOCL
          persists decisions before triggering WARD.  Defence in depth
          across all four protocols hardens the system.
        </t>
        <t>
          <strong>T8: Key Theft.</strong>
          An attacker who steals the Ed25519 signing key can forge tips.
          Mitigation: store keys in HSM/TPM, implement key rotation
          schedules, maintain key revocation lists, and use separate
          keys per environment.
        </t>
      </section>
    </section>

    <!-- ============================================================ -->
    <!-- Section 20: Compatibility with AEE, AOCL, and VOLT           -->
    <!-- ============================================================ -->
    <section anchor="compatibility">
      <name>Compatibility with AEE, AOCL, and VOLT</name>
      <t>
        WARD is designed as the fourth member of a family of
        complementary protocols for agentic AI governance:
      </t>
      <ul>
        <li><strong>AEE</strong> <xref target="AEE"/>: message format
            and correlation for agent communication.</li>
        <li><strong>AOCL</strong> <xref target="AOCL"/>: orchestration
            control layers for policy decisions, permissions, and
            human-in-the-loop gates.</li>
        <li><strong>VOLT</strong> <xref target="VOLT"/>: evidence
            recording with hash-chained traces and exportable
            bundles.</li>
        <li><strong>WARD</strong>: content-free witnessing with
            hash-chain receipts, tips, and meta-chains.</li>
      </ul>
      <t>
        WARD and VOLT are complementary: VOLT records detailed trace
        events with payloads (the evidence layer), while WARD produces
        content-free hash-chain receipts (the witnessing layer).  WARD
        may witness VOLT events, but never stores VOLT payloads.  VOLT
        may reference WARD chain hashes as external integrity anchors.
      </t>
      <t>
        WARD witnesses AEE envelopes by identifier and hash but does not
        participate in AEE routing or transport.  WARD witnesses AOCL
        decisions by identifier and hash but does not evaluate policies
        or make control decisions.
      </t>
    </section>

    <!-- ============================================================ -->
    <!-- Section 21: Security Considerations                           -->
    <!-- ============================================================ -->
    <section anchor="security-considerations">
      <name>Security Considerations</name>
      <t>
        WARD provides tamper-evidence guarantees for hash-chain
        integrity.  It does not provide confidentiality, access control,
        or authentication of source events.  The following considerations
        apply to deployments.
      </t>
      <t>
        <strong>Hash Chain Integrity.</strong>
        WARD's core security property derives from SHA-256 hash chaining.
        Each entry's <tt>chain_hash</tt> depends on all preceding entries
        via the <tt>prev_chain_hash</tt> linkage.  Modifying any entry
        invalidates all subsequent chain hashes, making tampering
        detectable.  This property holds as long as SHA-256 remains
        preimage and collision resistant <xref target="RFC6234"/>.
      </t>
      <t>
        <strong>Hash-Input Unambiguity.</strong>
        The integrity property additionally requires that no two
        distinct field tuples produce the same hash input.  The
        canonical-JSON hash body of <xref target="chain-hash"/>
        guarantees this by construction: JSON string escaping makes
        field boundaries unambiguous for every possible identifier
        value.  The legacy version 0.1 scheme
        (<xref target="legacy-chain-hash"/>) lacked this property:
        because identifiers carried no character-set restriction, a
        crafted identifier containing the <tt>|</tt> delimiter could
        make two distinct tuples hash identically, so a chain hash
        could commit to an ambiguous statement.  Verifiers surface a
        warning when legacy entries contain the delimiter character in
        an identifier field.  Optional fields are outside the hash body
        in both schemes and MUST NOT be relied upon for any
        security-relevant purpose.
      </t>
      <t>
        <strong>Genesis Trust Anchor.</strong>
        The genesis hash is deterministically derived from the
        <tt>chain_id</tt>.  Any party with knowledge of the
        <tt>chain_id</tt> can independently compute the expected genesis
        hash and verify the chain's origin.  There is no secret material
        in the genesis computation.
      </t>
      <t>
        <strong>Content-Free Privacy.</strong>
        WARD's content-free design ensures that ward entries and chains
        do not leak source event content.  However, metadata (source
        identifiers, timestamps, source kinds, and payload hashes) may
        reveal operational patterns.  Deployments that require metadata
        privacy should apply additional protections at the integration
        layer.
      </t>
      <t>
        <strong>Confirmation Attacks on Low-Entropy Content.</strong>
        Content-free does not mean information-free.  A payload hash is
        an unsalted commitment: an attacker who can enumerate the
        plausible source contents (for example a decision field whose
        only values are approved, denied, or pending) can hash each
        candidate and confirm which one was witnessed.  Payload hashes
        over low-entropy or predictable source events permit this by
        construction.  Where it matters, callers SHOULD fold
        caller-held randomness or unpredictable identifiers into the
        canonical source content before hashing; a first-class blinded
        commitment profile is a candidate for a future revision.
      </t>
      <t>
        <strong>Tip Signing and External Sinks.</strong>
        Without signed tips published to external stores, an attacker
        who controls the WARD storage can rewrite the entire chain with
        consistent hashes.  Signed tips published to external sinks of
        documented strength (see <xref target="tip-sinks"/>) create
        anchors that
        make full chain rewrites detectable.  Deployments requiring strong
        non-repudiation SHOULD sign all tips and publish them to at least
        one external sink.
      </t>
      <t>
        <strong>Key Management.</strong>
        Ed25519 signing keys used for tips are high-value assets.
        Compromise of a signing key allows an attacker to forge tips.
        Implementations SHOULD store signing keys in hardware security
        modules, implement key rotation schedules, and maintain
        revocation lists.  Separate keys SHOULD be used for separate
        environments.
      </t>
      <t>
        <strong>Denial of Service.</strong>
        An attacker who can submit arbitrary source event references may
        attempt to inflate the chain with spurious entries.
        Implementations SHOULD rate-limit witness requests and validate
        that source references correspond to genuine events at the
        integration boundary.
      </t>
      <t>
        <strong>Layered Defence.</strong>
        WARD is most effective as part of a layered defence with
        AEE <xref target="AEE"/>, AOCL <xref target="AOCL"/>, and
        VOLT <xref target="VOLT"/>.  No single protocol provides
        complete security; together they provide message integrity (AEE),
        policy enforcement (AOCL), evidence recording (VOLT), and
        tamper-evident witnessing (WARD).
      </t>
    </section>

    <!-- ============================================================ -->
    <!-- Section 22: IANA Considerations                               -->
    <!-- ============================================================ -->
    <section anchor="iana-considerations">
      <name>IANA Considerations</name>
      <t>
        This document has no IANA actions.
      </t>
    </section>

  </middle>

  <back>

    <references>
      <name>References</name>

      <references>
        <name>Normative References</name>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.2119.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8174.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8259.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.3629.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.6234.xml"/>
      </references>

      <references>
        <name>Informative References</name>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8032.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8792.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9943.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9562.xml"/>

        <reference anchor="AEE" target="https://github.com/quoxai/aee">
          <front>
            <title>Agent Envelope Exchange (AEE): A Minimal JSON Envelope Format for Inter-Agent Communication</title>
            <author initials="A." surname="Cowles" fullname="Adam Cowles"/>
            <date year="2026"/>
          </front>
          <seriesInfo name="Internet-Draft" value="draft-cowles-aee-01"/>
        </reference>

        <reference anchor="AOCL" target="https://github.com/quoxai/aocl">
          <front>
            <title>Agent Orchestration Control Layers (AOCL): A Multi-Layer Governance Framework for Agentic AI Systems</title>
            <author initials="A." surname="Cowles" fullname="Adam Cowles"/>
            <date year="2026"/>
          </front>
          <seriesInfo name="Internet-Draft" value="draft-cowles-aocl-01"/>
        </reference>

        <reference anchor="VOLT" target="https://github.com/quoxai/volt">
          <front>
            <title>Verifiable Operations Ledger and Trace (VOLT) Protocol</title>
            <author initials="A." surname="Cowles" fullname="Adam Cowles"/>
            <date year="2026"/>
          </front>
          <seriesInfo name="Internet-Draft" value="draft-cowles-volt-01"/>
        </reference>

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

    </references>

    <section anchor="legacy-chain-hash">
      <name>Legacy Version 0.1 Chain Hash</name>
      <t>
        Entries with <tt>ward_version</tt> <tt>"0.1"</tt> computed
        <tt>chain_hash</tt> by delimiter concatenation:
      </t>
      <artwork><![CDATA[
chain_hash = SHA-256(
  prev_chain_hash + "|" +
  chain_id        + "|" +
  seq             + "|" +
  ward_entry_id   + "|" +
  timestamp       + "|" +
  source_kind     + "|" +
  source_id       + "|" +
  payload_hash
)
]]></artwork>
      <t>
        All values are UTF-8 strings concatenated with pipe (<tt>|</tt>)
        separators.  The <tt>seq</tt> value is the decimal string
        representation of the sequence number with no leading zeros.
        The <tt>timestamp</tt> value is the ISO 8601 UTC string from the
        entry's <tt>witnessed_at</tt> field.  The <tt>issuer_id</tt> and
        <tt>ward_version</tt> fields are not part of the version 0.1
        hash input.
      </t>
      <t>
        This scheme is deprecated for the reason described in
        <xref target="chain-hash"/> and in the Security Considerations:
        identifiers containing the <tt>|</tt> character make the hash
        input ambiguous.  Chains are append-only and are never rehashed,
        so verifiers MUST still implement this scheme to verify existing
        version 0.1 entries.  Writers MUST NOT create new version 0.1
        entries; verifiers enforce this to the extent stored data allows
        via the version-monotonicity rule of the verification algorithm
        (<tt>VERSION_DOWNGRADE</tt>), and MUST append a warning to the
        verification result when a version 0.1 entry's
        <tt>ward_entry_id</tt> or <tt>source_id</tt> contains the
        <tt>|</tt> character.
      </t>
    </section>

    <section numbered="false">
      <name>Acknowledgements</name>
      <t>
        The author thanks the early adopters and reviewers of the Quox
        protocol family (AEE, AOCL, VOLT, WARD) for their feedback on
        content-free witnessing design, hash chain construction, and
        the meta-chain pattern.
      </t>
    </section>

  </back>

</rfc>
