Encryptor.Ecto.VaultProfileError exception (Encryptor.Ecto v0.2.0)

Copy Markdown View Source

Raised when a field declared tenant: :none names a :tenant-profile vault (ADR-0001 decision 5e, acceptance amendment 3).

A :none field omits the tenant key from the vault call entirely. A :tenant-profile vault carries the tenant reference in its required set and refuses an operation without it, so "a tenant vault with the pair omitted" is not a representable configuration: the pairing is wrong, not merely unlucky, and the host runs a second :single-profile vault for its global fields.

Why the check is worth its per-call cost

Left unchecked, the misconfiguration does still fail on the first write - but as whatever the vault happens to refuse first, which depends on the vault's key provider rather than on the pairing. Removing this check from this package's own fixtures turns the failure into an Encryptor.Ecto.EncryptError reading {:invalid_selector, :default}, a provider complaining about a selector nobody wrote; a provider that accepts that selector gets one step further and fails the required-context check instead, as Encryptor.Ecto.MissingContextError. Neither names the field's tenant: :none, neither names the vault's profile, and neither says what to change.

This exception exists so the pairing rule fails in its own words, naming the field, the vault it named, and the profile that vault resolved to. It is a distinct member of the family rather than a distinct kind of thing: the shared shape, the redaction rules, and the prohibition in Encryptor.Ecto.Error all apply unchanged.

Why it is raised from dump/3 and load/3 rather than at compile time

:context_profile is ordinary vault configuration, and it arrives through the same five layers as everything else the vault resolves - three of which (application environment, start_link/1 options, the optional init/1) do not exist when the vault module is compiled, let alone when a downstream Ecto type is. The authoritative value is the frozen Encryptor.Vault.Config struct the vault publishes at start, read through Encryptor.Vault.Config.fetch/1.

Reading the use options at compile time would be right in the common case and wrong in exactly the case a host varies the profile per environment, which is worse than checking later: it is a check that holds until it matters. Deriving the rule from the running vault's own configuration is the same one-source-of-truth property the tenant reference gets, so the check and the vault it checks cannot disagree.

:vault and :profile

:vault is the vault module the declaration named, and :profile is the profile it resolved to at start. Both are module and atom values from the declaration and the configuration; neither is key material.

Summary

Types

t()

An exception in the encrypted-field family.

Types

t()

@type t() :: %Encryptor.Ecto.VaultProfileError{
  __exception__: true,
  column: term(),
  context_keys: term(),
  profile: term(),
  reason: term(),
  table: term(),
  tenant: term(),
  vault: term()
}

An exception in the encrypted-field family.