# `AshReplicant.Resource.Verifiers.ValidateActionMultitenancy`
[🔗](https://github.com/baselabs/ash_replicant/blob/v0.3.0/lib/ash_replicant/resource/verifiers/validate_action_multitenancy.ex#L1)

Compile-verifier (surfaced as a Spark diagnostic; build-blocking under
`--warnings-as-errors`): the **sink-selected actions** of a multitenant replicant
resource must not declare `multitenancy :bypass` / `:bypass_all`.

The sink mirrors through the host's PRIMARY create (upsert, and the SCD2 version-open),
PRIMARY destroy, and — for an SCD2 resource — the configured `history_close_action`.
It also READS through the PRIMARY read: the SCD2 close (`Ash.bulk_update`) and mirror
delete (`Ash.bulk_destroy`) build an `Ash.Query.do_filter` over the primary read to
match the rows they update/delete, and under the stream strategy that read must be
tenant-scoped. Ash keys the tenant scoping on each action's `:multitenancy` mode:

- `:enforce` (default) — force-sets/filters the discriminator AND requires a tenant → scoped;
- `:allow_global` — force-sets/filters when a tenant is present (the sink ALWAYS passes a
  resolved tenant, fail-closed on nil/`false`) → scoped;
- `:bypass` / `:bypass_all` — neither scopes nor requires → the tenant the sink passes is
  **silently ignored** and the write/read spans tenants (fail-open isolation). On a read this
  lets a `bulk_update`/`bulk_destroy` match and mutate ANOTHER tenant's rows.

So this verifier rejects `:bypass` / `:bypass_all` on any sink-selected action (primary
read/create/destroy + SCD2 close) of a multitenant resource. It fires only when the resource
declares multitenancy (a non-multitenant resource has no tenant to bypass). A non-sink action's
mode is the host's business and is not checked. Messages are value-free — they name the action
and its declared mode, never a row value.

---

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