Deterministic ingestion / scale primitives for an Elixir operator pipeline.
These are the building blocks for operating a log (not just verifying one): content dedup keys, tile flush geometry, and Merkle recomputation over tile bytes. They are pure and side-effect-free — sequencing state and tile storage I/O stay on the BEAM side, which is the idiomatic split (NIFs do CPU-bound math; the BEAM owns state and I/O).
Sequencing & tile I/O live in Elixir
The Rust core's Sequencer and TileReader are intentionally not wrapped:
a sequencer is a per-namespace monotonic counter best kept as BEAM state
(e.g. an Agent/GenServer or a DB column), and tile reads are storage I/O.
Instead, your pipeline reads tile bytes from wherever they live and feeds them
to tile_hashes/4, parent_hash/1, and recompute_root/1 — the dirty-CPU
hashing primitives — to reproduce a root from tiles.
Binary values are base64-encoded; tile paths are returned as
tile/<level>/<index>[.p/<width>] strings.
Summary
Functions
Content dedup key for a payload under namespace, base64-encoded (64 bytes).
Token dedup key for an idempotency token under namespace, base64-encoded.
Entry-bundle paths that must be (re)written when growing from old_size to
new_size. Returns {:ok, [path]} or {:error, reason}.
Compute the parent hash above a full tile's 256 (or fewer, for a partial
tile) node hashes. Returns {:ok, hash_b64} or {:error, reason}. Dirty CPU.
Width (number of leaves, 1..256) of the partial tile at level for a tree
of size leaves; 0 if that tile is absent.
Recompute the RFC 6962 Merkle root from an ordered list of base64 leaf
hashes. Returns {:ok, root_b64} or {:error, reason}. Dirty CPU.
Parse the node hashes out of a tile's bytes.
Every tile path needed to represent a tree of size leaves.
Tile paths that must be (re)written when the tree grows from old_size to
new_size leaves. Returns {:ok, [path]} or {:error, reason}.
Functions
@spec dedup_key_from_record(namespace :: String.t(), payload_b64 :: String.t()) :: {:ok, String.t()} | {:error, String.t()}
Content dedup key for a payload under namespace, base64-encoded (64 bytes).
Domain-separated as SHA3-512_with_context("metamorphic-log/ingest-dedup-content/v1", lp(ns) || lp(payload)).
Example
{:ok, key} = MetamorphicLog.Ingest.dedup_key_from_record("acme", payload_b64)
@spec dedup_key_from_token(namespace :: String.t(), token_b64 :: String.t()) :: {:ok, String.t()} | {:error, String.t()}
Token dedup key for an idempotency token under namespace, base64-encoded.
Uses the metamorphic-log/ingest-dedup-token/v1 context.
@spec entry_bundles_to_flush(non_neg_integer(), non_neg_integer()) :: {:ok, [String.t()]} | {:error, String.t()}
Entry-bundle paths that must be (re)written when growing from old_size to
new_size. Returns {:ok, [path]} or {:error, reason}.
Compute the parent hash above a full tile's 256 (or fewer, for a partial
tile) node hashes. Returns {:ok, hash_b64} or {:error, reason}. Dirty CPU.
@spec partial_width(0..63, non_neg_integer()) :: 0..256
Width (number of leaves, 1..256) of the partial tile at level for a tree
of size leaves; 0 if that tile is absent.
Recompute the RFC 6962 Merkle root from an ordered list of base64 leaf
hashes. Returns {:ok, root_b64} or {:error, reason}. Dirty CPU.
@spec tile_hashes(0..63, non_neg_integer(), 1..256, String.t()) :: {:ok, [String.t()]} | {:error, String.t()}
Parse the node hashes out of a tile's bytes.
bytes is the base64-encoded tile blob; level/index/width identify the
tile. Returns {:ok, [hash_b64]} (each 32 bytes, base64) or
{:error, reason}. Runs on a dirty CPU scheduler.
@spec tiles_for_size(non_neg_integer()) :: [String.t()]
Every tile path needed to represent a tree of size leaves.
@spec tiles_to_flush(non_neg_integer(), non_neg_integer()) :: {:ok, [String.t()]} | {:error, String.t()}
Tile paths that must be (re)written when the tree grows from old_size to
new_size leaves. Returns {:ok, [path]} or {:error, reason}.