# `DrinkMeAlice.Migrations`

A checksummed, locked, versioned migration runner; the migration list
is supplied by the caller.

Bookkeeping: `schema_migration` rows record `started`/`applied`/`failed` per version with the SHA-256
of the version's joined statements; a `migration_lock:main` row makes
concurrent ownership a hard conflict. The runner refuses a database
that holds a lock it did not take, a `started`-but-unfinished row, or a
checksum that disagrees with its source.

## Evidence rules (shared with `DrinkMeAlice.Outcome`)

Every transaction-shaped operation — lock acquisition, migration
application — is classified by `DrinkMeAlice.Outcome.classify_tx/3`
with the script's positional context, so the runner uses the SAME
three-way distinction as the write path:

- **observed commit** — full-length reply, well-formed entries, OK
  `BEGIN`/`COMMIT` slots with nil results, mapped decision. Only this
  is success;
- **qualified abort** — the pinned executor's statement-abort
  silhouette (exactly one real error, `NotExecuted` markers before it,
  `Cancelled` markers after). Only this is a definite failure, and
  only for it may the `failed` bookkeeping mark be written;
- **uncertain** — the commit-failure rewrite (multiple rewritten
  `Internal` errors; the storage commit may already be durable),
  malformed or unexpected-length replies. The runner writes NOTHING
  and retains the lock: `{:error, {:lock_retained,
  {:migration_unknown, version, why}}}`. Reconcile by reading
  `schema_migration` for `version` (an `applied` row with the expected
  checksum means the migration committed) and then removing the lock
  row; anything else needs investigation before a retry.

Non-transaction operations (bootstrap, the state read, the release
DELETE) demand their own complete evidence: exact statement count,
well-formed `{status, result}` entries on every slot, and the expected
result shape. An empty or malformed reply is a refusal, never proof of
execution. A release that cannot be proven is reported
(`{:lock_release_unproven, _}`), never discarded, and is never
attempted through a session that already reported transport loss.

Callbacks (`vars`, `verify_applied`) and the caller's `query` function
may crash without leaking the lock: a callback crash becomes a typed
error with the lock released; a query-function crash during an
operation with durable effect is treated exactly like transport loss.

# `migration`

```elixir
@type migration() :: %{
  :version =&gt; pos_integer(),
  :name =&gt; binary(),
  :statements =&gt; [binary()],
  optional(:seed) =&gt; [binary()],
  optional(:vars) =&gt; (keyword() -&gt; {:ok, map()} | {:error, term()}),
  optional(:verify_applied) =&gt; (query() -&gt; :ok | {:error, term()})
}
```

# `query`

```elixir
@type query() :: (binary(), map() -&gt; {:ok, list()} | {:error, term()})
```

# `apply_all`

```elixir
@spec apply_all(query(), binary(), [migration()], keyword()) :: :ok | {:error, term()}
```

Apply every pending migration over an authenticated root `query`
(see `DrinkMeAlice.Admin.migrate/3`). `owner` is a fresh per-run id.

- `:ok` — every migration observed committed (or already applied with
  matching checksums) AND the lock release was proven;
- `{:error, :locked}` — another owner holds `migration_lock`;
- `{:error, {:dirty, version, detail}}` — a `started`/`failed` row or a
  checksum mismatch; refuses until reconciled by hand;
- `{:error, {:lock_state_unknown, detail}}` — lock acquisition ended
  uncertain (transport loss, a crashed query function, or an
  unqualified reply): the lock may or may not be held. Inspect
  `migration_lock` before retrying;
- `{:error, {:lock_retained, {:migration_unknown, version, detail}}}` —
  a migration transaction's outcome is UNCERTAIN (commit-failure
  rewrite, malformed/unexpected reply, crashed query function).
  Nothing was overwritten and the lock is still held; reconcile as the
  module doc describes before retrying;
- `{:error, {:lock_release_unproven, detail}}` — the run itself is
  known (`{:run_ok, reason}` or `{:run_error, run_reason, reason}`) but
  the release DELETE could not be proven; the lock row may remain;
- `{:error, {:callback_crash, class}}` — a `vars`/`verify_applied`
  callback crashed before anything was submitted for that migration;
  the lock WAS released;
- `{:error, {:incomplete_reply, got, expected}}` — bootstrap evidence
  was empty or truncated (bootstrap runs before any lock exists);
- `{:error, {:bad_statement, detail}}` — a malformed bootstrap entry;
- `{:error, {:statement_err, msg}}` — a QUALIFIED statement abort (per
  `Outcome`) in a transaction, or a definite statement error outside
  one;
- `{:error, {:invalid_migrations, _}}` — the list failed `check/1`;
- `{:error, reason}` — transport/statement failure or a
  `verify_applied`/`vars` refusal.

# `check`

```elixir
@spec check([migration()]) :: :ok | {:error, term()}
```

Validate a migration list: contiguous versions from 1, names that are
identifiers, non-empty binary statement lists within bounds. Every
member must be a map — anything else is a typed refusal, never a
raise.

# `checksum`

```elixir
@spec checksum([binary()]) :: binary()
```

SHA-256 (lowercase hex) of a statement list, joined by newline.

---

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