# `MobDev.Plugin`
[🔗](https://github.com/genericjam/mob_dev/blob/master/lib/mob_dev/plugin.ex#L1)

Compile-time host-config surface for code-generated plugins.

Spec-v2 plugins that generate their contributions from the host app's
configuration — e.g. a `mob_ash` plugin reading the host's registered
Ash domains, or a `mob_ecto` plugin reading its schemas — read that
config through this function rather than calling `Application.get_env/3`
directly. Routing every host-config read through one named surface is
what later lets the plugin audit (see
[`MOB_PLUGINS.md`](https://github.com/GenericJam/mob/blob/master/MOB_PLUGINS.md) and
[`MOB_PLUGIN_SECURITY.md`](https://github.com/GenericJam/mob/blob/master/MOB_PLUGIN_SECURITY.md))
verify exactly which keys a generator touches.

When a generator runs under `with_host_config_audit/3` (which the
build-time generator runner uses), every read is checked against the
plugin's declared `:host_config_keys` and recorded; an undeclared read
fails the build loudly. Outside an audit scope (e.g. in tests) it is a
plain `Application.get_env/3`.

# `activated_entry`

```elixir
@type activated_entry() :: {Path.t(), map() | nil, verify_status()}
```

One entry in `activated_with_verify/0`'s return list.

# `verify_status`

```elixir
@type verify_status() ::
  :ok | :unsigned | {:error, MobDev.Plugin.Verify.verify_error()}
```

Verification status returned from `activated_with_verify/0`. `:ok` means the
manifest was loaded after a passing signature + tamper check; `:unsigned`
means there was no manifest at all (tier-0 plugin — no signature required);
`{:error, reason}` means the plugin failed verification and its manifest
bytes were never eval'd. Reasons come from `MobDev.Plugin.Verify.verify_error/0`.

# `activated`

```elixir
@spec activated() :: [{Path.t(), map() | nil}]
```

The activated plugins as `{plugin_dir, manifest}` pairs, ready for
`MobDev.Plugin.Merge`.

Resolves each activated name to its dependency directory and loads its
manifest via `MobDev.Plugin.Verify.load_verified/1` — signature and
file-integrity checks run **before** `Code.eval_file` (MOB-74), so a
plugin that fails verification never has its `priv/mob_plugin.exs`
executed. Failed plugins come back as `{dir, nil}` here; callers that
need to distinguish "tier-0 plugin (no manifest)" from "verify failed"
should use `activated_with_verify/0` — that's the shape
`SignatureGate.check_activated/1` consumes to produce friendly
build-blocking errors. Activated names that don't resolve to a dep
are skipped — `mix mob.plugins` is where that mismatch surfaces
to users.

# `activated_names`

```elixir
@spec activated_names(Path.t()) :: [atom()]
```

The activated plugin names — `config :mob, :plugins` from `mob.exs`.

Activation is the second opt-in step (see [`MOB_PLUGINS.md`](https://github.com/GenericJam/mob/blob/master/MOB_PLUGINS.md)): a plugin in
`deps` contributes nothing until it appears here. When there is no
`mob.exs`, falls back to the loaded Application env, then `[]`. A
`mob.exs` that fails to evaluate raises the reader's error (MOB-280) —
treating it as "no plugins" would link no plugin NIFs and surface only as
`:nif_not_loaded` at runtime.

# `activated_with_verify`

```elixir
@spec activated_with_verify() :: [activated_entry()]
```

Same as `activated/0` but also returns the verification status per plugin.

`SignatureGate.check_activated/1` consumes this shape so it can produce a
clear build-blocking error naming the failed plugin — without needing to
re-load or re-verify anything. A plugin that failed verification appears as
`{dir, nil, {:error, reason}}`; a tier-0 plugin (no `priv/mob_plugin.exs`
and no signature required) appears as `{dir, nil, :unsigned}`.

Each plugin appears once, however many times it is activated (MOB-325).

# `host_config`

```elixir
@spec host_config(atom(), atom(), term()) :: term()
```

Reads `key` from the host application's environment, returning `default`
when the key is unset.

`otp_app` is the host app's OTP application name — the atom under which it
registers `config :my_app, ...`. Code-generated plugins call this during
the compile step:

    domains = MobDev.Plugin.host_config(:my_app, :ash_domains, [])

Under an audit scope, reading a key the plugin didn't declare in its
manifest `:host_config_keys` raises — the generator must declare what it
touches so `mix mob.audit_plugins` can verify it.

# `with_host_config_audit`

```elixir
@spec with_host_config_audit(atom(), [atom()], (-&gt; result)) ::
  {result, [{atom(), atom()}]}
when result: term()
```

Runs `fun` with host-config auditing scoped to `plugin` (allowing only the
keys in `allowed`, the plugin's manifest `:host_config_keys`). Returns
`{result, reads}` where `reads` is the ordered list of `{otp_app, key}` the
generator actually touched. Nested scopes restore the prior one on exit.

---

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