Represents a policy on an Ash.Resource
Summary
Functions
Evaluates a policy's check chain against already-computed facts and returns its decision.
Returns the first non-bypass policy considered responsible for a forbidden outcome, along with its computed state.
Default Options for Crux scenarios
Types
@type error_message() :: String.t() | (subject(), error_message_context() -> String.t() | Exception.t())
@type error_message_context() :: %{ resource: Ash.Resource.t(), action: Ash.Resource.Actions.action() | nil, actor: Ash.actor() | nil, domain: Ash.Domain.t() | nil, tenant: Ash.ToTenant.t() | nil }
@type subject() :: Ash.Query.t() | Ash.Changeset.t() | Ash.ActionInput.t()
@type t() :: %Ash.Policy.Policy{ __spark_metadata__: Spark.Dsl.Entity.spark_meta(), access_type: :strict | :filter | :runtime, bypass?: boolean(), condition: nil | Ash.Policy.Check.ref() | [Ash.Policy.Check.ref()], description: String.t() | nil, error_message: error_message() | nil, policies: [Ash.Policy.Check.t()] }
Functions
Evaluates a policy's check chain against already-computed facts and returns its decision.
Walks checks in source order. The first decisive check fixes the decision:
authorize_if XwithXtrue →:authorizedauthorize_unless XwithXfalse →:authorizedforbid_if XwithXtrue →:forbiddenforbid_unless XwithXfalse →:forbidden
Anything else leaves the state at :unknown and the walk continues. If no
check is decisive, the policy ends at :unknown — callers that need a
binary "did this policy deny?" should treat :unknown as :forbidden,
matching Ash's "if nothing authorized, the request is forbidden" rule.
Reads from the supplied facts map only. Strict checks are not invoked,
so this is safe to call from error-construction paths where rerunning a
strict check would surface unrelated errors (e.g. missing calculation
arguments). For pre-flight call sites that need lazy strict-check
evaluation, use the private policy_fails_statically?/2 in the policy
authorizer.
@spec expression( policies :: t() | Ash.Policy.FieldPolicy.t() | [t() | Ash.Policy.FieldPolicy.t()], check_context :: Ash.Policy.Check.context() ) :: Crux.Expression.t(Ash.Policy.Check.ref())
@spec fetch_fact( facts :: map(), check :: Ash.Policy.Check.t() | Ash.Policy.Check.ref() ) :: {:ok, Crux.Expression.t(Ash.Policy.Check.ref())} | :error
@spec fetch_or_strict_check_fact( Ash.Policy.Authorizer.t(), Ash.Policy.Check.t() | Ash.Policy.Check.ref() ) :: {:ok, Crux.Expression.t(Ash.Policy.Check.ref()), Ash.Policy.Authorizer.t()} | {:error, Ash.Policy.Authorizer.t()}
@spec responsible_for_forbidden([t() | Ash.Policy.FieldPolicy.t()], map()) :: {t(), :forbidden | :unknown} | nil
Returns the first non-bypass policy considered responsible for a forbidden outcome, along with its computed state.
A policy is considered responsible if:
- Its
conditionapplies given the facts (no condition resolves to{:ok, false}), - It is not a bypass (bypasses don't deny on their own; an unsatisfied bypass just falls through to the next policy), and
- Its
evaluate/2decision is:forbidden(explicit deny) or:unknown(no check authorized).
Within those, explicit :forbidden is preferred over :unknown. Returns
nil when no non-bypass policy is responsible.
@spec scenario_options(check_context :: Ash.Policy.Check.context()) :: Crux.opts(Ash.Policy.Check.ref())
Default Options for Crux scenarios
@spec solve(authorizer :: Ash.Policy.Authorizer.t()) :: {:ok, boolean() | [map()], Ash.Policy.Authorizer.t()} | {:error, Ash.Policy.Authorizer.t(), Ash.Error.t()}