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

The lib-owned checkpoint store (spec §7) for non-transactional sinks. A GenServer
over a normal Postgrex connection that owns one `replicant_checkpoints` row per
slot and reads/writes `commit_lsn` behind the value-free boundary (Critical Rule
1 — the store carries slot_name + LSN only; a Postgrex fault is scrubbed to a
structural `%Replicant.Error{}`, never echoing a parameter).

The connection uses a **non-sync connect** so a transient store blip at boot does
not fail the `:temporary` pipeline (Postgrex reconnects in the background); a
genuine outage surfaces as a query fault that halts fail-closed. The table is
created + shape-probed lazily on the first read/write (once), so `init/1` never
blocks on the DB.

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `default_max_retries`

```elixir
@spec default_max_retries() :: non_neg_integer()
```

Default `max_retries` when `:checkpoint_store` omits it (spec §6).

# `default_retry_backoff_ms`

```elixir
@spec default_retry_backoff_ms() :: pos_integer()
```

Default `retry_backoff_ms` when `:checkpoint_store` omits it (spec §6).

# `emit_retrying`

```elixir
@spec emit_retrying(String.t(), pos_integer(), non_neg_integer()) :: :ok
```

Emit the value-free `[:replicant, :checkpoint_store, :retrying]` event (slot_name + ints only).

# `permanent_reason?`

```elixir
@spec permanent_reason?(atom()) :: boolean()
```

True when a store fault reason is PERMANENT (retrying cannot fix it): a wrong
pre-existing column type (`:checkpoint_store_schema_mismatch`) or an invalid table
identifier (`:config_invalid`). Every other reason — `:checkpoint_store_failed`, into
which a `%Postgrex.Error{}` / `%DBConnection.ConnectionError{}` is scrubbed — is
transient AT THE VALUE-FREE BOUNDARY (a momentary blip and a wrong-host misconfig are
indistinguishable here), so it is retried, then halted (spec §7).

# `read`

```elixir
@spec read(GenServer.server()) ::
  {:ok, Replicant.lsn() | nil} | {:error, Replicant.Error.t()}
```

Read the durable checkpoint for this slot (`nil` = never written) or a value-free error.

# `read_progress`

```elixir
@spec read_progress(GenServer.server()) ::
  {:ok, binary() | nil} | {:error, Replicant.Error.t()}
```

Read the incremental-snapshot progress token (`nil` = none) or a value-free error.

# `retry_decision`

```elixir
@spec retry_decision(non_neg_integer(), non_neg_integer()) :: :retry | :halt
```

The shared retry decision: `:retry` while `attempt < max_retries`, else `:halt`.
`attempt` is the count of retries ALREADY made (0 on the first fault). `max_retries: 0`
⇒ always `:halt` (halt-now opt-out). Used identically by the connect-read (timer) and
mid-stream write (sleep) sites so their counter semantics cannot drift (spec §4).

# `start_link`

```elixir
@spec start_link(keyword()) :: GenServer.on_start()
```

# `via`

```elixir
@spec via(String.t()) :: {:via, module(), term()}
```

The Registry via-name a pipeline's CheckpointStore registers under.

# `write`

```elixir
@spec write(GenServer.server(), Replicant.lsn()) ::
  :ok | {:error, Replicant.Error.t()}
```

Write the checkpoint (`INSERT ... ON CONFLICT`) or return a value-free error. Never advance the ack on `{:error, _}`.

# `write_progress`

```elixir
@spec write_progress(GenServer.server(), binary()) ::
  :ok | {:error, Replicant.Error.t()}
```

Durably upsert the progress token (an opaque bytea; spec §6.2) or return a value-free error.

---

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