Cairnloop.Tool behaviour (cairnloop v0.1.0)

Copy Markdown View Source

Governed-tool behaviour and compile-time validating __using__ macro.

Host developers declare a governed tool with:

use Cairnloop.Tool,
  risk_tier: :read_only,
  title: "Lookup Order",
  description: "Retrieve an order by ID."

The macro validates risk_tier and approval_mode enum values at compile time (raises CompileError on bad values), derives a fail-closed approval_mode from risk_tier when omitted, and generates a __tool_spec__/0 returning a frozen %Cairnloop.Tool.Spec{} pure data struct.

A tool that declares no policy is denied by default — authorize/2 returns {:error, :no_policy_defined} unless overridden.

Callbacks

Required (host must implement):

  • run/3 — executes the tool; NOT called in Phase 13
  • changeset/2 — validates typed input via Ecto embedded schema (D-04)
  • scope/0 — returns list of required scope atoms

Optional:

  • authorize/2 — dynamic policy callback; defaults to {:error, :no_policy_defined} (deny-by-default, D-16)
  • custom_ui/0 — custom LiveView UI module; defaults to nil
  • preview/1 — human-readable consequence summary; no default (Phase 14 seam)

Summary

Callbacks

Dynamic policy callback. Returns :ok to permit or {:error, reason} to deny. Default implementation returns {:error, :no_policy_defined} (deny-by-default, D-16).

Returns an Ecto changeset for the tool's inputs. This is the typed-input seam (D-04). Host must implement; no default is injected.

Optional callback to provide a custom UI module (e.g. a LiveView module) that should be rendered instead of the auto-generated form.

Optional callback returning a human-readable consequence summary string. No default implementation — Phase 14 seam.

Executes the tool logic with the populated struct. Returns {:ok, result} or {:error, reason}.

Returns the list of scope atoms this tool requires to be present in the actor context. Used by the registry visibility filter and the Governance validation pipeline.

Functions

Derives the fail-closed approval_mode from a risk_tier value (D-11).

Types

actor_id()

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

context()

@type context() :: map()

Callbacks

authorize(actor_id, context)

@callback authorize(actor_id(), context()) :: :ok | {:error, reason :: atom()}

Dynamic policy callback. Returns :ok to permit or {:error, reason} to deny. Default implementation returns {:error, :no_policy_defined} (deny-by-default, D-16).

changeset(tool, attrs)

@callback changeset(tool :: struct(), attrs :: map()) :: Ecto.Changeset.t()

Returns an Ecto changeset for the tool's inputs. This is the typed-input seam (D-04). Host must implement; no default is injected.

custom_ui()

(optional)
@callback custom_ui() :: module() | nil

Optional callback to provide a custom UI module (e.g. a LiveView module) that should be rendered instead of the auto-generated form.

preview(tool)

(optional)
@callback preview(tool :: struct()) :: String.t()

Optional callback returning a human-readable consequence summary string. No default implementation — Phase 14 seam.

run(tool, actor_id, context)

@callback run(tool :: struct(), actor_id(), context()) :: {:ok, any()} | {:error, any()}

Executes the tool logic with the populated struct. Returns {:ok, result} or {:error, reason}.

NOT called in Phase 13 — execution is deferred to Phase 16.

scope()

@callback scope() :: [atom()]

Returns the list of scope atoms this tool requires to be present in the actor context. Used by the registry visibility filter and the Governance validation pipeline.

Functions

derive_approval_mode(arg1)

Derives the fail-closed approval_mode from a risk_tier value (D-11).

Called at macro-expansion time (not runtime) so this is a plain def, not a macro.

read_only    -> :auto
low_write    -> :requires_approval
high_write   -> :requires_approval
destructive  -> :always_block
unknown/nil  -> :always_block  (fail-closed default)