# `AshReplicant.Error`
[🔗](https://github.com/baselabs/ash_replicant/blob/v0.3.0/lib/ash_replicant/error.ex#L1)

Value-free error for the sink boundary. Carries structure only — a reason atom,
the resource module, the op, and the offending error's struct-module NAME —
never a row value, changeset, or DB message. `scrub/3` normalizes any raised or
returned error into this shape, so the fail-closed halt path leaks nothing
(including pre-encryption sensitive plaintext).

The value-free guarantee is enforced by `scrub/3` and by callers passing only a
`reason`, a resource module, and an op atom to `exception/1`. Do NOT forward an
upstream error's message, `vars`, or `bread_crumbs` into `exception/1` — those
Splode fields are rendered by `inspect`/`message` and would leak.

# `reason`

```elixir
@type reason() ::
  :sink_failed
  | :tenant_required
  | :schema_change_destructive
  | :truncate_halt
  | :duplicate_source
  | :config_invalid
```

# `t`

```elixir
@type t() :: %AshReplicant.Error{
  __exception__: true,
  bread_crumbs: term(),
  class: term(),
  op: atom() | nil,
  path: term(),
  reason: reason() | nil,
  resource: module() | nil,
  shape: String.t() | nil,
  splode: term(),
  stacktrace: term(),
  vars: term()
}
```

# `exception`

```elixir
@spec exception(opts :: Keyword.t()) :: %AshReplicant.Error{
  __exception__: true,
  bread_crumbs: term(),
  class: term(),
  op: term(),
  path: term(),
  reason: term(),
  resource: term(),
  shape: term(),
  splode: term(),
  stacktrace: term(),
  vars: term()
}
```

Create an `Elixir.AshReplicant.Error` without raising it.

## Keys

- :reason
- :resource
- :op
- :shape

# `scrub`

```elixir
@spec scrub(term(), module() | nil, atom()) :: t()
```

Normalize any error into a value-free `%AshReplicant.Error{}`. Never inspects a
message, changeset, or value — keeps only the struct-module name on `:shape`.

---

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