AshReplicant. Resource. Verifiers. ValidateMultitenancy
(AshReplicant v0.3.0)
Copy Markdown
View Source
Compile-verifier for the replicant tenant sources (surfaced as a Spark
diagnostic; build-blocking under --warnings-as-errors). Two arms.
tenant_attribute
When a tenant_attribute is set it is the per-row tenant discriminator Ash
injects as a plaintext filter and force-set. It fails closed unless it is:
- not classified
sensitive— an encrypted discriminator would match nothing (fail-open isolation), andash_replicantholds no key material; - not listed in
skip— a skipped discriminator is never written, so the tenant filter matches nothing (fail-open isolation); - a declared attribute — an undeclared discriminator cannot be resolved or force-set;
- not binary-storage-typed — the discriminator is a plaintext comparator; a tag/base64 discriminator would scope inconsistently; and
- backed by an Ash
multitenancyblock — the sink passes the resolved per-row tenant to Ash as thetenant:option, which Ash HONORS only under declared multitenancy. With nomultitenancyblock,tenant:is silently ignored and every tenant's rows are mirrored UNSCOPED (fail-open isolation).
tenant_mfa
An mfa-resolved tenant is passed to Ash as the same tenant: option, so it has
the identical "no multitenancy block ⇒ tenant: ignored ⇒ every tenant
mirrored unscoped" fail-open. This arm requires an Ash multitenancy block when
tenant_mfa is declared — any strategy satisfies (both :attribute and
:context honor tenant:; :context is the typical mfa pairing, since the mfa
value is a computed function result, not a stored attribute). The shape checks
above do not apply to the mfa arm — the tenant is a function result, not a
declared column.
Multitenancy block :attribute shape
Independent of the tenant source, when the resource declares strategy :attribute
the multitenancy block's OWN attribute is force-set to the plaintext tenant on
write and filtered on read. It must be a plaintext, non-sensitive comparator — a
sensitive-classified or binary-storage-typed discriminator would store/compare a
mismatched value and silently mis-scope (reads return empty). This runs for both
tenant sources (and a global :attribute resource). An AshCloak-encrypted attribute
is already rejected by Ash's own multitenancy verifier (the cloak transform removes
the plain attribute), so it is not re-checked here.
Messages are value-free: they name schema structure (the attribute name or the
tenant_mfa key), never a row value or the mfa target's arguments.