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

Finds the concrete `PhoenixKit.Migrations.ExpectedSchema.Behaviour`
implementation to consume — the single choke point every P2 consumer
(the repair engine, `mix phoenix_kit.repair`, `mix phoenix_kit.doctor`,
`mix phoenix_kit.release_check`'s chain-hash assertion) goes through,
so "the manifest is not generated yet" is handled identically everywhere
instead of once per call site.

## Why this has to exist at all

`PhoenixKit.Migrations.ExpectedSchema` — the tool-generated manifest — does
not exist in this repository. It is produced from a real migrated scratch
database (spec §5.1/§8.3) and lands with the squash PR (P3); everything in
P2 has to build and run correctly *before* that happens. Every consumer
must therefore treat "the module is absent" as an ordinary, expected
runtime condition — not an exception to guard against defensively in each
of them — which is exactly what `resolve/0` turns it into:
`{:error, :not_generated}`, no raise, no crash, ever.

## Resolution order

  1. `Application.get_env(:phoenix_kit, :expected_schema_module)` — the
     test/override hook. Not read from `config/config.exs` anywhere in this
     library; set it with `Application.put_env/3` (and clean up with
     `on_exit/1` — this is process-global application config, not
     per-process state).
  2. `default_module/0` (`PhoenixKit.Migrations.ExpectedSchema`) — the real
     manifest, once P3 generates it.

Either way, the resolved module must actually be loaded
(`Code.ensure_loaded?/1`) *and* export `objects/1`, `data_invariants/1`, and
`chain_hash/0` (checked via `__info__(:functions)` membership, not
`function_exported?/3` — this codebase hit real async-test flakiness from
that check running before a just-compiled test-support module was loaded
into the process calling it; see `PhoenixKit.MigrationTest`'s `exports?/3`
for the same fix applied previously). A misconfigured override pointing at
some unrelated-but-loaded module therefore resolves to the same
`{:error, :not_generated}` as a genuinely absent module, rather than
surfacing as a confusing `UndefinedFunctionError` deep inside a caller —
both cases mean "there is nothing usable to hand back".

## Usage

    case Resolver.resolve() do
      {:ok, module} ->
        module.objects(prefix)

      {:error, :not_generated} ->
        {:error, Resolver.not_generated_message()}
    end

Testing against a fixture:

    setup do
      Application.put_env(:phoenix_kit, :expected_schema_module, PhoenixKit.Test.FixtureExpectedSchema)
      on_exit(fn -> Application.delete_env(:phoenix_kit, :expected_schema_module) end)
    end

# `resolution`

```elixir
@type resolution() :: {:ok, module()} | {:error, :not_generated}
```

`resolve/0`'s return shape.

# `default_module`

```elixir
@spec default_module() :: module()
```

The manifest module name every consumer resolves to absent an override —
`PhoenixKit.Migrations.ExpectedSchema`, the module P3's squash PR generates.
Exposed so callers/tests can reference it without repeating the literal
atom (and so a future rename only has to change it here).

# `not_generated_message`

```elixir
@spec not_generated_message() :: String.t()
```

The exact wording every P2 consumer should surface for
`{:error, :not_generated}`, so the message is identical whether it comes
from the repair engine, `mix phoenix_kit.repair`, or `mix phoenix_kit.doctor`.

# `resolve`

```elixir
@spec resolve() :: resolution()
```

Resolves the `PhoenixKit.Migrations.ExpectedSchema.Behaviour` implementation
to consume. Never raises.

Returns `{:ok, module}` when the resolved module is loaded and exports the
full contract, `{:error, :not_generated}` otherwise (absent, not yet
compiled, or loaded-but-not-conformant — see the moduledoc's "Resolution
order" section for why those collapse to the same result).

---

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