# `Raxol.MCP.Client.Tables`
[🔗](https://github.com/DROOdotFOO/raxol/blob/v2.8.0/lib/raxol/mcp/client/tables.ex#L1)

The one process that owns the MCP client's per-ORIGIN state.

ADR-0037 decision 2 asked for "a public ETS table with no owning process",
which is not a thing that can be built: `:ets.new/2` tables are owned by the
process that creates them (`circuit_breaker.ex:31-33`, whose only in-tree
caller creates its tables inside a GenServer's `init_manager`). Two concrete
failures follow from letting each client create its own:

  * an era verdict created by a client is a verdict per client, so every
    client pays for its own probe and the cache caches nothing;
  * a circuit breaker created per client never observes anyone else's
    failures, so a challenge-serving origin is retried by every client
    forever.

Both pieces of state outlive any one client, so they are created once, here,
and handed out through `:persistent_term`: a lock-free read of a
write-once value rather than a message to this process per request.

This process holds no logic. It creates two tables in `init/1` and answers
nothing, which is what makes a restart theoretical rather than a live
concern. Callers read through `eras/0` and `breakers/0` per use rather than
caching a reference, because a restart replaces both tables and a stale
reference raises `ArgumentError` from ETS.

## Why `ensure_started/0` exists

The normal path is the supervised child of `Raxol.MCP.Supervisor`. But a
client is also started directly, outside that tree, by
`Raxol.Agent.McpBundle`, and an unreachable table there would mean no remote
server at all rather than a slower one. So the HTTP transport ensures the
owner exists before its first read. The started process is NOT linked to
whoever asked: a client that dies must not take the shared verdict cache
with it.

# `t`

```elixir
@type t() :: %{eras: :ets.table(), breakers: :ets.table(), reservations: :ets.table()}
```

# `breakers`

```elixir
@spec breakers() :: :ets.table()
```

The circuit-breaker table, keyed `{:origin, origin}`.

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `ensure_started`

```elixir
@spec ensure_started() :: t()
```

Every table, starting the owner if nothing has yet.

Idempotent, and safe from a transient caller: the owner is started
unlinked.

The answer comes from the owner rather than from `:persistent_term` when
this call did not start it. `GenServer.start/3` registers the name before
`init/1` runs, so two clients connecting concurrently both get past the
clash while only one has written the terms, and the loser used to read a
term that did not exist yet -- an `ArgumentError` from
`handle_continue`, in the exact embedded configuration (`raxol_agent`
alone, no `Raxol.MCP.Supervisor`) this function exists for. A call is
queued behind `init/1`, so it cannot observe the gap.

# `eras`

```elixir
@spec eras() :: :ets.table()
```

The era-verdict table, keyed `{origin, path}`.

Raises if the owner has never run; callers that cannot assume a supervision
tree call `ensure_started/0` first.

# `reservations`

```elixir
@spec reservations() :: :ets.table()
```

The minted single-use spend handles (`Raxol.MCP.Client.Reservation`).

# `start_link`

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

Start the table owner. One per node, named.

A name clash is not a failure here. `ensure_started/0` starts this process
unlinked and outside any tree, so a client that ran before the subsystem
booted may already own the tables; the supervised child adopts that owner
rather than refusing to start the whole MCP subtree over the name. The
tables are the point, and they are the same ones.

---

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