SkillKit.Scope.Validation (SkillKit v0.1.0)

Copy Markdown View Source

Pure-function scope validation and wildcard matching for SkillKit.

Scope Format

Scopes are two-segment strings of the form "namespace:action", where both segments must match ^[a-z][a-z0-9_-]*$ (lowercase ASCII, digits, hyphens, underscores). Examples: "admin:read", "skills:execute", "tools:delete-all".

A wildcard scope "namespace:*" is valid format and means "any action within the given namespace". The bare string "*" is NOT a valid scope.

Matching Rules

  • Exact match: "ns:action" covers "ns:action" only.
  • Wildcard match: "ns:*" covers "ns:action" for any valid action.
  • No cross-namespace bypass: "ns1:*" does NOT cover "ns2:action" even if ns1 is a substring of ns2.
  • Segment boundary enforcement: Three-segment strings (e.g. "a:b:c") are never matched, even by a wildcard.

Multi-Scope Semantics

any_covers?/2 checks whether ANY scope in a granted list covers the required scope (OR semantics). ALL-of semantics — where a caller must hold every required scope — are implemented in the SkillKit.Authorization module.

Examples

iex> SkillKit.Scope.Validation.valid?("admin:read")
true

iex> SkillKit.Scope.Validation.valid?("admin:*")
true

iex> SkillKit.Scope.Validation.valid?("*")
false

iex> SkillKit.Scope.Validation.covers?("admin:*", "admin:read")
true

iex> SkillKit.Scope.Validation.covers?("ski:*", "skills:read")
false

iex> SkillKit.Scope.Validation.any_covers?(["admin:*", "other:read"], "admin:write")
true

Summary

Types

A scope string, e.g. "admin:read" or "admin:*"

Functions

Returns true when any scope in granted_list covers required.

Returns true when granted covers required.

Returns true when scope is a syntactically valid scope string.

Validates scope and returns a tagged tuple.

Types

scope()

@type scope() :: String.t()

A scope string, e.g. "admin:read" or "admin:*"

Functions

any_covers?(granted_list, required)

@spec any_covers?(term(), term()) :: boolean()

Returns true when any scope in granted_list covers required.

granted_list must be a list; any other value (including nil) returns false. Empty list returns false.

covers?(granted, required)

@spec covers?(term(), term()) :: boolean()

Returns true when granted covers required.

Supports exact match and wildcard match. Both arguments must be valid scope strings; any malformed or non-binary input returns false without raising.

valid?(scope)

@spec valid?(term()) :: boolean()

Returns true when scope is a syntactically valid scope string.

Accepts exact scopes ("ns:action") and wildcard scopes ("ns:*"). Rejects non-binary values, whitespace-padded strings, bare "*", three-segment strings, and strings with uppercase characters.

validate(scope)

@spec validate(term()) ::
  {:ok, scope()}
  | {:error,
     :not_a_string
     | :leading_trailing_whitespace
     | {:invalid_scope_format, term()}}

Validates scope and returns a tagged tuple.

Returns {:ok, scope} for valid scopes, or one of:

  • {:error, :not_a_string} — input is not a binary
  • {:error, :leading_trailing_whitespace} — binary has surrounding whitespace
  • {:error, {:invalid_scope_format, scope}} — binary but invalid format