AgentToolPolicy.Access (AgentToolPolicy v0.1.0)

Copy Markdown View Source

This module provides pure capability-mask semantics for caller-provided agent tool universes.

Each entry identifies an integration or system scope. It selects :all, :read, or :none. The entry can add explicit tool names. Callers build universes and control credentials, persistence, and baselines.

Resolution fails closed for malformed stored entries. Use the resulting MapSet to advertise tools and authorize dispatch.

Summary

Types

The per-scope default level when a mask has no entry for a scope.

A parsed mask entry. The credential_id and extra fields are optional.

A maskable scope with a read or write classification for each tool.

A scope identity with kind and scope fields. It does not contain tools.

Selected tool names for each scope. Editors use this type as working state.

Functions

Renders a parsed mask to the string-keyed stored representation.

Folds a per-scope selection into the most permissive equivalent mask.

Canonicalizes a mask and drops entries equal to their caller-supplied baseline.

Parses the string-keyed stored representation into the atom-keyed policy shape.

Returns the allowed tool names over universe.

Validates that each mask entry is well-formed and belongs to universe.

Types

baseline()

@type baseline() :: (scope_ref() -> level())

The per-scope default level when a mask has no entry for a scope.

entry()

@type entry() :: %{
  :kind => kind(),
  :scope => String.t(),
  :level => level(),
  optional(:credential_id) => String.t() | nil,
  optional(:extra) => [String.t()]
}

A parsed mask entry. The credential_id and extra fields are optional.

kind()

@type kind() :: :integration | :system

level()

@type level() :: :all | :read | :none

scope()

@type scope() :: %{
  kind: kind(),
  scope: String.t(),
  tools: [%{name: String.t(), read: boolean()}]
}

A maskable scope with a read or write classification for each tool.

scope_ref()

@type scope_ref() :: %{kind: kind(), scope: String.t()}

A scope identity with kind and scope fields. It does not contain tools.

selection()

@type selection() :: [
  %{
    :kind => kind(),
    :scope => String.t(),
    :tools => [String.t()],
    optional(:credential_id) => String.t() | nil
  }
]

Selected tool names for each scope. Editors use this type as working state.

tool_access()

@type tool_access() :: [entry()]

universe()

@type universe() :: [scope()]

Functions

dump(tool_access)

@spec dump(tool_access()) :: [map()]

Renders a parsed mask to the string-keyed stored representation.

fold(selection, universe)

@spec fold(selection(), universe()) :: tool_access()

Folds a per-scope selection into the most permissive equivalent mask.

A full scope becomes :all. All reads with optional writes become :read. Other selections become :none with sorted extra values.

normalize(tool_access, universe, baseline)

@spec normalize(tool_access(), universe(), baseline()) :: tool_access()

Canonicalizes a mask and drops entries equal to their caller-supplied baseline.

normalize/3 never removes a credential-bearing entry because it matches the baseline.

parse(tool_access)

@spec parse([map()] | tool_access()) :: tool_access()

Parses the string-keyed stored representation into the atom-keyed policy shape.

Unknown kinds and levels become the :__invalid__ sentinel without creating atoms. Non-map entries also become an invalid sentinel. resolve/3 treats these sentinels as denials. validate/2 reports them.

resolve(tool_access, universe, baseline)

@spec resolve(tool_access(), universe(), baseline()) :: MapSet.t(String.t())

Returns the allowed tool names over universe.

Each scope resolves independently through its matching entry or the caller's baseline. The union is the MapSet to use for advertisement and dispatch.

validate(tool_access, universe)

@spec validate(tool_access(), universe()) :: :ok | {:error, String.t()}

Validates that each mask entry is well-formed and belongs to universe.