# `Raxol.AgentClientProtocol.Ext.Journal.Writer`
[🔗](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/writer.ex#L1)

ONE GenServer per `session_id` — the SINGLE PUBLISHER of a session's live
tail.

Every durable record — streamed updates, turn boundaries, genesis, orphan
repairs — passes through this one process, so "append THEN publish" is
structural, not conventional: the publish call sits lexically AFTER
`journal.append/2` returns `{:ok, record}` (durable, read-visible) and only
post-write code holds a stamped record (no publish-ahead). There is no
second door to the subscriber set.

## Responsibilities (this cell)

  * **Append-then-publish** — `append/4` writes durably, then `send`s
    `{:reattach_live, session_id, %Record{}}` to every subscriber, at the tail
    (never publish-ahead).
  * **Lazy genesis + Writer-restart tip-fold orphan repair** — on (re)start,
    BEFORE honoring the first `append`/`subscribe`
    for a session (mailbox-serialized, strictly-before), the Writer:
      - if the journal is empty, appends the genesis `session_created` at
        offset 1; else
      - reads the tip; if it is a `turn_started` with no matching
        `turn_completed`, appends EXACTLY ONE orphaned `turn_completed`
        (idempotent: a no-op when the tip is already a `turn_completed`, so a
        restart during the fold cannot double-append).
    Running the fold *after* the first op is a defect with two witnesses
    (a new turn on top either buries the strand or double-completes it); the
    strictly-before ordering closes both. A test-only `:__dead_bootstrap_after_op__`
    knob realizes the defect so tests can witness it.
  * **Cross-connection turn latch** — `append("turn_started", …)` while
    a turn is latched returns `{:error, :turn_in_flight}`; the latch monitors
    the appender. Symmetrically, `append("turn_completed", …)` from a NON-holder
    while a turn is latched is REFUSED at the durable append itself
    (`{:error, :not_turn_holder}`) — the single-holder law guards the APPEND, not
    just the latch-clear, so a foreign connection can never write a phantom
    completion row that a reattacher would finalize from (log + telemetry, the
    journal is never corrupted).
  * **Atomic latch clear** — appending `"turn_completed"` as the HOLDER
    clears the turn latch INSIDE the same `handle_call`, before the reply.
    Success therefore never leaves the latch held: a Session crash immediately
    AFTER a successful `turn_completed` append produces ZERO orphan rows (no
    double `turn_completed`) and never wedges the next prompt at
    `:turn_in_flight`.
  * **Orphan repair on appender death** — if the latched appender dies
    between `turn_started` and `turn_completed`, the Writer appends exactly one
    orphaned `turn_completed{"outcome":"orphaned","stopReason":"cancelled"}`
    and clears the latch.
  * **Credit-based lagged producer** — each subscriber carries a publish
    credit (default `1024`, per-Writer configurable via `:subscriber_credit`).
    Every live publish consumes one credit; a subscriber that overruns its credit
    is sent EXACTLY ONE terminal `{:reattach_lagged, session_id, last_offset}`
    (`last_offset` = the highest offset it was actually sent, so the client heals
    at `+1` dup-free) and is then demonitored and dropped — the Writer never
    blocks on a slow subscriber and never drops a middle record for one still
    attached. `credit/3` replenishes. A dead subscriber is pruned by its monitor.

## Taint — annotate, never filter

The Writer STAMPS taint into the record (via the store) and delivers the record
to EVERY subscriber regardless of value. No code path here drops, withholds, or
reroutes a record by taint.

## Restart limitations

The Writer-restart tip-fold folds on the journal TIP alone. It guarantees that
no dangling OPEN turn survives a restart, but it leaves two interleavings
inherent to a tip-fold when the Writer dies but the appender Session survives
the restart:

  * **(a) tip = `turn_started`, turn still live.** The fold reads a dangling
    `turn_started` and synthesizes one orphaned `turn_completed` — but the real
    appender is still running; its later real updates and its own real
    `turn_completed` then land AFTER the synthetic completion (a false-orphan of a
    live turn).
  * **(b) tip = a mid-turn `session_update`.** The tip is not a `turn_started`, so
    the fold no-ops and the in-memory latch is silently lost across the restart; a
    concurrent second `turn_started` then succeeds against a turn that is, on the
    wire, still open.

## Supervision

`Raxol.AgentClientProtocol.Ext.Journal.ensure_writer/3` starts Writers lazily
under the package's `WriterSupervisor` (`DynamicSupervisor`); each Writer is
registered by `session_id` in the Writer `Registry` (`via/1`). The journal
handle `{mod, j}` is passed in at `start_link` — its owning process (the app
supervisor in production, the test process in tests) OUTLIVES the Writer,
which is exactly what lets the tip-fold survive a Writer crash.

# `subscriber`

```elixir
@type subscriber() :: %{
  pid: pid(),
  credit: non_neg_integer(),
  sent_hi: non_neg_integer()
}
```

Per-subscriber state: pid, remaining publish credit, and highest offset sent.

# `t`

```elixir
@type t() :: %Raxol.AgentClientProtocol.Ext.Journal.Writer{
  bootstrapped: boolean(),
  dead_bootstrap_after_op: boolean(),
  dead_publish_phantom: boolean(),
  journal: {module(), term()},
  session_id: String.t(),
  session_meta: map(),
  subscriber_credit: pos_integer(),
  subscribers: %{required(reference()) =&gt; subscriber()},
  turn: turn()
}
```

# `turn`

```elixir
@type turn() ::
  %{turn_id: term(), appender_ref: reference(), appender_pid: pid()} | nil
```

# `append`

```elixir
@spec append(GenServer.server(), String.t(), map(), String.t()) ::
  {:ok, Raxol.AgentClientProtocol.Ext.Journal.Record.t()}
  | {:error, :turn_in_flight | :not_turn_holder}
```

Durably append a record, then publish it live. Returns the COMPLETE stamped
`%Record{}` (offset = `record.offset`).

`kind == "turn_started"` acquires the turn latch (or returns
`{:error, :turn_in_flight}` if one is held); `kind == "turn_completed"` by the
latch HOLDER clears the latch atomically in the same step, while a
`turn_completed` from a NON-holder while a turn is latched is REFUSED at the
durable append (`{:error, :not_turn_holder}` — no journal write, no false
boundary); every other kind (and a `turn_completed` with no open turn) is a
plain append. Triggers the lazy bootstrap strictly BEFORE the append (unless the
dead `:__dead_bootstrap_after_op__` knob is set).

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `credit`

```elixir
@spec credit(GenServer.server(), pid(), pos_integer()) :: :ok
```

Replenish `subscriber`'s publish credit by `n`. A cast — the Writer never
blocks on credit accounting. A subscriber calls this after forwarding frames so
its credit does not run down under a healthy delivery rate; an already-dropped
(lagged) subscriber is a no-op.

# `registry`

```elixir
@spec registry() :: atom()
```

The package-level unique `Registry` name for Writers (keys = `session_id` binaries).

# `start_link`

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

Start a Writer.

Options:

  * `:session_id` (required, binary)
  * `:journal` (required) — an already-open `{module, handle}`; the handle's
    owner MUST outlive the Writer (so a restart can tip-fold).
  * `:session_meta` — the genesis `session_created` payload (default `%{}`).
  * `:subscriber_credit` — per-subscriber publish credit (default
    `1024`); a subscriber that overruns it is sent one
    terminal `{:reattach_lagged, ...}` and dropped.
  * `:name` — override the `:via` name; pass `nil` to start unregistered
    (tests). Defaults to `via(session_id)`.
  * `:__dead_publish_phantom__` / `:__dead_bootstrap_after_op__` — TEST-ONLY
    dead-injector knobs (default `false`); see the moduledoc.

# `subscribe`

```elixir
@spec subscribe(GenServer.server(), pid()) :: :ok
```

Register `subscriber` for the live tail. Serialized against appends in this
Writer's mailbox. Idempotent per pid. Triggers the lazy
bootstrap (genesis / tip-fold) strictly BEFORE registering.

# `unsubscribe`

```elixir
@spec unsubscribe(GenServer.server(), pid()) :: :ok
```

Idempotently detach `subscriber` from the live tail.

# `via`

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

The `:via` tuple that keys a Writer on its `session_id` (binary, never atomized).

# `whereis`

```elixir
@spec whereis(String.t()) :: pid() | nil
```

Look up the live Writer for `session_id`, or `nil`. Used by the
`Ext.Journal.subscribe/2` facade to route registration through the single
publisher's mailbox.

---

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