# `Anubis.Server.Registry`

Behaviour for pluggable session registries and deterministic naming utilities.

The registry is responsible for mapping session IDs to PIDs. Different transports
have different needs:

- STDIO: single session, no registry needed (`Registry.None`)
- HTTP: multiple sessions, need lookup by session ID (`Registry.Local`)

## Naming Utilities

The module also provides deterministic atom naming for internal processes
(transports, supervisors, task stores). These are keyed off the server module,
which is compile-time bounded, so they cannot exhaust the atom table.

Session processes are different: their ids come from the client-controlled
`mcp-session-id` header. `resolve_session_name/3` therefore names sessions via
an Elixir `Registry` keyed by the session-id string (a `:via` tuple) rather
than minting one atom per session id.

# `session_id`

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

# `child_spec`

```elixir
@callback child_spec(keyword()) :: Supervisor.child_spec() | :ignore
```

# `lookup_session`

```elixir
@callback lookup_session(name :: term(), session_id()) ::
  {:ok, pid()} | {:error, :not_found}
```

# `register_session`

```elixir
@callback register_session(name :: term(), session_id(), pid()) :: :ok | {:error, term()}
```

# `session_name`
*optional* 

```elixir
@callback session_name(registry_name :: term(), session_id()) :: GenServer.name()
```

Returns the GenServer name for a session. Override this to return a `:via` tuple
(e.g. `{:via, Horde.Registry, {name, session_id}}`) when using a distributed registry
that auto-registers processes on `start_link`. The default returns a plain atom.

When a `:via` tuple is returned, `register_session/3` should be a no-op since
registration happens automatically on process start.

# `unregister_session`

```elixir
@callback unregister_session(name :: term(), session_id()) :: :ok
```

# `event_store_name`

```elixir
@spec event_store_name(module()) :: atom()
```

Default atom name for a server's Streamable HTTP SSE event store process.
Adapters may override via the optional `resolve_name/2` callback to return a
`:via` tuple for distributed deployments.

## Examples

    iex> Anubis.Server.Registry.event_store_name(MyApp.Server)
    :"Anubis.Elixir.MyApp.Server.event_store"

# `naming_registry_name`

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

Name of the per-server `Registry` used to name session processes.

Session ids are client-controlled (the `mcp-session-id` header), so naming
session processes with `:"#{registry_name}.session.#{session_id}"` would mint
a fresh atom per session id. Atoms are never garbage collected, so an attacker
feeding distinct session ids could exhaust the atom table and crash the VM.

Instead we route the default naming through an Elixir `Registry` keyed by the
session-id string. `registry_name` is a compile-time bounded server atom, so
deriving this name from it is safe.

# `registry_name`

```elixir
@spec registry_name(module()) :: atom()
```

# `resolve_session_name`

```elixir
@spec resolve_session_name(module(), term(), session_id()) :: GenServer.name()
```

Resolves the session GenServer name via the registry adapter.

Falls back to the default `:via` naming if the adapter does not implement the
optional `session_name/2` callback.

# `session_name`

```elixir
@spec session_name(module(), String.t()) :: atom()
```

Deterministic atom name for a session process.

## Warning

This mints an atom per `session_id`. Only call it with compile-time bounded or
otherwise trusted session ids (e.g. in tests). It must **not** be used on the
request path with client-supplied session ids, since atoms are never garbage
collected and an attacker could exhaust the atom table. The runtime session
naming path goes through `resolve_session_name/3`, which returns a `:via`
`Registry` name keyed by the session-id string instead.

# `session_supervisor_name`

```elixir
@spec session_supervisor_name(module()) :: atom()
```

# `stdio_session_name`

```elixir
@spec stdio_session_name(module()) :: atom()
```

# `supervisor_name`

```elixir
@spec supervisor_name(module()) :: atom()
```

# `task_store_name`

```elixir
@spec task_store_name(module()) :: atom()
```

Default atom name for a server's `Anubis.Server.TaskStore` process. Adapters
may override via the optional `resolve_name/2` callback to return a `:via`
tuple for distributed deployments.

# `task_supervisor_name`

```elixir
@spec task_supervisor_name(module()) :: atom()
```

# `transport_name`

```elixir
@spec transport_name(module(), atom()) :: atom()
```

---

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