# `Replicant.SnapshotWindow`
[🔗](https://github.com/baselabs/replicant/blob/v0.3.1/lib/replicant/snapshot_window.ex#L1)

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.

# `chunk`

```elixir
@type chunk() :: %{
  qualified: String.t(),
  schema: String.t(),
  table: String.t(),
  pk_raw: [String.t()],
  pk_canon: [[term()]],
  changes: [Replicant.Change.t()],
  hw: Replicant.lsn(),
  first?: boolean(),
  complete?: boolean(),
  progress: binary(),
  bound: [term()] | nil
}
```

# `t`

```elixir
@type t() :: %Replicant.SnapshotWindow{
  discarded: %{optional(String.t()) =&gt; true},
  drop_cap: pos_integer(),
  epoch: non_neg_integer(),
  frontier: Replicant.lsn(),
  max_pending: pos_integer(),
  pending: [chunk()],
  tracking: %{optional(String.t()) =&gt; %{pks: MapSet.t(), pk_raw: [String.t()]}}
}
```

# `add_chunk`

```elixir
@spec add_chunk(t(), chunk()) :: {t(), :ok | :at_capacity}
```

Buffer a chunk awaiting closure. `:at_capacity` when max_pending is reached (caller defers the reader).

# `clear_discarded`

```elixir
@spec clear_discarded(t(), String.t()) :: t()
```

Clear a table's discard flag once its reader has been told to re-read.

# `discarded?`

```elixir
@spec discarded?(t(), String.t()) :: boolean()
```

True when `qualified`'s pending chunks were discarded (contention) and its reader not yet told.

# `new`

```elixir
@spec new(keyword()) :: t()
```

# `observe_applied`

```elixir
@spec observe_applied(t(), Replicant.lsn()) :: t()
```

Advance the frontier from an applied/skipped txn commit LSN (same-process, always current epoch).

# `open_window`

```elixir
@spec open_window(t(), String.t()) :: t()
```

Start tracking a table's flushed PKs. MUST precede the reader's LW capture (spec §2 R1).

# `pop_ready`

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

# `reset`

```elixir
@spec reset(t(), non_neg_integer()) :: t()
```

Reconnect re-seed (spec §4): discard ALL pending chunks + tracking, adopt the new epoch.

# `set_frontier`

```elixir
@spec set_frontier(t(), non_neg_integer(), Replicant.lsn()) :: t()
```

Advance the frontier from an epoch-tagged wal_end (stale epochs ignored — 85672f1 class).

# `table_pending?`

```elixir
@spec table_pending?(t(), String.t()) :: boolean()
```

Any of `qualified`'s chunks still buffered (pending, not yet applied)?

# `taint_table`

```elixir
@spec taint_table(t(), String.t()) :: t()
```

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

# `track`

```elixir
@spec track(t(), [Replicant.Change.t()]) :: t()
```

Record delivered txn changes' PKs for every OPEN table (receipt-time, convergence-safe).

# `track_capped`

```elixir
@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_cap` can never fire for a keyless table (its PK tuple collapses to `[]`), so any
    concurrent write potentially staled the whole read ⇒ redo (spec §6.4).

# `tracked?`

```elixir
@spec tracked?(t(), String.t(), [term()]) :: boolean()
```

True when the PK tuple is in the table's tracking set (test/inspection helper).

---

*Consult [api-reference.md](api-reference.md) for complete listing*
