# `Cairnloop.Tools.InternalNote`
[🔗](https://github.com/szTheory/cairnloop/blob/main/lib/cairnloop/tools/internal_note.ex#L1)

Example governed-write tool: appends an operator-only internal note to the
host-owned `cairnloop_messages` store.

## Usage

This is the Phase 16 proof-of-concept (ACT-01, D16-01) and the reference implementation
for host developers building governed-write tools. Copy this module and adapt the schema,
changeset, and `run/3` body.

## Design Notes

- `risk_tier: :low_write` → derives `approval_mode: :requires_approval` automatically (D-09/D-10).
- `scope/0` returns `[]` — no special scopes required (D16-01: operator-only, low blast radius).
- `authorize/2` overrides the deny-by-default to `:ok` — any authenticated operator may propose
  this action; the approval gate provides the safety barrier.
- `run/3` is the ONLY place an actual side effect occurs. It is called only by
  `Cairnloop.Workers.ToolExecutionWorker` after the full approval + re-validation chain (D16-03).
- Idempotency is implemented via an indexed `run_key` column existence check (D16-05).
  NEVER use a JSONB `metadata:` containment query — Ecto does not emit `@>` for map equality,
  and such a query would bypass the index entirely.
- The note row carries `role: :internal_note` so it is distinguishable from customer-visible
  messages and can be filtered by host queries (D16-01 "never customer-visible").
- Repo indirection: `Application.fetch_env!(:cairnloop, :repo)` — never `Cairnloop.Repo`
  directly. The host owns the repo; the library is a guest (D-02 / D16-02).

## Run key idempotency

The worker passes `:run_idempotency_key` in the execution `context`. `run/3` does an
indexed existence check before inserting. If the row already exists, returns
`{:ok, %{idempotent: true}}` without inserting — safe under Oban job replay (T-16-01).

## Atomicity precondition (WR-01)

The `run_idempotency_key` passed to `run/3` is attempt-scoped: it is derived from the
proposal's `idempotency_key` and the current `attempt` number, so a Oban retry gets a
**different** key. This design allows a retry to proceed cleanly if a prior attempt left
no evidence row.

**IMPORTANT for host tool authors copying this module:** your `run/3` MUST be a
**single atomic write** keyed on `run_key`. If your tool performs multiple writes (e.g.
row A then row B), a transient failure between them would be retried with a *new*
`run_idempotency_key` — the existence check for the new key finds nothing, and the
partial prior write (row A) is not rolled back. This could result in duplicate or
inconsistent state. Rule: one `run/3` invocation = one atomic operation (one `INSERT`,
one Ecto.Multi inside a single transaction, etc.).

# `run`

Appends an `internal_note` role row to `cairnloop_messages`.

Idempotent on `context[:run_idempotency_key]`: if a row with that `run_key` already
exists, returns `{:ok, %{idempotent: true}}` without inserting (T-16-01, D16-05).

Returns:
- `{:ok, %{message_id: id}}` — note inserted successfully
- `{:ok, %{idempotent: true}}` — duplicate key; note already written
- `{:error, changeset}` — insert failed (propagated for Oban retry logic)

---

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