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

The single `File.*` boundary for consumer disk spill (spec §5/§6). Owns the per-slot spill
directory (`0700`), the per-top-xid spill file (`0600`), append of length-prefixed
`:erlang.term_to_binary({subxid, %Change{}})` frames, file discard, and the per-slot startup
sweep. Every I/O or serialization fault is scrubbed to a value-free
`{:error, %Replicant.Error{reason: :spill_io_failed}}` here (Critical Rule 1) — no path, frame,
or row value ever escapes. Spill files are scratch: never fsync'd, deleted on
commit/abort/reset/halt, and (for a prior abnormal exit) swept per-slot on that slot's next start.

# `handle`

```elixir
@type handle() :: %{device: :file.io_device(), path: String.t()}
```

# `append`

```elixir
@spec append(handle(), non_neg_integer(), Replicant.Change.t()) ::
  {:ok, pos_integer()} | {:error, Replicant.Error.t()}
```

Append one `{subxid, %Change{}}` frame; returns the frame's byte size.

# `close`

```elixir
@spec close(handle()) :: :ok
```

Close the device WITHOUT removing the file (the reader re-opens for a single read pass).

# `discard`

```elixir
@spec discard(handle()) :: :ok
```

Close the device AND delete the file (commit-after-delivery / abort / reset / halt).

# `open`

```elixir
@spec open(String.t(), String.t(), non_neg_integer()) ::
  {:ok, handle()} | {:error, Replicant.Error.t()}
```

Open (create+truncate) the spill file for `top_xid` under `base_dir`/`slot_name` (0700 dir, 0600 file).

# `rm`

```elixir
@spec rm(String.t()) :: :ok
```

Delete a spill file at `path` if it exists (used when only the path is retained). Best-effort, value-free.

# `sweep_slot`

```elixir
@spec sweep_slot(String.t(), String.t()) :: :ok
```

Sweep THIS slot's spill subdir on the slot's pipeline start — removes any file a prior abnormal
exit left (spec §5). Per-slot, NOT global: a global sweep would delete a concurrently-running
slot's active files. Best-effort and value-free.

---

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