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

Runtime resolution for the AshReplicant sink — the tenant/classification layer
that keeps `replicant` tenant-blind. Pure functions over compiled resource
metadata (no DB access):

  * `build_index/1` — reflect the configured domains into a
    `{source_schema, source_table} => resource` index, failing closed on a
    duplicate source key (ambiguous route).
  * `resolve_tenant/2` — per-row tenant from `tenant_attribute` / `tenant_mfa`,
    failing closed with `:tenant_required` on a nil/`false`/blank tenant (any value Ash would
    treat as unscoped); `resolve_tenant!/3` is the raising variant every apply path shares.
  * `writable_target/2` / `attrs_for_upsert/2` — map source string columns to
    their real writable targets, routing AshCloak-sensitive columns through the
    cloak argument while naming `encrypted_<col>` in `upsert_fields`. The bulk
    snapshot path computes `upsert_reflection/1` once and maps each row via
    `upsert_input/2` (the batch-invariant hoist).
  * `primary_key/1` / `pk_values/2` / `upsert_identity/1` / `upsert_action/1` /
    `destroy_action/1`.

# `source_key`

```elixir
@type source_key() :: {schema :: String.t(), table :: String.t()}
```

# `upsert_reflection`

```elixir
@type upsert_reflection() :: {[atom()], [atom()], MapSet.t(atom())}
```

The batch-invariant upsert reflection: `{skip, cloak_attrs, attribute_names}`.

# `attrs_for_upsert`

```elixir
@spec attrs_for_upsert(module(), map()) :: {map(), [atom()]}
```

# `build_index`

```elixir
@spec build_index([module()]) ::
  {:ok, %{required(source_key()) =&gt; module()}}
  | {:error, {:duplicate_source, source_key()}}
  | {:error, {:missing_source_table, module()}}
```

# `business_key_values`

```elixir
@spec business_key_values(module(), map()) :: map()
```

The declared SCD2 business-key values from a string-keyed source `record`.

# `destroy_action`

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

The primary destroy action name (mirrors `upsert_action/1` for `:create`).

# `lookup`

```elixir
@spec lookup(%{required(source_key()) =&gt; module()}, String.t() | nil, String.t()) ::
  module() | nil
```

Look up the mirror resource for a source `{schema, table}` in an index built by
`build_index/1`, applying the SAME `nil`-schema → `"public"` default the index
keys use (so the convention lives in one place next to the builder). Returns the
resource, or `nil` for an unmapped table.

# `open_version_query`

```elixir
@spec open_version_query(module(), map(), integer(), keyword()) :: Ash.Query.t()
```

A query over `resource` selecting the CURRENT open version of the business key in
`record`, whose `valid_from_lsn` is strictly less than `lsn` (open-path close) or at
most `lsn` (delete/terminal close, `inclusive?: true`). Uses dynamic `^ref/1` because
the window column names are DSL-configured.

# `pk_values`

```elixir
@spec pk_values(module(), map()) :: map()
```

# `primary_key`

```elixir
@spec primary_key(module()) :: [atom()]
```

# `resolve_tenant`

```elixir
@spec resolve_tenant(module(), map()) :: {:ok, term()} | {:error, :tenant_required}
```

# `resolve_tenant!`

```elixir
@spec resolve_tenant!(module(), map(), atom()) :: term()
```

The fail-closed bang variant of `resolve_tenant/2`: returns the per-row tenant, or raises a
value-free `AshReplicant.Error` (`reason: :tenant_required`) when the row carries no usable
tenant. `op` labels the failing sink operation (`:upsert` / `:destroy` / ...) in the
structural error. The single tenant-resolution entry point shared by every apply path
(`Apply`, `Apply.Scd2`), so `:tenant_required` fails identically everywhere.

# `upsert_action`

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

The upsert-capable create action name (the resource's primary create action).

# `upsert_identity`

```elixir
@spec upsert_identity(module()) :: atom() | nil
```

The upsert identity name from the DSL (`nil` → primary-key upsert).

# `upsert_input`

```elixir
@spec upsert_input(upsert_reflection(), map()) :: {map(), [atom()]}
```

Map one source `record` to `{inputs, upsert_fields}` under a precomputed
`upsert_reflection/1`. AshCloak-sensitive columns pass plaintext under the cloak
argument while `upsert_fields` names `encrypted_<col>`.

# `upsert_reflection`

```elixir
@spec upsert_reflection(module()) :: upsert_reflection()
```

Compute the batch-invariant upsert reflection for a resource ONCE — the `skip`
list, the AshCloak cloak attributes, and the attribute-name set. Thread it into
`upsert_input/2` per row of a column-homogeneous batch (the snapshot bulk path)
to avoid re-deriving these for every row. Single-record callers use
`attrs_for_upsert/2`, which computes the reflection inline.

# `version_open_input`

```elixir
@spec version_open_input(module(), map(), map()) :: {map(), [atom()]}
```

The `{inputs, upsert_fields}` for OPENING a version: the source data columns (via the
existing upsert reflection) PLUS the window columns (`valid_from_lsn`, optional
`valid_from_ts`, `valid_to_lsn: nil`, optional `is_current: true`). `upsert_fields`
names every window column so a same-`lsn` re-open coalesces in place.

# `writable_target`

```elixir
@spec writable_target(module(), String.t()) :: {:ok, atom()} | :skip
```

---

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