# `Raxol.AgentClientProtocol.Ext.Journal.Mem`
[🔗](https://github.com/DROOdotFOO/raxol-agent/blob/raxol_agent_client_protocol-v0.1.0/packages/raxol_agent_client_protocol/lib/raxol/agent_client_protocol/ext/journal/mem.ex#L1)

ETS-backed in-memory journal — an implementation of
`Raxol.AgentClientProtocol.Ext.Journal` bound by its OFFSET LAW.

## Layout

One `:ordered_set` ETS table per session, keyed by `offset`
(`{offset, %Record{}}`). Because the table is an `:ordered_set` keyed on the
integer offset, `:ets.last/1` is the durable high-watermark in O(log n) and
`read/3` is an offset-ordered range select.

## Ownership (why it survives Writer death)

`open/2` creates the table owned by the CALLING process (the app supervisor in
production, the test process in tests) — deliberately NOT the Writer. The
journal therefore outlives any single Writer crash/restart, which is the
precondition for the Writer-restart tip-fold orphan repair.

## Protection level (`:public`, deliberate)

The table is `:public`, not `:protected`. `:protected` would forbid all
non-owner writes, but the single-publisher Writer is a DIFFERENT process from
the owner (the owner OUTLIVES the Writer, per the ownership note above), so
`:protected` would break every append. Offset integrity
is instead structural: exactly one Writer per session (the unique Writer
Registry) is the sole appender in production. Records are stored in PLAINTEXT —
co-resident BEAM processes are trusted; this store is not a confidentiality
boundary.

## Atomic offset assignment — no counter

`append/2` computes `offset = high_watermark(j) + 1` by reading the table max
(`:ets.last/1`), NEVER a cached counter (OFFSET LAW rule 2). Atomicity across
concurrent appenders is the Writer's job: a single Writer
process per session serializes every `append` through one mailbox, so the
read-then-insert is race-free by construction (the behaviour may assume a
single writer per session).

# `t`

```elixir
@type t() :: %Raxol.AgentClientProtocol.Ext.Journal.Mem{
  session_id: String.t(),
  table: :ets.tid()
}
```

# `append`

```elixir
@spec append(t(), Raxol.AgentClientProtocol.Ext.Journal.entry()) ::
  {:ok, Raxol.AgentClientProtocol.Ext.Journal.Record.t()}
```

Assign the next offset atomically (max + 1, read from the store), stamp
`session_id` + `ts_hook`, persist, and return the COMPLETE `%Record{}`.

# `close`

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

Delete the backing table. Idempotent-ish: a double close raises like any ETS misuse.

# `high_watermark`

```elixir
@spec high_watermark(t()) :: non_neg_integer()
```

Last DURABLE offset, read from the store (`:ets.last/1`); `0` for an empty
session. NEVER a cached counter (OFFSET LAW rule 2).

# `open`

```elixir
@spec open(String.t(), keyword()) :: {:ok, t()}
```

Open a fresh per-session handle. The ETS table is owned by the caller (see the
ownership note in the moduledoc). `opts` is accepted for behaviour conformance
and ignored.

# `read`

```elixir
@spec read(t(), pos_integer(), non_neg_integer()) ::
  {:ok, [Raxol.AgentClientProtocol.Ext.Journal.Record.t()]}
```

Offset-ordered records in `[from, to]`. An empty or inverted range yields
`{:ok, []}`. Reads directly from the store — value-identical to what `append`
stored.

---

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