Tamale. Space
(tamale v0.1.0)
Copy Markdown
A versioned identity space: an ordered set of element ids, an append-only op log, and nothing else.
- ids are stable and never reused — a deleted id stays dead, so a historical anchor can never resurrect onto a new element
- every write is an
Tamale.Opbatch; a batch is atomic and bumpsversionby one, appending one log entry - the Space holds no domain data — element payloads (notes, phonemes, frames) live in Caller-side tables keyed by id
The log doubles as the tombstone record: anchors carry at_version and
are transported along log[at_version..head] (Tamale.Transport), so
truncate/2 below the oldest live anchor version replaces
reference-counted GC. Single-writer assumption: one linear log, no
branching. (Concurrent/offline editing would reintroduce tombstones —
as a deliberate kernel extension, not a heuristic.)
Note: log is a plain list appended at the end (O(n) per batch). Fine
at scaffold scale; a production Space can swap in a queue without
changing semantics.
Summary
Functions
Applies a batch atomically: each op validates against the running state;
all land or none. On success bumps version and appends one log entry.
Applies a single op. Sugar for apply_batch(space, [op]).
Log entries with version > from_version, oldest first.
Whether id is currently active.
Creates a space. Genesis ids are active at version 0.
Raising variant of new/1, for tests and known-good genesis lists.
Drops log entries at or below oldest_live_version — the log-age
equivalent of GC. Anchors older than that horizon can no longer be
transported (log_from/2 reports :log_truncated).
Types
@type entry() :: {Tamale.version(), [Tamale.Op.t()]}
One log entry: the version a batch produced, and its ops.
@type t() :: %Tamale.Space{ base_version: Tamale.version(), ids: [Tamale.id()], log: [entry()], seen: MapSet.t(Tamale.id()), version: Tamale.version() }
Functions
@spec apply_batch(t(), [Tamale.Op.t()]) :: {:ok, t()} | {:error, term()}
Applies a batch atomically: each op validates against the running state;
all land or none. On success bumps version and appends one log entry.
@spec apply_op(t(), Tamale.Op.t()) :: {:ok, t()} | {:error, term()}
Applies a single op. Sugar for apply_batch(space, [op]).
@spec log_from(t(), Tamale.version()) :: {:ok, [entry()]} | {:error, term()}
Log entries with version > from_version, oldest first.
Errors are explicit: {:future_version, v} if from_version is ahead of
head, :log_truncated if the needed history was dropped by truncate/2.
Whether id is currently active.
Creates a space. Genesis ids are active at version 0.
Returns {:error, :duplicate_ids} when genesis ids repeat — a Caller bug,
reported rather than raised so Domain layers keep their no-raise rule.
Raising variant of new/1, for tests and known-good genesis lists.
@spec truncate(t(), Tamale.version()) :: t()
Drops log entries at or below oldest_live_version — the log-age
equivalent of GC. Anchors older than that horizon can no longer be
transported (log_from/2 reports :log_truncated).