# `Raxol.AgentClientProtocol.Transport.Reassembly`
[🔗](https://github.com/DROOdotFOO/raxol-agent/blob/raxol_agent_client_protocol-v0.1.0/packages/raxol_agent_client_protocol/lib/raxol/agent_client_protocol/transport/reassembly.ex#L1)

A pure, process-free reorder buffer for **unordered-carrier** transports
(pairs with `Raxol.AgentClientProtocol.Transport.Envelope` on the send
side; no shipped transport uses it).

Shaped exactly like `Transport.Framer`: no process, no mailbox, no I/O —
a caller (a transport's single reader) threads `t()` through successive
`push/3` calls as enveloped frames arrive off the wire, in *whatever*
order the carrier delivers them, and gets back the frames that are now
releasable *in original send order*.

## Scope: reliable-unordered, not lossy

This module targets carriers that reorder frames but never lose them
(e.g. frames fanned out over several independently-ordered streams, a
bus with per-partition-only ordering, or a datagram carrier with its
own retransmit below this layer). It restores order; it does not
detect or recover from loss. A missing `tseq` and a merely-delayed
`tseq` are indistinguishable to this module — both simply hold up
release until either the gap arrives or the watermark is crossed. A
*lossy* carrier needs its own ARQ (ack/retransmit) underneath this
layer to turn itself into a reliable-unordered one; that is a transport
concern, not an ordering one, and is out of scope here.

## Semantics

  * `next_expected` starts at `1` (or `:start`, see `new/1`).
  * `tseq == next_expected` — release that frame, then cascade: release
    every already-buffered contiguous successor (`next_expected + 1`,
    `+2`, ...) in one `push/3` call, advancing `next_expected` past all
    of them.
  * `tseq > next_expected` — buffer it (keyed by `tseq`); nothing is
    released.
  * `tseq < next_expected` — a duplicate of an already-released frame;
    dropped silently (idempotent under carrier-level retransmit).
  * a duplicate of an already-*buffered* (not yet released) `tseq` —
    also dropped silently; the buffer keeps the first copy.
  * the buffer crossing either watermark (`:max_buffered_frames`,
    default `1024`; `:max_buffered_bytes`, default 16 MiB)
    returns `{:closed, {:transport, :reassembly_overflow}}` — the
    **only** failure mode. There is no other way this module ever
    reports a problem.

## Overflow-bounded, never timer-bounded

There is no wall clock anywhere in this module — no timer, no
`Process.send_after`, no deadline. While under the watermark, an
out-of-order frame is held indefinitely, waiting for its missing
predecessor; the only two resolutions are the predecessor's arrival or
the watermark closing the stream. A carrier that wants liveness
detection (peer silently vanished, nothing further will ever arrive)
implements its own keepalive below this layer and calls `close/1` (or
simply stops calling `push/3` and tears down its own state) — that is
the carrier's reliability concern, not this buffer's ordering concern.

## Usage

    buf = Reassembly.new()
    {:ok, [], buf} = Reassembly.push(buf, 2, %{"b" => 2})   # buffered, nothing releasable yet
    {:ok, released, buf} = Reassembly.push(buf, 1, %{"a" => 1})
    # released == [%{"a" => 1}, %{"b" => 2}]  -- 1 releases, then 2 cascades

# `frame`

```elixir
@type frame() :: map()
```

One JSON-RPC frame (a decoded map), as delivered by `Envelope.unwrap/1`.

# `overflow_reason`

```elixir
@type overflow_reason() :: {:transport, :reassembly_overflow}
```

The sole failure this module ever produces: the watermark was crossed.

# `t`

```elixir
@type t() :: %Raxol.AgentClientProtocol.Transport.Reassembly{
  buffer: %{optional(tseq()) =&gt; frame()},
  buffered_bytes: non_neg_integer(),
  max_buffered_bytes: pos_integer(),
  max_buffered_frames: pos_integer(),
  next_expected: pos_integer()
}
```

# `tseq`

```elixir
@type tseq() :: pos_integer()
```

Sender-assigned monotone sequence number (see `Envelope`).

# `new`

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

Create a fresh reassembly buffer.

Options:

  * `:start` — the first `tseq` this buffer expects (default `1`,
    matching `Envelope`'s counter start). Must be a positive integer.
  * `:max_buffered_frames` — maximum number of not-yet-releasable
    frames held at once before overflow (default
    `1024`). Must be a positive integer.
  * `:max_buffered_bytes` — maximum approximate total size (in bytes,
    via `:erlang.external_size/1` on each buffered frame) of buffered
    frames before overflow (default 16777216
    = 16 MiB). Must be a positive integer.

# `push`

```elixir
@spec push(t(), tseq(), frame()) ::
  {:ok, [frame()], t()} | {:closed, overflow_reason()}
```

Feed one arrived (already-unwrapped) `{tseq, frame}` pair into the
buffer.

Returns `{:ok, released, buffer}` where `released` is the (possibly
empty) list of frames now releasable, in original send order — or
`{:closed, {:transport, :reassembly_overflow}}` if admitting this frame
would cross either watermark. Once `:closed` is returned the caller
MUST stop calling `push/3` (mirroring `T-TERM`: nothing is delivered
after a close) and deliver
`{:acp_transport, ref, {:closed, {:transport, :reassembly_overflow}}}`
to its owner; this module holds no further state for that stream.

---

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