# `Cairnloop.Tool`
[🔗](https://github.com/szTheory/cairnloop/blob/main/lib/cairnloop/tool.ex#L1)

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)

# `actor_id`

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

# `context`

```elixir
@type context() :: map()
```

# `authorize`

```elixir
@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`

```elixir
@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* 

```elixir
@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`
*optional* 

```elixir
@callback preview(tool :: struct()) :: String.t()
```

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

# `run`

```elixir
@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`

```elixir
@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.

# `derive_approval_mode`

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)

---

*Consult [api-reference.md](api-reference.md) for complete listing*
