# `Raxol.AgentClientProtocol.Ext.Capability.Token`
[🔗](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/attach_policy/token.ex#L1)

The `RXC1` capability token — offline-verifiable detached Ed25519 signature.

The headline capability: an attach to a **writerless** session (a tar'd journal
directory, no live authority, no network, no issuer reachable) is authorized
cryptographically from the token bytes + a shipped public key ALONE.

## Encoding — version-pinned, no `alg` field anywhere

```
token         := "RXC1." <> b64url_nopad(claims_json) <> "." <> b64url_nopad(sig)
signing_input := "RXC1." <> b64url_nopad(claims_json)      # prefix INCLUDED in signed bytes
sig           := Ed25519-sign(issuer_priv, signing_input)  # 64 bytes, detached
```

Encoding rules, each load-bearing:

  * **No header, no `alg`.** The literal prefix `RXC1` *is* the algorithm
    binding (`RXC1 ⟺ Ed25519`). The JWT `alg:none` / RS256→HS256 confusion is
    structurally UNEXPRESSIBLE — no code path reads an algorithm name.
  * **The prefix is inside the signed bytes**, so a signature minted for a
    hypothetical `RXC2` can never be replayed under `RXC1` (downgrade).
  * **The signature covers the encoded blob, not re-canonicalized JSON.** The
    verifier base64url-decodes and JSON-*decodes* but NEVER re-encodes — there
    is no canonicalization step, hence no malleability class (key ordering,
    unicode escaping, duplicate keys are all bound by the signed bytes).
  * **Strict base64url, `padding: false`.** Any `=`/`+`/`/`/whitespace/
    non-alphabet byte ⇒ decode failure ⇒ deny. Exactly three dot-separated
    non-empty segments — one token has exactly one valid byte form.

## Verification is TOTAL

`verify/4` is a **pure function**: same inputs ⇒ same output; no network, no
process dictionary, no clock read (`now` is a parameter), no filesystem.
For ALL binaries it returns `{:ok, claims} | {:error, atom}` — never raises,
never creates an atom. Reason atoms are internal
(telemetry only); the wire sees a single bare deny (anti-oracle).

# `keyring`

```elixir
@type keyring() :: %{optional(String.t()) =&gt; binary()}
```

Verifier keyring: kid (binary) → 32-byte Ed25519 public key.

# `clock_skew_s`

```elixir
@spec clock_skew_s() :: non_neg_integer()
```

The `iat` clock-skew tolerance (seconds). `exp` has none.

# `restrictive_implemented`

```elixir
@spec restrictive_implemented() :: [String.t()]
```

The restrictive claim keys this verifier IMPLEMENTS (enforce-if-present).

# `verify`

```elixir
@spec verify(term(), keyring(), integer(), String.t()) ::
  {:ok, map()} | {:error, atom()}
```

Verify a token against a keyring, wall-clock `now` (unix seconds), and the
expected session id. Total: every failure is `{:error, atom}`, never a raise.

---

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