Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Resource Heap

The resource heap provides explicit runtime state tracking for choreography-layer resources. It stores resources under deterministic identifiers, tracks consumed resources in a nullifier set, and can derive Merkle commitments for the current heap state. This module lives in telltale_runtime::heap.

Scope

The heap is a telltale-runtime utility. It is not the same system as the type-level content addressing in telltale-types. The runtime heap uses the shared Hasher abstraction with its own canonical heap encoding and commitment logic.

The heap now has a focused Lean parity mirror. lean/Runtime/Resources/HeapModel.lean mirrors canonical bytes, tagged preimages, ordering rules, proof-index semantics, and deterministic replay for the published heap corpus. Digest computation remains Rust-side and is checked against published vectors rather than reimplemented in Lean.

Resource Identifiers

Resources are identified by ResourceId. Each ID stores a hasher digest and an allocation counter. The current default uses the shared BLAKE3 policy through DefaultContentHasher. The identifier is computed by hashing the canonical resource bytes plus the allocation counter.

#![allow(unused)]
fn main() {
pub struct ResourceId<H: Hasher = DefaultContentHasher> {
    hash: H::Digest,
    counter: u64,
}
}

This means the identifier is allocation-unique rather than pure-content-stable. Two identical resources allocated with different counters produce different ResourceId values. That behavior is intentional in the current runtime heap.

Resource Types

The heap stores four resource kinds.

#![allow(unused)]
fn main() {
pub enum Resource {
    Channel(ChannelState),
    Message(Message),
    Session { role: String, type_hash: u64 },
    Value { tag: String, data: Vec<u8> },
}
}

ChannelState stores sender, receiver, and an in-memory queue of MessagePayload. Message stores source, destination, label, payload bytes, and a sequence number. Session stores a role name and a type_hash value.

Heap Structure

The heap uses deterministic ordered containers.

#![allow(unused)]
fn main() {
pub struct Heap<H: Hasher = DefaultContentHasher> {
    resources: BTreeMap<ResourceId<H>, Resource>,
    nullifiers: BTreeSet<ResourceId<H>>,
    counter: u64,
}
}

BTreeMap and BTreeSet give stable iteration order. That stable order is important for reproducible commitments and proofs. The nullifier set records consumed resource IDs without deleting them from the resource map.

Heap APIs

The heap supports both functional and mutable update styles. The functional APIs return a new heap value. The mutable APIs exist for efficient in-place construction and updates.

#![allow(unused)]
fn main() {
use telltale_runtime::heap::{DefaultHeapHasher, Heap, Resource};

let heap = Heap::<DefaultHeapHasher>::new();
let msg = Resource::message("Alice", "Bob", "Ping", vec![1, 2, 3], 0);
let (msg_id, heap) = heap.alloc(msg);
let heap = heap.consume(&msg_id)?;
}

alloc(...), consume(...), remove(...), alloc_many(...), and consume_many(...) are the main functional operations. alloc_mut(...) and consume_mut(...) provide mutable variants. consume_many(...) validates the whole batch first and then nullifies all requested resources atomically.

read(...) only succeeds for active resources. If a resource has been consumed, read(...) returns HeapError::AlreadyConsumed. Use contains(...), is_consumed(...), is_active(...), active_resources(...), and consumed_ids(...) when you need explicit state inspection.

Size and Retention Semantics

size() returns the number of entries in the resource map. Consumed resources still count toward that number because consume(...) keeps them in the heap and only adds a nullifier. nullified_count() reports the number of consumed resource IDs.

remove(...) is the operation that actually deletes a resource from the heap. It also removes the resource from the nullifier set if it was already consumed. This makes remove(...) a cleanup operation rather than a normal consumption operation.

Merkle Commitments

The heap can derive Merkle commitments over its current state. MerkleTree::from_heap(...) builds a Merkle tree from active resources only. HeapCommitment::from_heap(...) combines the active-resource root, the nullifier root, and the allocation counter.

#![allow(unused)]
fn main() {
use telltale_runtime::heap::{HeapCommitment, MerkleTree};

let tree = MerkleTree::from_heap(&heap);
let proof = tree.prove(0);
let commitment = HeapCommitment::from_heap(&heap);
}

prove(...) generates an inclusion proof by leaf index in the current active-resource ordering. That ordering follows BTreeMap iteration over active ResourceId keys. resource_id_preimage(...), resource_leaf_preimage(...), nullifier_leaf_preimage(...), merkle_node_preimage(...), and heap_commitment_preimage(...) expose the tagged preimage rules directly. resource_leaf_hash(...), nullifier_leaf_hash(...), and merkle_node_hash(...) then hash those preimages. HeapCommitment::hash() then hashes a tagged preimage that contains the two roots and the counter.

Error Types

Heap operations use HeapError.

#![allow(unused)]
fn main() {
pub enum HeapError<H: Hasher = DefaultContentHasher> {
    NotFound(ResourceId<H>),
    AlreadyConsumed(ResourceId<H>),
    AlreadyExists(ResourceId<H>),
    TypeMismatch { expected: String, got: String },
    Other(String),
}
}

In the current implementation, NotFound and AlreadyConsumed are the main operational cases returned by heap methods. AlreadyExists and TypeMismatch are part of the public error vocabulary but are not heavily exercised by the basic heap operations shown above.

Encoding and Hashing Guarantees

The heap uses a versioned canonical binary format for resources. Every canonical heap value starts with the TTHP magic prefix, a little-endian encoding version, and a one-byte type tag. Strings and byte slices use explicit little-endian u32 length prefixes. Nested heap values are encoded as full canonical child values.

The heap also uses tagged preimages for ResourceId, resource-leaf hashes, nullifier-leaf hashes, Merkle internal nodes, and HeapCommitment::hash(). Those tagged preimages are part of the public heap contract.

Canonical Encoding Model

The heap uses a runtime-local canonical binary format. It does not reuse the type-level artifact encoding in Content Addressing. The current format is versioned by HEAP_ENCODING_VERSION.

Every canonical heap value starts with the same prelude. The prelude is the four-byte magic value TTHP, followed by the little-endian encoding version, followed by a one-byte type tag. The remainder of the byte sequence is the canonical body for that tagged value.

Strings and byte slices use explicit little-endian u32 length prefixes. Counters and numeric fields use little-endian integer encoding. Nested heap values are encoded as full canonical child values.

Covered Types

ChannelState encoding includes sender, receiver, queue length, and every queued MessagePayload in order. Message encoding includes source, destination, label, full payload bytes, and sequence number. Session and Value resources encode all of their current semantic fields.

The current canonical encoding also covers MessagePayload, ChannelState, Message, and Resource. Resource::Session includes role and type_hash. Resource::Value includes tag and full data.

Resource Identity

ResourceId remains allocation-unique in the current design. The heap computes it from a tagged preimage that contains the canonical resource bytes and the little-endian allocation counter. The digest uses the selected heap hasher, which defaults to DefaultHeapHasher.

This design keeps repeated allocations of identical semantic resources distinct. It also ties the ID contract directly to the canonical heap encoding boundary.

Merkle Leaves and Commitments

Active-resource leaves hash a tagged preimage that contains the ResourceId digest and the canonical resource bytes. Nullifier leaves hash a distinct tagged preimage that contains the ResourceId digest bytes. Internal Merkle nodes hash a tagged preimage that contains the left child digest and the right child digest. HeapCommitment stores the active-resource root, the nullifier root, and the allocation counter. HeapCommitment::hash() hashes a tagged preimage that contains those three values.

The allocation counter remains part of the authoritative commitment contract. This keeps the commitment aligned with allocation history rather than only current live content. The current heap model treats that history as semantically relevant.

Cross-Implementation Expectations

Another implementation should reproduce canonical bytes exactly. It should also preserve active-resource ordering and nullifier ordering exactly. The published heap test vectors are the conformance target for that behavior. The current published vector file is rust/runtime/tests/data/heap_vectors_v1.json.

The runtime heap now has a Lean parity mirror for canonical bytes and tagged preimages. That mirror lives in lean/Runtime/Resources/HeapModel.lean and is exercised through lean/Runtime/Tests/HeapParityRunner.lean.

The digest algorithm itself is still treated as an operational conformance target. Rust and Lean must agree on the published heap vectors exactly, but Lean does not currently reimplement BLAKE3. This section therefore defines both the boundary that an external embedder must match and the boundary that the current Rust↔Lean heap lane enforces.

See Rust-Lean Bridge and Parity for the Lean mirror boundary.

Determinism Contract

Heap operations are deterministic by contract. Active-resource ordering follows ResourceId ordering in the heap BTreeMap. Consumed-resource ordering follows the same ResourceId ordering in the nullifier BTreeSet. Merkle proof indices refer to that exact active-resource order.

Ordering Rules

Active resources are ordered by ResourceId. ResourceId ordering compares digest bytes first and allocation counter second. Heap::active_resources() uses that order because the heap stores resources in a BTreeMap.

Consumed resource IDs are ordered by the same ResourceId ordering. Heap::consumed_ids() uses that order because the heap stores nullifiers in a BTreeSet. Merkle leaves follow the iteration order of active_resources(). Merkle proof indices refer to that exact active-leaf order.

Replay Contract

Repeated executions of the same heap operation sequence must yield the same ResourceId values, proof paths, and HeapCommitment values. This contract is sequence-sensitive. If allocation order changes, the allocation counter changes. That changes ResourceId values and therefore changes commitments.

Commitment Authority

HeapCommitment is the authoritative cryptographic summary of heap state. HeapCommitment::hash() is the only supported single-value digest for that state. The heap no longer exposes a separate debug-only state fingerprint.

Order-Insensitive Operations

Some operations are deterministic even when their input lists are permuted. consume_many(...) is one example. If the same set of resource IDs is consumed successfully, the resulting nullifier set and heap commitment must match regardless of argument order.

Scope

This contract now has a focused Lean mirror. lean/Runtime/Resources/HeapModel.lean mirrors active/nullifier ordering, proof-index semantics, and deterministic replay for the published heap corpus. The published vectors and the strict Rust↔Lean heap parity suite are the current conformance boundary for other implementations.

Use Content Addressing for the type-level artifact identity system. Use Choreography Effect Handlers for choreography runtime integration. See Rust-Lean Bridge and Parity for the Rust↔Lean conformance lane.