Pure window algebra for the incremental backfill (spec §2/§4): per-table PK tracking opened BEFORE the reader captures LW (superset drop-set), pending chunks that become ready when the epoch-guarded frontier passes their HW, the drop-filter at apply, the derived drop-cap (discard + re-read on breach), and the pending-chunk capacity bound.
Value-free discipline (Critical Rule 1): this module holds PK VALUES (tracking sets, chunk rows) as data-plane state; nothing here formats, logs, or raises them — every public return is a tagged tuple over the caller's own data.
Summary
Functions
Buffer a chunk awaiting closure. :at_capacity when max_pending is reached (caller defers the reader).
Clear a table's discard flag once its reader has been told to re-read.
True when qualified's pending chunks were discarded (contention) and its reader not yet told.
Advance the frontier from an applied/skipped txn commit LSN (same-process, always current epoch).
Start tracking a table's flushed PKs. MUST precede the reader's LW capture (spec §2 R1).
Pop the first CLOSED pending chunk (frontier >= hw), drop-filtered. Chunks pop in delivery order; a not-yet-closed head blocks later chunks of the same table (PK order must be preserved per table) — and since the reader is serial, pending chunks are already globally ordered.
Reconnect re-seed (spec §4): discard ALL pending chunks + tracking, adopt the new epoch.
Advance the frontier from an epoch-tagged wal_end (stale epochs ignored — 85672f1 class).
Any of qualified's chunks still buffered (pending, not yet applied)?
Conservatively DISCARD a table's pending chunks, RESET its tracking entry, AND
SIGNAL RE-READ (spec §2/§4/§5/§6.4). Used by the applier when a delivered
transaction's changes is a lazy, single-pass spill-backed Enumerable (or is
otherwise unavailable) and so MUST NOT be enumerated to update the drop-set: the
table's in-flight chunks are dropped and the reader re-reads from durable progress.
Record delivered txn changes' PKs for every OPEN table (receipt-time, convergence-safe).
track/2 + contention enforcement: a table whose pending chunks must be DISCARDED
and re-read is returned in the discard list AND recorded in w.discarded so the
caller can signal its reader {:error, :table_discarded} (spec §4/§6.4). Two triggers
True when the PK tuple is in the table's tracking set (test/inspection helper).
Types
@type t() :: %Replicant.SnapshotWindow{ discarded: %{optional(String.t()) => true}, drop_cap: pos_integer(), epoch: non_neg_integer(), frontier: Replicant.lsn(), max_pending: pos_integer(), pending: [chunk()], tracking: %{optional(String.t()) => %{pks: MapSet.t(), pk_raw: [String.t()]}} }
Functions
Buffer a chunk awaiting closure. :at_capacity when max_pending is reached (caller defers the reader).
Clear a table's discard flag once its reader has been told to re-read.
True when qualified's pending chunks were discarded (contention) and its reader not yet told.
@spec observe_applied(t(), Replicant.lsn()) :: t()
Advance the frontier from an applied/skipped txn commit LSN (same-process, always current epoch).
Start tracking a table's flushed PKs. MUST precede the reader's LW capture (spec §2 R1).
@spec pop_ready(t()) :: {:apply, [Replicant.Change.t()], chunk(), t()} | {:discard, chunk(), t()} | :none
Pop the first CLOSED pending chunk (frontier >= hw), drop-filtered. Chunks pop in delivery order; a not-yet-closed head blocks later chunks of the same table (PK order must be preserved per table) — and since the reader is serial, pending chunks are already globally ordered.
@spec reset(t(), non_neg_integer()) :: t()
Reconnect re-seed (spec §4): discard ALL pending chunks + tracking, adopt the new epoch.
@spec set_frontier(t(), non_neg_integer(), Replicant.lsn()) :: t()
Advance the frontier from an epoch-tagged wal_end (stale epochs ignored — 85672f1 class).
Any of qualified's chunks still buffered (pending, not yet applied)?
Conservatively DISCARD a table's pending chunks, RESET its tracking entry, AND
SIGNAL RE-READ (spec §2/§4/§5/§6.4). Used by the applier when a delivered
transaction's changes is a lazy, single-pass spill-backed Enumerable (or is
otherwise unavailable) and so MUST NOT be enumerated to update the drop-set: the
table's in-flight chunks are dropped and the reader re-reads from durable progress.
The tainted table is folded into w.discarded — the SAME reader-facing signal the
keyed drop-cap breach / keyless concurrent-write taint use (apply_contention/3) —
so the reader's next open/deliver/barrier for it returns {:error, :table_discarded} and re-reads. Without this signal the reader would never learn
its chunks were dropped: its bound would advance, a later chunk would persist a
bound PAST the discarded chunks, and the discarded chunks' untouched rows would be
LOST (the confirmed data-loss hole). Convergence-safe (discard-and-re-read — never
data loss, never a chunk whose drop-set is now unknowable). A no-op for a table that
is not being tracked (no chunks to drop, nothing to re-read).
@spec track(t(), [Replicant.Change.t()]) :: t()
Record delivered txn changes' PKs for every OPEN table (receipt-time, convergence-safe).
@spec track_capped(t(), [Replicant.Change.t()]) :: {t(), [String.t()]}
track/2 + contention enforcement: a table whose pending chunks must be DISCARDED
and re-read is returned in the discard list AND recorded in w.discarded so the
caller can signal its reader {:error, :table_discarded} (spec §4/§6.4). Two triggers:
- a KEYED table whose tracking set breaches
drop_cap(superset discard + re-read); and - a PK-LESS table (its tracking entry's
pk_raw == []) that received ANY tracked write —drop_capcan never fire for a keyless table (its PK tuple collapses to[]), so any concurrent write potentially staled the whole read ⇒ redo (spec §6.4).
True when the PK tuple is in the table's tracking set (test/inspection helper).