# `AttestoMCP.Server.UrlElicitationStore`
[🔗](https://github.com/XukuLLC/attesto_mcp_server/blob/v2.8.0/lib/attesto_mcp_server/url_elicitation_store.ex#L1)

Durable storage contract for staged URL elicitation records.

Records are JSON-compatible string-keyed maps with the following fields:
`"id"`, `"namespace"`, `"subject_hash"`, `"action"`, `"fields"`,
`"created_at_ms"`, `"expires_at_ms"`, and `"consumed_at_ms"` (nil until consumed).

Adapters must guarantee that `consume/5` is atomic: a record is consumed at
most once even under concurrent calls. The store must verify that the subject
hash matches before revealing state or content. A foreign subject mismatch
returns `{:error, :foreign}` and takes precedence over consumed or expired
status so an unauthorized caller learns nothing about the record state.
`cleanup_expired/2` sweeps expired records and returns at most 1,000 IDs per
call.

# `adapter`

```elixir
@type adapter() :: {module(), store()}
```

A configured adapter module and its opaque store handle.

# `elicitation_record`

```elixir
@type elicitation_record() :: %{required(String.t()) =&gt; term()}
```

# `id`

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

# `namespace`

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

# `now_ms`

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

# `store`

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

# `subject_hash`

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

# `cleanup_expired`

```elixir
@callback cleanup_expired(store(), now_ms()) :: {:ok, [id()]} | {:error, term()}
```

# `consume`

```elixir
@callback consume(store(), namespace(), id(), subject_hash(), now_ms()) ::
  {:ok, elicitation_record()}
  | :not_found
  | {:error, :expired | :consumed | :foreign | term()}
```

# `fetch`

```elixir
@callback fetch(store(), namespace(), id()) ::
  {:ok, elicitation_record()} | :not_found | {:error, term()}
```

# `put`

```elixir
@callback put(store(), elicitation_record()) :: :ok | {:error, term()}
```

---

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