Aqua ProtocolAqua Protocol
Aqua Protocol
Aqua ProtocolAqua Protocol, an open protocol for verifiable data provenance and accountability. Developed in the open as a proposed standard, with public specification, documentation, and reference implementations.

Documentation

Welcome to Aqua ProtocolQuick StartCore ConceptsRelease Status & Versioning
Developer Onboarding
aqua-rs-sdk-coreTemplate RegistryAudit Trails for AI Agents
Protocol Reference
Protocol ReferenceData ModelHashing and CanonicalizationTemplatesSignaturesAnchors and LinksSelective DisclosureVerification
Use Cases
Aqua Protocol Use CasesDocument VerificationIdentity AttestationSupply Chain Tracking

Documentation

Welcome to Aqua ProtocolQuick StartCore ConceptsRelease Status & Versioning
Developer Onboarding
aqua-rs-sdk-coreTemplate RegistryAudit Trails for AI Agents
Protocol Reference
Protocol ReferenceData ModelHashing and CanonicalizationTemplatesSignaturesAnchors and LinksSelective DisclosureVerification
Use Cases
Aqua Protocol Use CasesDocument VerificationIdentity AttestationSupply Chain Tracking
Docs
Schema Reference
Templates

Templates

Templates declare types in Aqua Protocol v4: identity by content hash, derivation and narrowing, bounds, template resolution, compute declarations, and the shipped catalog

13 min read

A template declares a type. It is itself a revision: a JSON Schema for the payloads of object revisions of that type, plus optional derivation metadata, optional shape bounds, and an optional compute declaration. Templates are identified by content hash — there are no template names on the wire; names are a resolution convenience only.

The normative source is the protocol specification in the aqua-rs-sdk-core repository; this page is the readable reference. Where they disagree, 03 — Templates wins.

Template identity

The identity of a template — its template hash — is the hash of its own canonical form (Hashing and Canonicalization), computed always with SHA3-256, regardless of any tree's algorithm and regardless of the template's own method field. The identity is used in two renderings:

  • the bare digest (32 bytes) in catalogs and ledgers;
  • the full multihash (0x1620…) everywhere on the wire: as the naming value (revision_type) of object revisions, in derives_from and ancestry, in anchor structural links, and as the template's key in its own tree.

Because the identity is a content hash, any change to a template — schema, bounds, description strings, nonce, local_timestamp — mints a new type. Template authors MUST therefore pin nonce and local_timestamp to fixed literals; a freshly generated value would create a new type identity on every build.

Template trees

A template is distributed as a single-revision tree: one template revision keyed by its full SHA3-256 multihash, with no anchor and no previous_revision. Derivation hierarchy is expressed inside the template, never through chain structure — chaining a template to any parent revision would change its content hash and destroy its identity.

A template revision keyed under a BLAKE3-256 multihash is invalid: identity is SHA3-256, so hash verification fails.

template_meta and the bootstrap

template_meta is the template-of-templates. Every template revision declares as its naming value (revision_type) the full multihash of template_meta:

Code
text
10x1620f3040850a8836717dd73e87d046723e11f9e9870e3b2e246803ad842fbf01155

with exactly one exception: template_meta itself cannot name its own content hash, so it declares the genesis bootstrap hash — the SHA3-256 multihash of the ASCII string aqua:genesis:template_meta:

Code
text
10x162087ea911a93f2698563b68b860f33fd7a568ca2391d4a227b532812d496039e74

Both values classify a revision as a template. template_meta's schema constrains the body of templates (their schema, verification, derives_from, ancestry members).

Derivation

A template MAY derive from another template:

  • derives_from = the parent's full multihash. A template MAY instead name template_meta itself as its derives_from; that marks it as a family root, not a derived template, and contributes nothing to ancestry.
  • ancestry = the derivation chain within the template's family, [root, …, parent], root first, where the family root is the nearest ancestor with no family parent. template_meta never appears in an ancestry. The last element MUST equal derives_from.
  • ancestry MUST NOT exceed 3 entries — the maximum derivation depth is 4 (a family root plus three levels of children). Derivation depth is the length of ancestry; family roots have depth 0.

Narrowing

Narrowing is a derivation-time discipline for refinement templates: a child derived under it admits only payloads its parent also admits. The rules, checked property-wise on the two schemas:

  1. No new properties. Every key in the child's properties MUST exist in the parent's properties.
  2. No dropped requirements. Every entry of the parent's required MUST remain in the child's required, or — if relaxed to optional — MUST still appear in the child's properties.
  3. Constraint tightening. For each property present in both schemas, upper bounds (maxLength, maximum, exclusiveMaximum) may only decrease, lower bounds (minLength, minimum, exclusiveMinimum) may only increase, a child's enum MUST be a subset of the parent's (or be replaced by a const drawn from it), a parent's const MUST be repeated identically, and a parent's pattern or bound keyword may be dropped only in favor of a const. A keyword only the child declares is always a valid tightening.
Narrowing is not re-checked at verification

Narrowing is checked when the child template is produced, and it is not re-checked at verification. On the wire, derives_from and ancestry assert family membership, not schema subsumption — consumers MUST NOT infer payload compatibility from lineage. In particular, the shipped signature and audit families are extension families: their concrete members add properties to a minimal abstract parent and were not produced under the narrowing discipline.

Closed payload schemas are what keep types analyzable: template schemas SHOULD declare additionalProperties: false at every object level whose member set is fixed. An open level defeats narrowing analysis for everything beneath it.

Bounds

A template MAY declare bounds — shape limits for trees of its type. Bounds are part of the template body and therefore part of its identity hash. When bounds is present, all six members MUST be present:

MemberTypeLimits
max_chain_depthu16maximum chain depth
structural_linksobjectrequired (u8) and max (u8) structural links
max_signature_branchesu8signature branches per node
max_timestamp_branchesu8timestamp branches per node
max_anchor_branchesu8anchor branches per node
max_total_revisionsu16revisions per tree

Resolution is nearest-declaration-wins: a template's effective bounds are its own declaration if present; otherwise the nearest ancestor's declaration, walking ancestry from the direct parent toward the root; otherwise the permissive default:

Code
json
1{
2 "max_chain_depth": 64,
3 "structural_links": { "required": 0, "max": 4 },
4 "max_signature_branches": 8,
5 "max_timestamp_branches": 4,
6 "max_anchor_branches": 4,
7 "max_total_revisions": 1024
8}

Protocol ceilings cap every declaration; no template can raise them:

CeilingValue
structural links per anchor64
compositional links per anchor64
compositional links with role reference per anchor64
chain depth256
branches per node256
revisions per object tree4096

Enforcement status. In the core profile, declared bounds and the ceilings above are normative limits on producers, but their enforcement is advisory: the core verification procedure does not count revisions, branches, or links against them. Runtime environments that admit revisions incrementally SHOULD enforce effective bounds at admission time; verifiers MAY enforce bounds and ceilings and reject trees that exceed them.

Typing object revisions

An object revision claims its type through its naming value: revision_type = the template's full multihash. Verification of an object revision requires both:

  1. the naming value equals the resolved template's identity (recomputed, not trusted); and
  2. the object's payloads conforms to the template's schema (JSON Schema draft 2020-12).

Template resolution

Templates are resolved in this order, identical at creation and verification:

  1. the tree's own revisions (an embedded template revision);
  2. the verifier's catalog of built-in templates (keyed by bare SHA3-256 digest);
  3. supplied linked trees / template sources.

At creation, typed trees reference their template via the genesis anchor's structural links and do not embed it; embedding happens only in export (Self-descriptive artifacts).

Three failure conditions arise from resolution and validation:

ConditionMeaningstrictofflinedebug
TEMPLATE_NOT_FOUNDthe object's template is unresolvableFailWarnWarn
ANCESTOR_TEMPLATE_NOT_FOUNDthe template resolves but an ancestor in its derives_from chain does notFailFailWarn
SCHEMA_VALIDATION_FAILEDthe template resolves and the payload does not conformFailFailFail — never policy-relaxable

A verifier MAY recognize an unresolvable hash as a known template of a richer profile it does not implement and say so in its diagnostics, but the machine-readable outcome MUST remain TEMPLATE_NOT_FOUND — a verifier is never more permissive because the missing template is known-missing. ANCESTOR_TEMPLATE_NOT_FOUND exists because a child MUST NOT silently bypass invariants declared by a parent it cannot see.

Compute declarations

A template MAY carry a verification member declaring computations to be executed by a WASM runtime (state machines over tree content). Its wire shape:

MemberRequiredContent
computationsyesarray of computation objects (below)
host_dependenciesyes (MAY be empty)capability names
statesyesstate names
terminal_statesomitted when emptyterminal state names

Each computation object carries wasm (hex of the compiled WASM module bytes; 0x prefix optional) and wasm_hash (SHA3-256 digest of the WASM bytes), plus OPTIONAL source (inline code, its SHA3-256 hash, and a language identifier), build (a build recipe), and description. Unknown members MUST be rejected at every level of the declaration.

The core profile does not execute compute. Its obligations are:

  • Static validation. Whenever a template carrying a verification member is itself verified as a revision, each computation's hex-decoded wasm MUST NOT exceed 2 MiB (the size gate applies to the hex length before decoding) and its SHA3-256 digest MUST equal the declared wasm_hash; when source is present, its code MUST NOT exceed 512 KiB and the SHA3-256 digest of its UTF-8 bytes MUST equal the declared source.hash. Digest comparisons tolerate an optional 0x prefix and are case-insensitive.
  • Fail-closed refusal. When verifying an object revision whose resolved template chain carries any verification member, a core-profile verifier MUST fail that revision with COMPUTE_UNSUPPORTED — it has no runtime and refuses to guess. Verification of such trees requires a full-profile verifier.

No template in the shipped catalog carries a compute declaration, so this rule fires only for third-party templates.

The shipped catalog

The core profile ships 19 templates. Their identities (bare SHA3-256 digests) are normative:

TemplateIdentity (bare digest)Role
template_meta0xf3040850a8836717dd73e87d046723e11f9e9870e3b2e246803ad842fbf01155template-of-templates
anchor_template0x479a304927c47f4308d027a858060ce287a9bdb45f2203f8130574a73511e899naming value of anchor revisions
file0x00f3abb3d74fc9dfc2b961cea906b3211716188f4de6f588180fdfcbbfa3fe53file-content genesis payloads
signature_base0xbdc93b4152c0163e40d3bf8cb956e235f0150731c1e8c8b50d9c3b7abe50e4a7abstract parent of the signature suites
signature_ed255190xbaf1d5d47eef50dcde3931956879bb30c5580064a92ce43ed4c6bd8b878b659aEd25519 signature revisions
signature_eip1910x57090c9095a2e9af36e9b6cb4574196fa973c44a210e703bd15dab2623dbd370EIP-191 signature revisions
signature_p2560x23a2cdd4618224a67235321e2dfeffac9ab1809175549d8b5402dd5c5376d81cECDSA P-256 signature revisions
signature_webauthn0x2cdea1604c08b4e23f5415d8fcf885cf86d0a3e017cf4b7989c41aa99f3f2188WebAuthn signature revisions
audit_artifact0x431668e53b2181311ec43db30ff4d4cf738059051829a5a4f3398c22440a16f3abstract root of the audit family
audit_user_turn_marker (T1)0x9bf38992cb2cc1230edb6539a98e6d3c889c69aa09b1764a4b91b30cb6a36990opens a user turn; its revision hash is the turn id
audit_user_prompt (T2)0x80143d8018fa7a0fa959c115ff7bf7da0d3363192db5202f2904b4626559ecc7the user's prompt
audit_agent_thinking (T3)0xfe1d5fd50335d5d21f3f1976baadc9eab68a0506241ed5303bb8ad0a25a407f1agent reasoning
audit_agent_tool_call (T4)0x13d08a7ea2a0dc5f4d5a380d9dd9456ec10392bc752f152517dd227bb272592fa tool invocation
audit_api_response (T5)0x6d1e300bbb0145cedc1a2ff19fd1ad0e0dcfcfde6a4a9b0d6ea67c7d31c4a129an attested API response
audit_tool_result (T6)0xad46f51cfce3fd961acb2a3d9047b4c217c3da43c32addc41e8e11b84b5c4732a tool result
audit_hitl_approval (T7)0xcd588f6994409f3088eef84d231027ba69a1d9d7193fcb1bf0d07a1716ba6560a human-in-the-loop decision
audit_agent_response (T8)0xe36f3af11b2c6d5c94e2862e4a66db016ff8e8f5486fe72594daa7df480c5413the agent's response
audit_round_anchor0xf174f2f669d102d3d74efb80248a08485fe2c40d18b2df5ccc7d225e794b63e7Merkle commitment closing a turn
audit_session_close0x9069525103b9408f039a41454d25ed5ffc59aed9e46224cb19864f2e71e0c23bcloses an audit session

Of the 19, 14 are resolvable by naming value in a core-profile verifier's catalog: file, the four concrete signature templates, audit_artifact, and T1–T8. The remaining five (template_meta, anchor_template, signature_base, audit_round_anchor, audit_session_close) are shipped and hash-pinned but deliberately outside the resolution catalog: nothing resolves an object's type through them by default. Trees using audit_round_anchor or audit_session_close carry or link their templates like any registry template.

The eleven audit templates form one family rooted at audit_artifact (each concrete template derives from it with an ancestry of exactly one entry). They give AI-agent workflows verifiable, per-turn audit trails; see Audit Trails for AI Agents. This family is a deliberate, bounded fork of the audit family published by richer Aqua profiles: the constraint content is identical, but the ancestry is re-rooted at audit_artifact, so all eleven identities differ from their full-profile counterparts. A verifier presented with a full-profile audit hash resolves nothing and reports TEMPLATE_NOT_FOUND.

Distribution. The sanctioned distribution channel for the audit family is the template registry (aqua-template-registry); the catalog resolvability of audit_artifact and T1–T8 is a transitional convenience, not a protocol guarantee. Producers MUST NOT rely on any verifier's catalog beyond the machinery templates; the interoperable way to ship a tree is self-descriptive export or explicit template sources.

Authoring your own template

The condensed flow, from the SDK's template authoring guide (data-only templates; WASM-carrying templates require the full aqua-rs-sdk):

Write the payload schema

A standard JSON Schema (draft 2020-12) for your payload. Set "additionalProperties": false on every object level, and constrain everything you can (pattern for DIDs, minimum for counters, maxLength for strings).

Write the template JSON

revision_type is always the template_meta multihash; method is the default hashing method for objects of this type (tree enables field-level selective disclosure, scalar is cheaper). Pin nonce and local_timestamp to fixed literals — pick the values once, commit the file, never touch it again. A derived template adds derives_from and ancestry.

Compute and pin the hash

Compute the template's canonical SHA3-256 hash once and pin it in your code, guarded by a self-check test that recomputes the hash from the template JSON and fails when it drifts. Any edit to the file mints a new type and orphans every artifact of the old type.

Create objects with the validated path

Use create_object_validated with your template tree as an explicit source. Plain create_object validates payloads only against the built-in catalog, so for a custom template it would silently build a revision from a payload that violates your schema. The validated path fails closed: TemplateNotFound, AncestorTemplateNotFound, or SchemaViolation with per-field errors.

Ship with self-descriptive export

export_tree embeds every referenced template and its full ancestry into the exported tree, so it resolves its own types anywhere. It fails closed on unresolvable templates; run the missing_templates lint first (or in CI).

Publish through the registry

For consumers beyond receivers of your exported trees, publish the template tree through the template registry under your publisher DID. Consumers retrieve it hash-pinned through the registry's fail-closed trust layer and pass it to the SDK as an explicit template source.

Self-descriptive artifacts

A tree can be exported as a self-descriptive artifact: a tree into which every referenced template — and every ancestor of every referenced template — is embedded as an ordinary template revision, keyed by its full multihash. Five normative rules:

  1. Closure. The embedded set is the transitive closure over each typed revision's naming value plus each embedded template's derives_from/ancestry; signature, anchor, and template revisions contribute no naming values (they dispatch on machinery templates).
  2. Fail-closed. If any template in the closure cannot be resolved, the export MUST fail with the unresolved identities; partial embedding is not permitted.
  3. Idempotence. Exporting an already self-descriptive artifact MUST be a no-op; already-embedded templates are not duplicated.
  4. Receiver-relative built-ins. "Built-in" is relative to the receiver, not the sender: a self-descriptive export embeds catalog templates too, so the artifact verifies on receivers with a different (or empty) catalog; an exporter MAY offer an opt-out at the cost of that portability.
  5. The missing-templates lint. The same closure walk without embedding yields the set of unresolvable template identities; publishers SHOULD run it before shipping a tree — a non-empty result means receivers will hit TEMPLATE_NOT_FOUND.

Embedding changes no revision: templates enter the artifact under their own identities, and every existing hash and signature remains valid.

See also

  • Hashing and Canonicalization — how the template hash is computed
  • Data Model — revision kinds and how revision_type classifies them
  • Verification — policies, stages, and the error vocabulary
  • Template Registry — publishing and retrieving templates by hash
  • Audit Trails for AI Agents — the audit template family in practice
Edit this pageReport an issue
Previous
Hashing and Canonicalization
Next
Signatures

Documentation

  • Getting Started
  • Protocol Reference

Community

  • GitHub

Copyright © 2026 inblock.io assets GmbH. All rights reserved.

On this page

Template identityTemplate trees`template_meta` and the bootstrapDerivationNarrowingBoundsTyping object revisionsTemplate resolutionCompute declarationsThe shipped catalogAuthoring your own templateSelf-descriptive artifactsSee also