Tamale

zread

zongzi isn't πŸ«” β€” this one is.

A minimal kernel for preserving user edits across upstream regeneration cycles. What the user writes is a patch relative to an upstream base; the base regenerates; after each regeneration every patch must be judged still applicable / applicable after transform / dead. That is a three-way merge with an explicit merge-base β€” the same problem git patch + base + transform rules solves β€” and this kernel is its smallest honest answer.

Tamale is a greenfield successor designed from a review of zongzi: it keeps zongzi's two-phase survival doctrine and compresses its Anchor + Intervention + Timeline subsystems into a single transport mechanism.

Quick Start

Tamale separates two questions:

  1. Did the edited thing survive the upstream change?
  2. Does the edit still apply to the new content?

Let's Start with an upstream document.

source = %{s1: "Hello world."}
{:ok, space} = Space.new([:s1])

The user creates a translation for the original text(s1):

anchor = %Ordinal{
refs: [:s1],
at_version: space.version
}
{:ok, patch} =
Patch.new(
source.s1,
%{lang: :zh, text: "δ½ ε₯½οΌŒδΈ–η•Œγ€‚"}
)

At this point, the patch means:

"Apply δ½ ε₯½οΌŒδΈ–η•Œγ€‚ to s1, but only if s1 is still based on Hello world.."

Suppose the upstream generator splits s1:

{:ok, space} =
Space.apply_op(
space,
%Tamale.Op.Split{
id: :s1,
children: [:s1, :s1b]
}
)
source = %{
s1: "Hello",
s1b: " world."
}

The important part is that the identity of the first child survives the split:

s1
β”‚
β”œβ”€β”€ s1 ← original identity survives
└── s1b

Then transport the user's anchor.

{:ok, anchor} = Transport.transport(anchor, space)

The anchor survived the structural change, so the translation is still attached to s1.

Dive to 2nd phase, check whether the patch still applies.

case Patch.resolve(patch, source.s1) do
{:ok, payload} ->
IO.puts("APPLY: #{payload.text}")
{:conflict, :base_changed} ->
IO.puts("CONFLICT: the source changed")
{:error, reason} ->
IO.puts("ERROR: #{inspect(reason)}")
end

The result is:

CONFLICT: the source changed

This is intentional.

The anchor survived the split, but s1 is no longer the text the user originally edited:

base: "Hello world."
current: "Hello"

So Tamale reports:

structural survival β†’ yes
semantic survival β†’ no

That distinction is the core of Tamale's two-phase survival model.

Architecture

Layering

Tamale couldn't works without your task, so it needs combination with kernel, policy & adapters/host.

kernel : Space(id, order, version) Β· Op Β· Anchor/Transport Β· Patch ← this package
policy : relocation choice, clip-vs-conflict, digest chunk granularity ← callbacks
adapters : Tempo→Warp · curve samplers · windowing · score theory · engine bindings

The kernel holds no domain data and no engine contract.

Core Concepts

Tamale has four small building blocks:

How they fit together

The whole flow is:

Op
β”‚
β–Ό
Space ──────► new version
β”‚
β”‚ transport
β–Ό
Anchor
β”‚
β”‚ locate
β–Ό
Patch
β”‚
β”‚ resolve
β–Ό
apply / conflict

This gives Tamale two deliberately separate questions:

1. Did the edited location survive the upstream change?
β†’ Anchor + Transport
2. Does the edit still apply to the new content?
β†’ Patch + Digest

That separation is the core of Tamale's two-phase survival model.

Invariants

Status: scaffold

Working and tested:

Guides and specs:

Done (implemented in the downstream coconut editor core):

Not yet:

Design decisions: docs/decisions/.

License

MIT (same as zongzi).