Explicit caller-owned identity mapping for Ash policies and actions.
scope/2 accepts a successful generic or Web Bot Auth RequestSeal.Verification and a map
containing exactly three required choices, with no defaults or extra keys:
:actor— a one-argument function receiving only the opaque attributed principal. It must return{:ok, actor}with a non-nil actor, or:error. It never receives signature key identifiers, parameters, or labels.:unattributed—:anonymousmaps an unattributed verification to a nil actor without calling the actor function;:rejectrejects it.:tenant—:none,{:value, tenant}, or a one-argument principal function returning{:ok, tenant}or:error. The function runs only for attributed principals; anonymous scopes otherwise have a nil tenant. An explicit value is caller-trusted: do not derive it from untrusted forwarded headers.
Principal attribution belongs to the selected discovery and custody contract.
This adapter neither establishes identity from key possession nor defines the
principal's shape. Attributed values remain opaque caller-owned terms; nil
and false are not attributed values and reject with :invalid_verification.
Only the atom :unattributed selects anonymous mapping; other opaque values,
including similarly named strings or atoms, are attributed. The generic
verifier returns :unattributed. Pass only a result from trusted verification
code; a caller-constructed or modified struct
cannot establish cryptographic validity or trusted identity.
The returned RequestSeal.Ash.Scope always requests authorize?: true.
Actor mapping grants no authority: Ash policies decide whether an action runs.
Per Ash's scope contract, explicit
actor, tenant, and authorization options take precedence over scope values;
passing authorize?: false explicitly is the caller's own authorization bypass.
Explicit context is deep merged by Ash, so callers also own any overrides.
Tenant scoping constrains queries, not records already loaded under another
tenant on primary-key data layers such as ETS. A loaded foreign-tenant record
can still be destroyed by primary key. Ash.destroy can seed an explicit
tenant from loaded-record metadata before resolving the scope; pass the
intended tenant: explicitly (or use Scope.to_opts/1). For destroy and update
actions, add a caller-owned policy check for the tenant_id == ^tenant()
condition alongside
the action's permission checks. Check the original record's tenant against the
action tenant before mutation, for example with an Ash.Policy.SimpleCheck;
a query filter alone does not enforce that check on a loaded record. See
Ash policies and
tenant expressions.
The caller owns the actor value. RequestSeal's default scope inspection,
projected context, and binding errors exclude it; actor options intentionally
carry it into Ash. Ash's own Forbidden inspect and enabled policy-breakdown
messages and logs can show the actor. Map principals to a minimal non-secret
actor or give the actor a custom Inspect implementation. Configure
Ash policy breakdowns
with that disclosure in mind.
Context contains only request_seal facts: selected label, profile, the
:attributed or :unattributed principal classification, canonical covered
identifier strings, content: :not_required | :checked,
freshness: :not_evaluated | :evaluated, and replay: :not_required | :claimed.
A RequestSeal.Replay.Receipt projects only to :claimed; context never includes
receipt contents, the principal value, keyid, parameters, message bytes, or keys.
Label and covered identifiers retain the core's 256-byte label, 256-component,
and 65,536-byte aggregate limits. These facts are policy input, not an
authorization decision or telemetry labels.
Errors use the :binding layer: :invalid_verification, :invalid_binding,
:unattributed, :actor_unbound, or :tenant_unbound. Callback errors, malformed
results, raises, throws, and exits retain no callback text. See RequestSeal.Error.
The core mapping and RequestSeal.Ash.Scope.to_opts/1 require no Ash dependency.
The optional protocol implementation follows the public
Ash scope protocol.
Summary
Functions
Maps verified facts and explicit caller bindings to an authorization-enabled scope.
Types
Functions
@spec scope(RequestSeal.Verification.t(), binding()) :: {:ok, RequestSeal.Ash.Scope.t()} | {:error, RequestSeal.Error.t()}
Maps verified facts and explicit caller bindings to an authorization-enabled scope.