DrinkMeAlice. Migrations
(drink_me_alice v0.1.0)
Copy Markdown
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/COMMITslots with nil results, mapped decision. Only this is success; - qualified abort — the pinned executor's statement-abort
silhouette (exactly one real error,
NotExecutedmarkers before it,Cancelledmarkers after). Only this is a definite failure, and only for it may thefailedbookkeeping mark be written; - uncertain — the commit-failure rewrite (multiple rewritten
Internalerrors; 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 readingschema_migrationforversion(anappliedrow 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.
Summary
Functions
Apply every pending migration over an authenticated root query
(see DrinkMeAlice.Admin.migrate/3). owner is a fresh per-run id.
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.
SHA-256 (lowercase hex) of a statement list, joined by newline.
Types
Functions
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 holdsmigration_lock;{:error, {:dirty, version, detail}}— astarted/failedrow 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. Inspectmigration_lockbefore 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}}— avars/verify_appliedcallback 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 (perOutcome) in a transaction, or a definite statement error outside one;{:error, {:invalid_migrations, _}}— the list failedcheck/1;{:error, reason}— transport/statement failure or averify_applied/varsrefusal.
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.
SHA-256 (lowercase hex) of a statement list, joined by newline.