# `PhoenixKit.Migrations.Repair`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v2.13.7/lib/phoenix_kit/migrations/repair.ex#L1)

Runtime, additive-only verify-and-repair for the PhoenixKit migration
chain (spec §6, D6). Two public entry points:

  * `verify/1` — read-only. Never executes a create, never writes the
    version comment, never calls `Oban.Migration.up/1`.
  * `repair/1` — `dry_run: false` (the default) actually applies missing
    objects and comment-policy writes; `dry_run: true` runs the identical
    pipeline but only *plans* (see `PhoenixKit.Migrations.Repair.Executor.create_action/2`).

Both return `{:ok, PhoenixKit.Migrations.Repair.Report.t()}` on a completed
run, or `{:error, reason}` for a condition the pipeline refuses to proceed
past (manifest not generated, below-floor, comment ahead of code, a
pooled connection without `--unsafe-pooled`, or a detected concurrent
migration). `reason` is always either `:not_generated` or a `{tag, ...}`
tuple; `error_message/1` renders any of them to the same text
`mix phoenix_kit.repair`/`mix phoenix_kit.doctor` print.

## Options

  * `:prefix` — schema prefix, already resolved (default `"public"`).
    Callers resolving it from config/`--prefix`
    (`PhoenixKit.Install.PrefixConfig.resolve_prefix/1`) do so before
    calling here — this module does not read application config itself,
    the same way `PhoenixKit.Migrations.Postgres.up/1` does not.
  * `:repo` — defaults to `PhoenixKit.RepoHelper.repo/0`.
  * `:adopt` — R4 (§6.4). Only consulted when the raw comment is `nil`.
  * `:heal_comment` — R2's stale-low heal. Only consulted for an
    in-range comment whose marker cross-check found `{:stale_low, _}`.
  * `:unsafe_pooled` — skip the advisory lock and FK `VALIDATE` (§6.3).
    Required (else `{:error, {:pooled_connection, message}}`) whenever
    `PhoenixKit.Migrations.Repair.Environment.pooled?/2` says yes AND this
    run would actually write (`repair/1` with `dry_run: false`, the
    default). Never consulted for a read-only pass — `verify/1`, or
    `repair/1` with `dry_run: true` — which takes neither the lock nor
    the FK `VALIDATE` path regardless of the pooled verdict, so a pooled
    connection can never block one. This is what lets
    `mix phoenix_kit.doctor`'s manifest-repair check call `verify/1`
    plainly, with no flag, and still get real information on a
    PgBouncer-fronted deployment (a real, documented topology for this
    codebase's own runtime).
  * `:dry_run` — `repair/1` only; `verify/1` always runs as if this were
    `true` regardless of what is passed.

## Repair vs. a concurrently running migration

`Postgres.up/1` and `.down/1` take the same advisory lock key this module
takes (`PhoenixKit.Migrations.Repair.Environment.with_lock/2`) — spec §6.1 —
but the exclusion that buys is **one-directional**: a chain run started while
a repair holds the lock waits for it, whereas a repair started while a chain
run is mid-DDL is not blocked, because the generated wrappers disable their
DDL transaction and the migration side's transaction-scoped lock is therefore
released after its own statement (see `Postgres.acquire_chain_lock!/0` for the
full reasoning). For that direction — and on a pooled connection, where
advisory locking cannot be trusted at all — the before/after raw-comment
re-read (`PhoenixKit.Migrations.Repair.CommentPolicy.concurrent_migration?/2`,
S18) remains the mechanism, and it *detects* rather than prevents.

# `error_reason`

```elixir
@type error_reason() ::
  :not_generated
  | {:not_installed, prefix :: String.t()}
  | {:below_floor, comment :: pos_integer(), floor :: pos_integer()}
  | {:comment_unreadable, term()}
  | {:above_current, comment :: pos_integer(), current :: pos_integer()}
  | {:pooled_connection, String.t()}
  | {:concurrent_migration, before :: term(), after_ :: term()}
```

Every `{:error, _}` shape this module returns; see moduledoc.

# `error_message`

```elixir
@spec error_message(error_reason()) :: String.t()
```

Renders any `error_reason()` this module returns to operator-facing text — the wording `mix phoenix_kit.repair`/`mix phoenix_kit.doctor` both print verbatim.

# `repair`

```elixir
@spec repair(keyword()) ::
  {:ok, PhoenixKit.Migrations.Repair.Report.t()} | {:error, error_reason()}
```

Applies missing objects and comment-policy writes unless `dry_run: true`. See moduledoc.

# `verify`

```elixir
@spec verify(keyword()) ::
  {:ok, PhoenixKit.Migrations.Repair.Report.t()} | {:error, error_reason()}
```

Read-only. See moduledoc.

---

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