MetamorphicLog.Ingest (metamorphic_log v0.1.0)

Copy Markdown View Source

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

dedup_key_from_record(namespace, payload_b64)

@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)

dedup_key_from_token(namespace, token_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.

entry_bundles_to_flush(old_size, new_size)

@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}.

parent_hash(tile_hashes_b64)

@spec parent_hash([String.t()]) :: {:ok, String.t()} | {:error, String.t()}

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.

partial_width(level, size)

@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_root(leaf_hashes_b64)

@spec recompute_root([String.t()]) :: {:ok, String.t()} | {:error, String.t()}

Recompute the RFC 6962 Merkle root from an ordered list of base64 leaf hashes. Returns {:ok, root_b64} or {:error, reason}. Dirty CPU.

tile_hashes(level, index, width, bytes_b64)

@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.

tiles_for_size(size)

@spec tiles_for_size(non_neg_integer()) :: [String.t()]

Every tile path needed to represent a tree of size leaves.

tiles_to_flush(old_size, new_size)

@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}.