Pure-function authorization API for SkillKit.
This module provides scope-based authorization for skills. It is a pure-function module — no process state, no GenServer, no registry coupling. All functions are safe to call from concurrent processes without synchronization.
ALL-of Semantics
Authorization uses ALL-of multi-scope semantics: a caller must hold every required scope in order to be authorized. Holding any subset is insufficient.
Scope coverage uses SkillKit.Scope.Validation.any_covers?/2 (OR primitive): a single
granted wildcard scope (e.g. "admin:*") can satisfy a required exact scope
(e.g. "admin:read"). ALL-of wraps this OR primitive across each required scope.
Error Signal Distinction
{:error, :unauthorized}— caller lacks required scopes{:error, reason}— provider returned an error (passed through unchanged){:error, :not_found}— never returned by this module; that atom is exclusive toSkillKit.Registry.get_skill/2
Usage
# authorize/2 — direct scope list
skill = %SkillKit.Skill{required_scope: ["admin:read"]}
{:ok, ^skill} = SkillKit.Authorization.authorize(skill, ["admin:read", "admin:write"])
{:error, :unauthorized} = SkillKit.Authorization.authorize(skill, ["tools:read"])
# authorize/3 — provider-resolved scopes
{:ok, ^skill} = SkillKit.Authorization.authorize(skill, MyApp.TokenProvider, %{token: "..."})
{:error, :token_expired} = SkillKit.Authorization.authorize(skill, MyApp.TokenProvider, %{token: "expired"})
# authorized?/2 — boolean convenience
true = SkillKit.Authorization.authorized?(skill, ["admin:*"])
false = SkillKit.Authorization.authorized?(skill, [])
Summary
Functions
Authorizes skill given a flat list of granted scope strings.
Authorizes skill by resolving scopes via provider.resolve_scopes(context).
Returns true when authorize(skill, granted_scopes) would return {:ok, _}.
Functions
@spec authorize(SkillKit.Skill.t(), [String.t()]) :: {:ok, SkillKit.Skill.t()} | {:error, :unauthorized}
Authorizes skill given a flat list of granted scope strings.
Returns {:ok, skill} when every required scope is covered by at least one
granted scope (ALL-of semantics). Returns {:error, :unauthorized} when any
required scope is not covered.
Fast-paths to {:ok, skill} immediately when required_scope is [].
@spec authorize(SkillKit.Skill.t(), module(), map()) :: {:ok, SkillKit.Skill.t()} | {:error, :unauthorized} | {:error, term()}
Authorizes skill by resolving scopes via provider.resolve_scopes(context).
provider must be an atom (module name) implementing SkillKit.AuthorizationProvider.
context is an opaque map passed through to the provider unchanged.
Fast-paths to {:ok, skill} without calling the provider when required_scope
is []. When the provider returns {:error, reason}, that error passes through
unchanged — it is never normalized to {:error, :unauthorized}.
Provider exceptions are NOT rescued (let it crash).
@spec authorized?(SkillKit.Skill.t(), [String.t()]) :: boolean()
Returns true when authorize(skill, granted_scopes) would return {:ok, _}.
Convenience wrapper for use in boolean contexts (guards, filters, pipeline conditions).