| Internet-Draft | WARD Protocol | August 2026 |
| Cowles | Expires 4 March 2027 | [Page] |
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.¶
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.¶
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.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 4 March 2027.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document.¶
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 [VOLT], can be rewritten if an attacker controls the host and no external checkpoints exist. Operators need a way to answer questions such as:¶
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) [AEE], Agent Orchestration Control Layers (AOCL) [AOCL], and VOLT [VOLT] protocols, producing tamper-evident receipts that can be verified independently.¶
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.¶
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.¶
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 [RFC2119] [RFC8174] when, and only when, they appear in ALL CAPITALS, as shown here.¶
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.¶
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.¶
Every ward entry MUST include a ward_version field.
Unknown fields MUST be ignored by verifiers to ensure forward
compatibility. Breaking changes MUST increment
ward_version; while the major version is 0, a minor
increment MAY be a breaking change (the standard pre-1.0
convention). The ward_version value stored in each
entry selects the chain hash scheme a verifier MUST apply to
that entry (see Section 7.4).¶
All hashes MUST be deterministically reproducible from the same inputs. No randomness is permitted in hash computation; only identifier generation may use random sources.¶
Ward entries are JSON objects [RFC8259]. All JSON MUST be encoded as UTF-8 [RFC3629]. The storage format is implementation-defined; common choices include SQLite rows, PostgreSQL tables, and NDJSON files.¶
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.¶
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 [VOLT], but this document is authoritative for WARD. An implementation MUST produce canonical byte representations as follows:¶
\u00xx with lowercase hexadecimal digits). Every
other character, including /, MUST appear
literally as UTF-8; optional escapes such as \/
are non-canonical and MUST NOT be produced. This makes the
serialization of any given string unique.¶
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; ward_version binds each entry to the
profile in force when it was written.¶
All hashing in WARD v0.2 MUST use SHA-256 [RFC6234] [FIPS180-4].¶
Hash values MUST be encoded as lowercase hexadecimal strings of 64 characters (256 bits).¶
The genesis hash anchors a chain's origin deterministically:¶
genesis_hash = SHA-256("WARD-GENESIS|" + chain_id)
¶
The input is the literal UTF-8 string WARD-GENESIS|
concatenated with the chain_id. The genesis hash
serves as prev_chain_hash 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.¶
Each entry's chain_hash links it to its predecessor.
It is computed over the canonical JSON (see
Section 6) of the entry's hash body:
the ten required fields of the entry schema excluding
chain_hash itself.¶
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))
¶
The hash body MUST contain exactly these ten members: no
extras, none absent, none null. The seq value is
hashed as a JSON integer, not a string. Optional fields
(source_ts, tags, sig) MUST NOT be
included in the hash body; they are annotations, not
commitments, and are not tamper-protected by the chain.¶
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 Appendix A.¶
Because ward_version 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.¶
Signatures are OPTIONAL in WARD v0.2 but the schema reserves fields for them. If used:¶
When signing a tip, the signature is computed over the raw
UTF-8 bytes of the tip_chain_hash hexadecimal string.¶
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
key_id: a verified signature therefore proves
possession of whichever key the deployment resolved for that
identifier, and nothing more. Deployments MUST document how
key_id 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.¶
The ward_entry_id MUST be unique within a chain.
The chain_id MUST be unique within a deployment.
Identifiers SHOULD be UUIDv4 [RFC9562] or ULID.¶
Chain identifiers follow a scoping convention:¶
Examples: ward:org/quox/env/production,
ward:org/quox/env/staging,
ward:meta/quox-global.¶
A chain_id MUST NOT contain the sequence
::, which is reserved as the separator in meta-chain
source_id values (Section 15.2).
Identifiers SHOULD be limited to printable ASCII without
whitespace; with canonical-JSON hashing this is hygiene rather
than a hashing-safety requirement.¶
The witnessed_at field MUST be an ISO 8601 timestamp
with UTC offset Z, for example
2026-03-05T14:30:00.000Z. This records when WARD
observed the source event, not when the source event occurred.
The optional source_ts field MAY carry the source
event's own timestamp for correlation.¶
A ward entry is a JSON object with 11 REQUIRED and 3 OPTIONAL fields.¶
The two field groups differ in trust, not just in
cardinality. Every required field except chain_hash
is committed: it is inside the hash body of
Section 7.4, 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.¶
| Field | Type | Description |
|---|---|---|
ward_version
|
string | Protocol version (e.g., "0.2"); selects the chain hash scheme |
ward_entry_id
|
string | Unique identifier within the chain |
chain_id
|
string | Identifies the chain this entry belongs to |
seq
|
integer | Monotonically increasing, starting at 1 |
witnessed_at
|
string | ISO 8601 UTC timestamp of when WARD observed the event |
source_kind
|
string | Enumerated source type (see Section 10) |
source_id
|
string | Identifier of the source event |
payload_hash
|
string | SHA-256 hex of source event's canonical content |
prev_chain_hash
|
string | Previous entry's chain_hash, or genesis_hash for seq=1 |
chain_hash
|
string | This entry's computed chain hash (see Section 7.4) |
issuer_id
|
string | Identifier of the WARD instance that created this entry |
| Field | Type | Description |
|---|---|---|
source_ts
|
string | ISO 8601 UTC timestamp from the source event itself |
tags
|
array | String tags for filtering and categorisation |
sig
|
string | Ed25519 signature over chain_hash (base64) |
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 Section 7.4. Long values are folded per [RFC8792].¶
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"
}
¶
Within a single chain, the combination of
(chain_id, source_kind, source_id)
MUST be unique. A chain MUST NOT contain two entries
witnessing the same source event. Different chains MAY
independently witness the same source event.¶
The seq field MUST start at 1 and increase
monotonically by exactly 1. Gaps in seq 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).¶
WARD defines five source kinds:¶
| Kind | Description |
|---|---|
AEE
|
An AEE envelope (agent-to-agent message) |
AOCL
|
An AOCL decision or policy evaluation |
VOLT
|
A VOLT trace event or bundle commitment |
WARD
|
A WARD tip from another chain (meta-chain pattern) |
EXTERNAL
|
Any non-protocol event (external audit log, webhook) |
Implementations MUST reject entries with unrecognised
source_kind values.¶
A ward chain descriptor is a convenience object for chain management with 7 REQUIRED fields. It is not part of the hash chain itself.¶
| Field | Type | Description |
|---|---|---|
chain_id
|
string | Unique chain identifier |
genesis_hash
|
string | SHA-256 hex, computed per Section 7.3 |
scope
|
string | Human-readable scope description |
created_at
|
string | ISO 8601 UTC timestamp of chain creation |
entry_count
|
integer | Total entries in the chain |
head_seq
|
integer | Sequence number of the latest entry |
head_chain_hash
|
string | Chain hash of the latest entry |
A tip is a checkpoint summarizing chain state at a point in time.¶
| Field | Type | Description |
|---|---|---|
tip_id
|
string | Unique tip identifier |
chain_id
|
string | The chain being checkpointed |
tip_seq
|
integer | The seq of the entry being checkpointed |
tip_chain_hash
|
string | Chain hash at tip_seq |
entry_count
|
integer | Entries in chain up to and including tip_seq |
created_at
|
string | ISO 8601 UTC timestamp |
| Field | Type | Description |
|---|---|---|
sig
|
string | Ed25519 signature over tip_chain_hash (base64) |
key_id
|
string | Stable identifier for signing key; REQUIRED when sig is present |
sink_ref
|
string | Reference to external sink publication |
notes
|
string | Human-readable annotation |
Tips SHOULD be signed. Unsigned tips provide checkpoint convenience but weaker non-repudiation guarantees.¶
Tips SHOULD be created:¶
Tips SHOULD be signed with Ed25519 [RFC8032].
The signature is computed over the raw UTF-8 bytes of the
tip_chain_hash hexadecimal string. A tip carrying
sig MUST also carry key_id: a signature
with no key identifier cannot be resolved and verified, so it
would be an annotation posing as evidence. The
key_id value SHOULD be a stable public key
identifier (DID or key fingerprint) to enable key lookup
during verification.¶
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:¶
The sink_ref field records where the tip was published,
for example gitea:owner/repo#ward-tip/prod/42.
Absence of an expected tip at its sink_ref is itself
evidence and SHOULD surface as a verification warning, not be
silently ignored.¶
This tip is real: tip_chain_hash is the head of the
three-entry example chain in the protocol repository, and
sig 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
WARD-EXAMPLE-KEY-v0.2; public key
pDN4/yZmzhkKfzDCHS5uc6seNgrcnei2+cJ3liDPwrc=), so
both are independently verifiable. That key is for
documentation only: its private half is public by
construction. Long values are folded per
[RFC8792].¶
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"
}
¶
WARD witnesses references, not content:¶
WARD MUST NOT witness:¶
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.¶
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 scope field.¶
AEE Envelopes. 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.¶
AOCL Decisions. 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.¶
VOLT Transitions. 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.¶
EXTERNAL Events. The caller provides both
source_id and payload_hash. 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.¶
chain_id following the conventions in
Section 8.1.¶
genesis_hash per
Section 7.3.¶
seq = previous seq + 1 (or 1 for
the first entry).¶
prev_chain_hash to the previous entry's
chain_hash (or genesis_hash for
seq=1).¶
chain_hash per
Section 7.4.¶
entry_count,
head_seq, head_chain_hash).¶
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.¶
Chain lifecycle states are: active (accepting
entries), sealed (final tip signed, no new entries),
and archived (moved to cold storage).¶
A meta-chain is a WARD chain that witnesses tips from other WARD chains, creating a chain-of-chains.¶
The double colon (::) separates the sub-chain
identifier from the tip sequence number:¶
ward:org/quox/env/production::42¶
This means: "tip at sequence 42 from chain
ward:org/quox/env/production". The double colon is
chosen to avoid ambiguity with single colons in chain
identifiers.¶
Normative grammar: for entries with source_kind
WARD, the source_id MUST consist of the
sub-chain identifier, then ::, then the tip sequence
as a positive decimal integer with no leading zeros. Because
a chain identifier MUST NOT contain ::
(Section 8.1), splitting on the first
occurrence of :: is unambiguous. A consumer MUST
reject a WARD-kind source_id that does not match
this grammar, including values where the separator region
contains three or more consecutive colons.¶
A successful verification proves:¶
In short: the chain has integrity and no entries have been modified, inserted, or deleted.¶
Verification does not prove:¶
WARD is a tamper-evidence protocol, not an oracle.¶
A WARD verifier MUST execute the following six steps in order:¶
Step 0: Validate Entry Well-Formedness.
Before any hash is recomputed, parse each entry with a JSON
parser that rejects duplicate member names
(Section 6), validate it against the
entry schema (all eleven required fields present, correct
types, hash fields 64-character lowercase hexadecimal,
seq 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
chain_id does not contain :: (version 0.1
entries predate those rules: a violation there MUST produce a
warning, not a failure).
Any failure produces failure code SCHEMA_INVALID.
Skipping this step and verifying hashes alone can "verify" an
entry whose displayed fields differ from its hashed fields.¶
Step 1: Validate Genesis.
Compute expected_genesis = SHA-256("WARD-GENESIS|" +
chain_id). Read the first entry (seq=1). Confirm that
the entry's prev_chain_hash equals
expected_genesis. Mismatch produces failure code
GENESIS_MISMATCH.¶
Step 2: Validate Chain Hashes.
For each entry in sequence order, select the hash scheme from
the entry's ward_version: "0.2" uses the
canonical-JSON hash body of Section 7.4;
"0.1" uses the legacy concatenation of
Appendix A; any other value produces
failure code CHAIN_HASH_MISMATCH with a detail naming
the unrecognized version. Recompute chain_hash under
that scheme and confirm it equals the stored value; mismatch
produces CHAIN_HASH_MISMATCH. Declared
ward_version values MUST be non-decreasing in
seq order, compared numerically as (major, minor);
an entry declaring a lower version than any earlier entry
produces failure code VERSION_DOWNGRADE. 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 ward_entry_id or source_id contains
the | character, the verifier MUST append a warning
to the result (delimiter-injection exposure,
Appendix A).¶
Step 3: Validate Chain Linkage.
For each entry where seq > 1, confirm that the entry's
prev_chain_hash equals the chain_hash of the
entry with seq - 1. Mismatch produces failure code
CHAIN_LINK_BROKEN.¶
Step 4: Validate Ordering and Uniqueness.
Confirm seq starts at 1, increases monotonically by 1
with no gaps, and that no duplicate
(chain_id, source_kind, source_id)
tuples exist. Sequence violation produces
SEQ_INVALID; duplicate source produces
DUPLICATE_SOURCE.¶
Step 5: Validate Tips.
For each tip, locate the entry at tip_seq and confirm
tip_chain_hash equals that entry's
chain_hash. If sig is present, resolve
key_id to a public key using the deployment's
documented key-resolution policy (key resolution and trust
are deployment-defined, see Section 7.5) and
verify the Ed25519 signature over the tip_chain_hash
hex bytes. A valid signature proves possession of the
resolved key, not that the key was authorized. Hash mismatch
produces TIP_MISMATCH; signature failure produces
TIP_SIGNATURE_INVALID.¶
| Code | Meaning |
|---|---|
SCHEMA_INVALID
|
Entry failed Step 0 well-formedness validation (schema, duplicate members, non-NFC strings, reserved "::" in chain_id) |
VERSION_DOWNGRADE
|
An entry declares a lower ward_version than an earlier entry (version monotonicity) |
GENESIS_MISMATCH
|
Computed genesis hash does not match entry seq=1 prev_chain_hash |
CHAIN_HASH_MISMATCH
|
Recomputed chain_hash does not match stored value |
CHAIN_LINK_BROKEN
|
Entry's prev_chain_hash does not match previous entry's chain_hash |
SEQ_INVALID
|
Sequence numbers are not monotonically increasing from 1 |
DUPLICATE_SOURCE
|
Same (chain_id, source_kind, source_id) appears more than once |
TIP_MISMATCH
|
Tip's chain_hash does not match the entry at tip_seq |
TIP_SIGNATURE_INVALID
|
Tip signature does not verify against the declared key |
INCOMPLETE_CHAIN
|
(PARTIAL) Chain verified up to a point, but later entries are unavailable |
All codes except INCOMPLETE_CHAIN accompany the
BROKEN status; INCOMPLETE_CHAIN accompanies
PARTIAL.
Implementations MAY define additional reason codes; a consumer
that encounters an unknown code MUST treat it according to the
status field, never as a success.¶
| Status | Meaning |
|---|---|
INTACT
|
Chain verified successfully: all hashes, linkage, ordering, and tips are correct |
BROKEN
|
Verification failed: at least one check did not pass |
PARTIAL
|
Chain is valid up to a point but incomplete |
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.¶
INTACT result:¶
{
"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": []
}
¶
BROKEN result:¶
{
"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"
}
}
¶
An implementation is WARD-W conformant if it:¶
genesis_hash and chain_hash
correctly per Section 7¶
An implementation is WARD-V conformant if it:¶
An implementation is WARD-T conformant if it:¶
This section is non-normative. It provides implementation guidance for common storage backends.¶
SQLite with WAL mode is the recommended storage for single-node deployments. The following schema is suggested:¶
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)
);
¶
Storage sizing is approximately 500 bytes per SQLite row including indexes; 10,000 entries require approximately 5 MB.¶
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.¶
T1: Post-Hoc Tampering.
An attacker modifies a ward entry after it has been recorded.
The chain hash recomputation during verification detects the
modification (CHAIN_HASH_MISMATCH).¶
T2: Entry Deletion.
An attacker removes an entry from the middle of the chain.
Chain linkage breaks at the entry after the gap
(CHAIN_LINK_BROKEN) and the sequence gap is detected
(SEQ_INVALID).¶
T3: Entry Insertion. 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.¶
T4: Backdating.
An attacker assigns an earlier witnessed_at timestamp.
If tips are published to external sinks with their own
timestamps, the inconsistency is detectable. Without external
sinks, backdating is harder to detect.¶
T5: Double-Witnessing.
An attacker attempts to witness the same source event twice.
The uniqueness constraint
(chain_id, source_kind, source_id)
prevents this at the storage layer.¶
T6: Compromised Issuer. 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.¶
T7: Pre-Witness Modification. 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.¶
T8: Key Theft. 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.¶
WARD is designed as the fourth member of a family of complementary protocols for agentic AI governance:¶
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.¶
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.¶
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.¶
Hash Chain Integrity.
WARD's core security property derives from SHA-256 hash chaining.
Each entry's chain_hash depends on all preceding entries
via the prev_chain_hash 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 [RFC6234].¶
Hash-Input Unambiguity.
The integrity property additionally requires that no two
distinct field tuples produce the same hash input. The
canonical-JSON hash body of Section 7.4
guarantees this by construction: JSON string escaping makes
field boundaries unambiguous for every possible identifier
value. The legacy version 0.1 scheme
(Appendix A) lacked this property:
because identifiers carried no character-set restriction, a
crafted identifier containing the | 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.¶
Genesis Trust Anchor.
The genesis hash is deterministically derived from the
chain_id. Any party with knowledge of the
chain_id can independently compute the expected genesis
hash and verify the chain's origin. There is no secret material
in the genesis computation.¶
Content-Free Privacy. 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.¶
Confirmation Attacks on Low-Entropy Content. 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.¶
Tip Signing and External Sinks. 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 Section 12.4) 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.¶
Key Management. 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.¶
Denial of Service. 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.¶
Layered Defence. WARD is most effective as part of a layered defence with AEE [AEE], AOCL [AOCL], and VOLT [VOLT]. No single protocol provides complete security; together they provide message integrity (AEE), policy enforcement (AOCL), evidence recording (VOLT), and tamper-evident witnessing (WARD).¶
This document has no IANA actions.¶
Entries with ward_version "0.1" computed
chain_hash by delimiter concatenation:¶
chain_hash = SHA-256( prev_chain_hash + "|" + chain_id + "|" + seq + "|" + ward_entry_id + "|" + timestamp + "|" + source_kind + "|" + source_id + "|" + payload_hash )¶
All values are UTF-8 strings concatenated with pipe (|)
separators. The seq value is the decimal string
representation of the sequence number with no leading zeros.
The timestamp value is the ISO 8601 UTC string from the
entry's witnessed_at field. The issuer_id and
ward_version fields are not part of the version 0.1
hash input.¶
This scheme is deprecated for the reason described in
Section 7.4 and in the Security Considerations:
identifiers containing the | 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
(VERSION_DOWNGRADE), and MUST append a warning to the
verification result when a version 0.1 entry's
ward_entry_id or source_id contains the
| character.¶
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.¶