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

Host-side build-time gate: runs `Verify.verify_plugin/2` + the
`TrustStore` check across every activated plugin and refuses the
build on any failure.

This is the Phase 2 cryptographic counterpart to the capability
drift check in `Validator`. Both run at the same hook point inside
`Validator.raise_on_capability_drift!/1` so the iOS-sim, iOS-device,
and Android paths all enforce them as a one-liner.

Three distinct failure modes are surfaced (per `MOB_PLUGIN_SECURITY.md`,
Phase 2):

- **Missing signature** — author hasn't run `mix mob.plugin.sign`.
  Suppressible per-plugin via `config :mob, :acknowledge_unsafe_plugins`
  with a persistent banner.
- **Invalid signature** — sig is present but doesn't verify; the
  manifest or sources have been tampered with after signing. Not
  suppressible.
- **Untrusted fingerprint** — signature verifies but the public key
  isn't in `config :mob, :trusted_plugins` (or is a different key
  from the trusted one, the key-rotation case). Not suppressible;
  user must run `mix mob.plugin.trust <name>`.

# `gate_error`

```elixir
@type gate_error() ::
  {:missing_signature, atom()}
  | {:missing_pubkey, atom()}
  | {:invalid_signature, atom()}
  | {:untrusted, atom(), MobDev.Plugin.Crypto.fingerprint(),
     MobDev.Plugin.Crypto.fingerprint() | nil}
```

Errors `check_plugin/2` can return.

# `check_activated`

```elixir
@spec check_activated([{Path.t(), map() | nil}]) :: :ok | {:error, [gate_error()]}
```

Runs the signature + trust check across `plugins` (the
`MobDev.Plugin.activated/0` shape — `[{plugin_dir, manifest}]`).

Returns `:ok` when every plugin verifies AND is trusted (or, for
missing signatures, is listed in `config :mob, :acknowledge_unsafe_plugins`).
Returns `{:error, errors}` otherwise — a list of `t:gate_error/0` tagged
by plugin name.

Reads the trust map from `mob.exs` (via `TrustStore.load_trusted_plugins/0`)
and the acknowledgement list from `:mob`'s Application env or `mob.exs`.
Pass the trust_map + acknowledged list explicitly via
`check_activated/3` from tests that need isolation.

# `check_activated`

```elixir
@spec check_activated(
  [{Path.t(), map() | nil}],
  MobDev.Plugin.TrustStore.trust_map(),
  [atom()]
) ::
  :ok | {:error, [gate_error()]}
```

Pure variant of `check_activated/1` for tests.

# `maybe_print_unsafe_banner`

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

Prints a stderr banner when any activated plugin is allowed only via
`:acknowledge_unsafe_plugins`. Idempotent within a single Mix invocation
in spirit — the banner fires every time it's called, so callers should
invoke it once per build.

# `raise_on_signature_drift!`

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

Runs `check_activated/1` and raises a `Mix.raise/1` with a clear,
actionable message when any plugin fails. No-op on success.

---

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