Skip to Content
Data Structures

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 signatures resolves to a key active at that envelope’s own entry index and verifies over JCS(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: and hmac-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_id derives from pubkey and is recomputed rather than trusted. The array IS the producer key state at the manifest’s entry index: it discards the prior snapshot, and later key statements 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".

AHL Evidence Receipt container

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 its source and its binding. A manifest-chain key MUST match a key object in the manifest version its binding names, on every member the two objects share; a producer key’s key_id is additionally recomputed from its pubkey. keys.producer is 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’s tree_size. A key a later manifest version retired therefore cannot validate a checkpoint issued under that later state.
  • anchoring.checkpoint is a superset of the committed state {log_id, tree_size, root_hash, checkpoint_time}, adding key_id and signature so the state can be authenticated offline. Where raw is carried it MUST parse to the same values as the JSON members, which govern; a mismatch is invalid.
  • anchoring.later_witnesses[] is present if and only if later_checkpoint is carried, and cosigns later_checkpoint itself. At L3 a receipt asserting continued_history MUST carry at least one element that verifies. A producer MUST NOT assert continued_history where 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 ascending manifest_entry_index order. Its checkpoint verifies under the outgoing log key, its tree_size exceeds the rotating manifest’s entry index, its inclusion_path runs to that rotation checkpoint’s root, and at L3 at least one of its witnesses cosignatures verifies under the outgoing witness set. Omitting a required element is invalid, 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.typeProvesRequired claim_material
statement-anchoredThe 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-ingestedA record was introduced by the subject ingestion statementrecord_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-derivedOne output record’s derivation is committed by the subject derivation, batched or notThe 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-declaredA trigger naming the record is anchored and its envelope is valid, signed by the declared issuerAn embedded record-ingested / record-derived receipt for the trigger’s record, plus one for the replacement in the case of a correction
trigger-effectiveAdditionally: at least one verified signer held authority at the trigger’s entry index, and this trigger governs at checkpoint Ctrigger-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-declaredA propagation statement anchored under the declared chain dispositions the recordAn 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-effectiveAs above, with an embedded trigger-effective receiptSame material; REQUIRES governance: "enumerated"
propagation-completeThe affected set equals the closure recomputable at the propagation’s declared corpus checkpoint D, never at a later oneThe 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-stateThe manifest and key state at a target index is exactly the presented chaintarget_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 — declared proves chain validity from genesis only; the active governance state at the subject’s index is not independently proven. enumerated adds an authenticated range enumeration over exactly [0, tree_size(C)), which forecloses omission. Only enumerated mode earns the word “active”.
  • competing_triggers — not-checked, or enumerated, 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_witnesses and a consistency proof are present and verify; at L3 at least one of those cosignatures verifies.
  • content_binding — none, plain-verified, or keyed-authorized (recomputed with a verifier-held dataset key). In both non-none cases 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 where content_binding is not none, absent otherwise: private-use where the carried identifier begins x-, public otherwise. 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.

Last updated on