Data Structures
Concrete wire shapes for AHL revision 0.4. Normative definitions live in The Protocol; every section below cites the Internet-Draft section it renders, and where the two differ the I-D governs. The shapes shown are the ones the reference implementation emits — see the corpus described in Test Vectors.
1. Statement Envelope
Every AHL statement is a signed envelope (Protocol §2.1). payload carries one of the seven statement types; signatures binds it to producer keys.
{
"payload": {
"ahl_version": "0.4",
"type": "derivation",
"producer": "acme-lending",
"manifest": "sha256:1122...",
"valid_time": "2026-08-01T00:00:00Z",
"issued_at": "2026-08-01T00:03:12Z"
},
"signatures": [
{ "key_id": "sha256:4433...", "sig": "base64:SGVsbG8..." }
]
}- Statement id =
SHA-256(JCS(payload))— the reference used inside the statement graph (derivation.inputs[].statement, trigger targets). - Entry id =
SHA-256(JCS(envelope))— the retrieval key for the anchored log entry, and the reference used where anchoring identity matters: manifest and key statements reference each other by entry id, and the genesis manifest’s entry id is the corpus trust anchor. - Envelope signature rule. An envelope is valid only if EVERY entry in
signaturesresolves to a key active at that envelope’s own entry index and verifies overJCS(payload). One non-verifying entry invalidates the envelope however many others verify, and a verifier MUST NOT accept a subset. An envelope failing the rule is VOID. - Family strings.
sha256:andhmac-sha256:carry exactly 64 lowercase hex digits;base64:carries the standard alphabet of RFC 4648 §4 WITH padding. Acceptance is strict and is never more permissive than emission; a failure is a schema failure and, in a receipt,invalid.
ahl_version is REQUIRED in every payload and is 0.4. A carried statement declaring any other value is unverifiable, never invalid; an absent or malformed value is invalid (Protocol §2.2).
2. Ingestion (JSON)
Protocol §2.4.1.
{ "type": "ingestion",
"dataset": "bureau",
"record": "hmac-sha256:9f86d081...",
"origin": "bureau-feed-2026-08" }3. Derivation (JSON)
Protocol §2.4.2.
{ "type": "derivation",
"pipeline": "credit-decision-v3",
"outputs": [
{ "dataset": "decisions", "record": "sha256:7f83b165...", "locator": "s3://decisions/2026/08/d-142.json" }
],
"inputs": [
{ "dataset": "bureau", "record": "hmac-sha256:9f86d081...", "role": "feature",
"statement": "sha256:514a00bd..." },
{ "dataset": "applications", "record": "sha256:a1b2c3d4...", "role": "reference" }
],
"transform": {
"code": { "digest": "sha256:c0ffee01...", "type": "git_commit" },
"model": { "digest": "sha256:4010a9f4...", "version": "credit-v4.2" },
"params": { "digest": "sha256:9988aabb..." }
} }Closure traversal uses the (dataset, record) pair only; representation and projection narrow what was consumed and never create identity. Batch form replaces outputs with outputs_root / outputs_count / leaf_format: "ahl-leaf-v2" — see §9.
4. Retraction and Correction (JSON)
Protocol §2.4.3.
{ "type": "retraction",
"dataset": "bureau",
"record": "hmac-sha256:9f86d081...",
"scope": { "effective_from": "2026-09-15T00:00:00Z", "retroactive": true },
"reason_code": "error" }{ "type": "correction",
"dataset": "bureau",
"record": "hmac-sha256:9f86d081...",
"replacement": "hmac-sha256:eeff2233...",
"scope": { "effective_from": "2026-09-15T00:00:00Z", "retroactive": true },
"reason_code": "error" }scope is REQUIRED; a scopeless trigger is malformed. Authority is tested only after the envelope is valid: a trigger is effective if and only if at least one of its verified signers holds a key in the record’s authority key set, active at the trigger’s own entry index. A valid trigger with no such signer anchors as a challenge — surfaced by verification, never traversed by closure. An envelope that does not verify is void and is never a challenge.
5. Propagation (JSON)
Protocol §2.4.4.
{ "type": "propagation",
"trigger": "sha256:trigger-statement-id...",
"corpus_checkpoint": { "log_id": "sha256:9988...", "tree_size": 1450, "root_hash": "sha256:7f83..." },
"affected_root": "sha256:disposition-tree-root...",
"affected_count": 12,
"complete_relative_to_manifest": true }Disposition leaf (one per affected record, JCS-canonical):
{ "dataset": "decisions", "record": "sha256:7f83b165...",
"disposition": "recomputed",
"successor_statement": "sha256:successor-derivation-id..." }disposition is one of invalidated, recomputed (REQUIRES successor_statement, whose inputs MUST include the correction’s replacement), or assessed_unaffected (REQUIRES assessment, the SHA-256 of a retained assessment artifact). The declared corpus_checkpoint MUST commit the trigger’s entry, and completeness is defined at that checkpoint D and nowhere else.
6. Manifest and Key (JSON)
The manifest payload IS the corpus manifest object at top level, with no wrapper member and with the manifest common field absent — see §10 below. key statements record producer key-set transitions (Protocol §2.4.6):
{ "type": "key", "action": "add",
"key": { "key_id": "sha256:4433...", "pubkey": "base64:MCowBQYD...", "valid_from": "2026-01-01T00:00:00Z" } }action is exactly one of add or retire; there is no third value and no default. key_id MUST equal sha256: plus the lowercase hex SHA-256 of the raw 32 octets pubkey decodes to, and a verifier recomputes it. valid_from is informative — it never orders anything — but it remains a REQUIRED member with a defined form. A key statement never changes log or witness keys; those rotate only by anchoring a new manifest version.
7. Commitments
Protocol §2.6. Record identity is a commitment, computed per the dataset’s declared mode over a preimage that binds the canonicalization descriptor:
ddig = SHA-256( JCS(descriptor) ) // raw 32 octets, not a family string
plain = SHA-256( dsid || 0x1F || ddig || 0x1F || canonical bytes )
keyed = HMAC-SHA-256( k_dataset, dsid || 0x1F || ddig || 0x1F || canonical bytes )The descriptor is {canonicalization, media_type?}; media_type is present if and only if that identifier’s procedure requires it (exact-bytes does, jcs does not). dsid is the dataset id as US-ASCII octets: 1 to 128 characters, each in 0x21–0x7E, and never a control octet — a 0x1F inside a dataset id would make the preimage ambiguous and let two distinct records collide.
keyed is REQUIRED for personal or sensitive data; k_dataset is held by the producer and disclosed under the manifest’s key-access rule to authorized verifiers only, and is never packaged in a receipt. One descriptor and one mode apply to every statement referencing a given dataset.
The preimage is injective over the triple (dsid, ddig, canonical bytes) unconditionally, by parsing alone. That distinct descriptors yield distinct ddig, and that distinct preimages yield distinct commitments, hold only under the collision resistance of SHA-256 (Protocol §8.6).
8. Merkle Tree Rules
Protocol §2.7. All AHL trees — batch outputs, input sets, dispositions — share one construction:
LeafHash = SHA-256( 0x00 || leaf_bytes )
NodeHash = SHA-256( 0x01 || LeftChildHash || RightChildHash )Leaves are sorted by record — ascending lexicographic comparison of the UTF-8 bytes of the canonical commitment string — and duplicates are prohibited. Membership proofs are O(log n). The rules are identical for every AHL tree, so a set assembled in any other order opens a root of its own and is not an AHL tree. The complete leaf set of every committed tree is corpus material: at L3 it MUST be retrievable and enumerable in full, independent of the producer (Protocol §3.4).
The log tree itself is the adaptor profile’s, not AHL’s: its leaf construction, checkpoint serialization and proof encodings are fixed by the pinned profile document (Adaptor Profiles).
9. Batching
A batch derivation replaces outputs with three top-level members:
{ "type": "derivation",
"pipeline": "credit-decision-v3",
"outputs_root": "sha256:aabb1122...",
"outputs_count": 5000,
"leaf_format": "ahl-leaf-v2",
"transform": { ... } }Each batch leaf (ahl-leaf-v2, JCS-canonical) is:
{ "dataset": "decisions", "record": "sha256:7f83b165...",
"inputs": [
{ "dataset": "bureau", "record": "hmac-sha256:9f86d081...", "role": "feature" }
] }For wide input sets, inputs MAY be the compact form { "input_set_root": "sha256:...", "input_set_count": 3 }, itself a tree following the §8 rules — the shape an Evidence Receipt uses when it proves membership of a single input without carrying the full input list.
10. Corpus Manifest (JSON)
Protocol §6.2. Shape as the reference corpus emits it:
{ "ahl_version": "0.4",
"type": "manifest",
"producer": "producer-1",
"predecessor": "sha256:<entry id of the prior manifest — absent only for genesis>",
"keys": [ { "key_id": "sha256:3475...", "pubkey": "base64:iojj3XQJ..." } ],
"log": {
"log_id": "sha256:f96b...",
"operator": "log-operator-1",
"adaptor": { "id": "ahl-adaptor-atl-v1", "hash": "sha256:80a7de..." },
"checkpoint_cadence": "PT1H",
"cadence_epoch": "2026-08-16T11:30:00Z",
"witness_grace_period": "PT15M",
"keys": [ { "key_id": "sha256:b62e...", "pubkey": "base64:7Uko...", "valid_from_index": 0 } ]
},
"witnesses": [
{ "witness_id": "witness-1",
"keys": [ { "key_id": "sha256:c5b9...", "pubkey": "base64:ypOs...", "valid_from_index": 0 } ] }
],
"datasets": {
"customers": { "canonicalization": "jcs", "commitment_mode": "keyed",
"key_access": "authorized-verifiers-only",
"authority": { "producer": "producer-1", "key_ids": ["sha256:3475..."] } },
"scores": { "canonicalization": "jcs", "commitment_mode": "plain", "key_access": "not-applicable" }
},
"pipelines": { "include": ["scoring-v1"], "exclude": [] },
"windows": { "anchoring": "PT24H", "propagation": "P30D" },
"retention": { "statements": "P10Y" },
"level": "L3" }- The producer key object is
{key_id, pubkey}and nothing more — any further member is a schema failure.key_idderives frompubkeyand is recomputed rather than trusted. The array IS the producer key state at the manifest’s entry index: it discards the prior snapshot, and laterkeystatements modify it in entry order until the next manifest version. - Log and witness key objects carry
valid_from_index, because a manifest MAY declare such a key valid from an index later than its own. Each manifest version replaces both sets in full. A version that replaces either set is a governance-key rotation, and a receipt carrying it must additionally carry a rotation-anchoring proof under the outgoing states (§11). - Datasets declare a canonicalization descriptor, a commitment mode, a key-access rule and a dataset authority. An ingestion into an authority-less dataset is invalid.
- Pipelines are inclusion and exclusion lists — the scope boundary, and what makes coverage an auditable assertion rather than silence.
The manifest statement’s own statement id is the manifest version id every other statement’s manifest field references. A non-genesis manifest references its predecessor by entry id, not statement id, in a REQUIRED predecessor member forbidden for genesis.
11. AHL Evidence Receipt (JSON)
Protocol §7.1. A JCS-canonical JSON document, file extension .ahl, media type application/ahl-receipt+json. Receipts of this revision carry ahl_receipt_version: "2" and spec_version: "0.4.0".
claim.record_subject is type-specific, not universal: REQUIRED for record-*, trigger-* and disposition-*, and MUST be absent for statement-anchored and governance-state, which target an index instead. The example below carries it because its claim.type is record-derived.
Structure, condensed:
{
"ahl_receipt_version": "2",
"spec_version": "0.4.0",
"claim": {
"type": "record-derived",
"record_subject": { "dataset": "decisions", "record": "sha256:7f83b165..." },
"assurance": {
"governance": "declared",
"competing_triggers": "not-checked",
"witnessed": true,
"continued_history": false,
"content_binding": "none"
},
"note": "<informative only; never strengthens the claim>"
},
"subject": {
"statement_id": "sha256:<hex>",
"entry_id": "sha256:<hex>",
"entry_index": 142,
"manifest": "sha256:<manifest version statement id>"
},
"envelope": { "payload": { ... }, "signatures": [ ... ] },
"keys": {
"log": [ { "key_id": "sha256:<hex>", "pubkey": "base64:<...>", "source": "manifest-chain", "binding": { "entry_index": 57 } } ],
"witness": [ { "witness_id": "<id>", "key_id": "sha256:<hex>", "pubkey": "base64:<...>", "source": "manifest-chain", "binding": { "entry_index": 57 } } ],
"producer": [ { "key_id": "sha256:<hex>", "pubkey": "base64:<...>", "source": "manifest-chain", "binding": { "entry_index": 0 } } ]
},
"anchoring": {
"adaptor": { "id": "<profile id>", "hash": "sha256:<hex>" },
"checkpoint": { "log_id": "sha256:<hex>", "tree_size": 1450, "root_hash": "sha256:<hex>",
"checkpoint_time": "<RFC 3339>", "key_id": "sha256:<hex>",
"signature": "base64:<...>", "raw": "base64:<optional adaptor form>" },
"inclusion_path": [ "sha256:<hex>", ... ],
"witnesses": [ { "witness_id": "<id>", "key_id": "sha256:<hex>", "cosignature": "base64:<...>", "cosigned_at": "<RFC 3339>" } ],
"later_checkpoint": { ... },
"consistency_path": [ "sha256:<hex>", ... ],
"later_witnesses": [ { "witness_id": "<id>", "key_id": "sha256:<hex>", "cosignature": "base64:<...>", "cosigned_at": "<RFC 3339>" } ]
},
"governance": {
"genesis_entry_id": "sha256:<hex>",
"chain": [ { "envelope": { ... }, "entry_index": 0, "inclusion_path": [ ... ] } ],
"rotation_proofs": [ { "manifest_entry_index": 812, "checkpoint": { ... },
"inclusion_path": [ ... ], "witnesses": [ ... ] } ],
"currency": { "mode": "declared", "material": {} }
},
"claim_material": { "output": { "dataset": "decisions", "record": "sha256:7f83b165..." } }
}keys. Every key used in verification MUST appear here with itssourceand itsbinding. Amanifest-chainkey MUST match a key object in the manifest version its binding names, on every member the two objects share; a producer key’skey_idis additionally recomputed from itspubkey.keys.produceris derived from the governance chain and listed for convenience — it is verified against the chain, never accepted on the strength of the listing. For log and witness keys the binding MUST name the manifest version active for the checkpoint being verified: the manifest statement with the greatest entry index strictly smaller than that checkpoint’stree_size. A key a later manifest version retired therefore cannot validate a checkpoint issued under that later state.anchoring.checkpointis a superset of the committed state{log_id, tree_size, root_hash, checkpoint_time}, addingkey_idandsignatureso the state can be authenticated offline. Whererawis carried it MUST parse to the same values as the JSON members, which govern; a mismatch isinvalid.anchoring.later_witnesses[]is present if and only iflater_checkpointis carried, and cosignslater_checkpointitself. At L3 a receipt assertingcontinued_historyMUST carry at least one element that verifies. A producer MUST NOT assertcontinued_historywhere a manifest statement lies at an index in[anchoring.checkpoint.tree_size, later_checkpoint.tree_size)— an obligation a verifier cannot check from the receipt alone, and an auditor can.governance.rotation_proofs[]is REQUIRED if and only if the carried chain contains a governance-key rotation, one element per rotation in ascendingmanifest_entry_indexorder. Itscheckpointverifies under the outgoing log key, itstree_sizeexceeds the rotating manifest’s entry index, itsinclusion_pathruns to that rotation checkpoint’s root, and at L3 at least one of itswitnessescosignatures verifies under the outgoing witness set. Omitting a required element isinvalid, not a verifier capability gap.
12. Claim-Type Registry
Protocol §7.2. A receipt declares exactly one claim.type and proves exactly that claim. Types suffixed -declared carry governance: "declared" and their rendered verdicts never use the words “effective”, “governs” or “complete”; -effective and -complete types REQUIRE governance: "enumerated".
claim.type | Proves | Required claim_material |
|---|---|---|
statement-anchored | The subject envelope is anchored at its entry index under a checkpoint and signed under the declared manifest chain. Applies to any statement type | {} — no additional material |
record-ingested | A record was introduced by the subject ingestion statement | record_bytes (the record as received, base64) plus the complete descriptor, present iff content_binding is not none; the verifier canonicalizes and recomputes the commitment |
record-derived | One output record’s derivation is committed by the subject derivation, batched or not | The output object; for a batch, batch_leaf, leaf_index and leaf_path to outputs_root, plus input_members where the leaf uses the input-set form. output_bytes and descriptor on the same terms as above |
trigger-declared | A trigger naming the record is anchored and its envelope is valid, signed by the declared issuer | An embedded record-ingested / record-derived receipt for the trigger’s record, plus one for the replacement in the case of a correction |
trigger-effective | Additionally: at least one verified signer held authority at the trigger’s entry index, and this trigger governs at checkpoint C | trigger-declared material plus checkpoint_C — matching the receipt’s own verified checkpoint on {log_id, tree_size, root_hash} — and a competing-trigger enumeration over [0, tree_size(C)), or [introduction_index, tree_size(C)) where the embedded introduction fixes that bound. REQUIRES governance: "enumerated" and competing_triggers: "enumerated" |
disposition-declared | A propagation statement anchored under the declared chain dispositions the record | An embedded trigger-declared receipt, plus disposition_leaf, leaf_index and leaf_path to affected_root. leaf_index is REQUIRED: a Merkle path is verifiable only against the leaf’s position |
disposition-effective | As above, with an embedded trigger-effective receipt | Same material; REQUIRES governance: "enumerated" |
propagation-complete | The affected set equals the closure recomputable at the propagation’s declared corpus checkpoint D, never at a later one | The authenticated D — by a consistency proof to the receipt’s checkpoint or by recomputation of D’s prefix root from the enumerated prefix — the full prefix [0, tree_size(D)), the complete leaf material of every referenced batch, input-set and disposition tree, and an embedded trigger-effective receipt. No compact form exists, by construction |
governance-state | The manifest and key state at a target index is exactly the presented chain | target_index, with enumerated material proving no manifest or key statement exists in (subject.entry_index, target_index]. The subject MUST be the manifest statement claimed active at the target. REQUIRES governance: "enumerated" |
13. Assurance Fields
Protocol §7.3. These fields state what the producer asserts the receipt supports. They are written at issue time, they are static, and they are not a verdict.
governance—declaredproves chain validity from genesis only; the active governance state at the subject’s index is not independently proven.enumeratedadds an authenticated range enumeration over exactly[0, tree_size(C)), which forecloses omission. Only enumerated mode earns the word “active”.competing_triggers—not-checked, orenumerated, meaning every trigger for the subject record through C is carried and the governing one is established by entry index.witnessed— at least one valid witness cosignature on the inclusion checkpoint.continued_history—later_checkpoint,later_witnessesand a consistency proof are present and verify; at L3 at least one of those cosignatures verifies.content_binding—none,plain-verified, orkeyed-authorized(recomputed with a verifier-held dataset key). In both non-nonecases the verifier runs the canonicalization itself over the bytes as received, so what is established is “the record I hold canonicalizes to a preimage committing to C”.canonicalization_namespace— REQUIRED wherecontent_bindingis notnone, absent otherwise:private-usewhere the carried identifier beginsx-,publicotherwise. It names only the namespace and asserts nothing about registration.
A verifier renders its verdict from claim.type and assurance together with its own result — verified, invalid or unverifiable (Protocol §7.7) — and never from the assurance fields alone. An assurance the verifier could not evaluate is not thereby established, and no result is ever expressed by rewriting the receipt’s assurance fields.
See Reference Implementations for the library that generates and verifies this format, and Test Vectors for the corpus exercising every claim type.