MobDev.Plugin (mob_dev v0.7.4)

Copy Markdown View Source

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 and 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.

Summary

Types

One entry in activated_with_verify/0's return list.

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.

Functions

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

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

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

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

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.

Types

activated_entry()

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

One entry in activated_with_verify/0's return list.

verify_status()

@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.

Functions

activated()

@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(project_dir \\ File.cwd!())

@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): 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()

@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}.

host_config(otp_app, key, default \\ nil)

@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(plugin, allowed, fun)

@spec with_host_config_audit(atom(), [atom()], (-> 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.