# `Raxol.Agent.Journal`
[🔗](https://github.com/DROOdotFOO/raxol/blob/v2.8.0/lib/raxol/agent/journal.ex#L1)

Behaviour for a session's durable append-only journal — the source of truth
for the durable tier of events (see the package README, "The journal").

A journal is an ordered, append-only log of events. Each appended event is
assigned a monotonic `offset` (= the event's id) and framed as one complete,
self-delimited JSON object per line. Replaying a journal yields the events
back in offset order.

The behaviour exists so alternate storage backends (S3, a database, ...) can
slot in later behind the same contract. The concrete file-backed
implementation is `Raxol.Agent.Journal.FileStore` — one directory per session,
size-capped JSONL segments, torn-tail recovery on replay.

## Callbacks

  * `open/2`    — open (creating if needed) the journal for a session, returning an opaque handle.
  * `append/2`  — append one event, returning `{:ok, offset}`.
  * `read/2`    — replay durable events in offset order.
  * `close/2`   — release the handle (flush + stop the writer).
  * `status/1`  — `:ok` for a healthy journal, `{:damaged, offset}` if corruption was detected.
  * `verify/1`  — walk a hash-chained journal: `:ok` or `{:broken, offset}`.

## Durability & recovery

Only *complete* records are ever returned. On replay a parse failure on the
final line of the last segment is treated as a torn tail (a crash mid-write)
and truncated away — everything before it is recovered and `status/1` stays
`:ok`. A parse failure anywhere interior marks the session damaged, raises a
hard alarm, deletes nothing, and never returns the damaged content downstream.

## Hash chain

A backend may offer hash-chained journals (`Raxol.Agent.Journal.FileStore`
does with `chain: true`): each record links to the hash of the one before
it, so changing, removing or truncating any committed record is detected.
`verify/1` reports the offset of the first record that fails, and
`status/1` reports the same offset as damage.

# `event`

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

An event to append — any JSON-encodable map.

# `handle`

```elixir
@type handle() :: term()
```

Opaque, backend-specific handle returned by `open/2`.

# `journal_record`

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

A durable record read back from the journal (a decoded map with an `"id"`).

# `offset`

```elixir
@type offset() :: non_neg_integer()
```

Monotonic offset assigned to an appended event (= its id).

# `session_id`

```elixir
@type session_id() :: String.t()
```

Stable identifier for a session (also the on-disk directory name).

# `append`

```elixir
@callback append(handle(), event()) :: {:ok, offset()} | {:error, term()}
```

Append one event. Returns the assigned monotonic offset.

# `close`

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

Release the handle (flush pending writes and stop the writer).

# `open`

```elixir
@callback open(session_id(), opts :: keyword()) :: {:ok, handle()} | {:error, term()}
```

Open (creating if needed) the journal for `session_id`. Returns an opaque handle.

# `read`

```elixir
@callback read(handle(), opts :: keyword()) ::
  {:ok, [journal_record()]} | {:error, term()}
```

Replay durable events in offset order.

Returns `{:ok, records}` for a healthy (or torn-tail-recovered) journal, or
`{:error, :damaged}` when interior corruption was detected — in which case the
damaged content is never returned.

Options:

  * `:from_offset` — only return records whose id is `>=` this value.

# `status`

```elixir
@callback status(handle()) :: :ok | {:damaged, offset()}
```

`:ok` for a healthy journal, `{:damaged, offset}` naming the first offset
where corruption (or, on a chained journal, a chain break) was detected.

# `verify`

```elixir
@callback verify(handle()) :: :ok | {:broken, offset()} | {:error, :unchained}
```

Walk the hash chain. `:ok` when every record links and verifies,
`{:broken, offset}` for the first that does not, `{:error, :unchained}` for
a journal created without a chain.

# `verify`

```elixir
@spec verify(%{__struct__: module()}) ::
  :ok | {:broken, offset()} | {:error, :unchained}
```

Dispatch `verify/1` to the backend module that owns `handle`.

---

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