# `Rho.Dyn.Check`
[🔗](https://github.com/GenericJam/rho/blob/v0.1.0/lib/rho/dyn/check.ex#L1)

The static check every staged file passes before it is compiled (the
pipeline's step 2, see `Rho.Dyn`), and `beam/2`, a second pass over the
compiled binaries.

**A best-effort guard against mistakes, not a sandbox.** It is there for
the model's mistakes: a tool that by accident calls something it
shouldn't, reaches into the Core, or installs code in a process that runs
for others. It does not hold against code written to get around it; the
BEAM has no capability isolation. Known ways around it, for a host with
stronger needs:

  * an atom handed in as data and called without parentheses, a function
    value smuggled in through a message;
  * raw messages to OTP's processes, a `report_cb` sent to
    `:logger_proxy` say, which then runs Dyn code there;
  * modules the deny-lists don't name: Logger.App (stopping Logger
    drops `Rho.Dyn.LogGuard`), `Logger.Backends.*`, `LoggerBackends`;
  * starting a child under a trusted supervisor (`:supervisor`,
    `Supervisor`, `DynamicSupervisor` call the child's start function in
    the supervisor), `:alarm_handler` (a `:gen_event` wrapper);
  * CPU, memory and message floods.

What keeps a mistake from sticking is the rest of the pipeline: versioned
modules, selftests in contained processes, screen-lock approval,
probation and automatic revert.

Source rules (each violation carries `file:line`; `<ns>` is the
namespace, `Rho.Dyn.Config`):

  * only `defmodule` at the top level of a file, named `<ns>.*`
    (`<ns>.G<n>` is reserved for compiled generations); no
    `defprotocol`, macros (`defmacro`, `quote`, `unquote`) or `@on_load`;
    `defimpl` only inside a module and for a Dyn module (`for:` left out,
    or a `<ns>.*` module), so the implementation is compiled as
    `<Protocol>.<ns>.G<n>.X`, which `beam/2` lets through;
  * no calls to, or references of: `:code`, `Code`, `Module`, `Port`,
    `Node`, `Mob.Dist`, `MobDeliver` (the Core's own updates), `File`
    (Dyn code's files go through a Core module the host allows, inside
    its roots), `Path.wildcard/2`,
    `:init`, `:file`, `:os.cmd`, `:erlang.halt` and the other code-loading
    and node-control functions listed in the `:partial` defaults,
    `System.halt/stop/cmd`, `Application.start/stop/put_env`, dynamic
    atoms (`String.to_atom`, ...), `:erlang.binary_to_term` (it makes any
    atom, a forbidden one too, and funs), and `Protocol.derive` /
    `consolidate` (they define or replace implementations for any type,
    the stdlib's too; `@derive` and `defimpl` for a Dyn module are the way);
  * nothing that controls or watches other processes or the VM: tracing
    (`:erlang.trace*`, `:trace`, `:seq_trace`, `:dbg`, `:erl_tracer`),
    `:erlang.suspend_process/resume_process`, `system_monitor` /
    `system_profile`, `:erts_debug`, `:erts_internal`, `:observer_backend`,
    and listing every process (`Process.list/0`, `:erlang.processes/0`);
    `:persistent_term` only to read (`put` / `erase` change VM-wide state
    and trigger a global GC);
  * nothing that installs code to run in other processes: `:logger`,
    `:error_logger`, `:logger_backends`, `:telemetry`, `:gen_event`,
    `:gen_statem` (it calls any OTP server), and `Logger`'s
    translators, backends and configuration (`Logger.info/1` and friends
    are fine); a Dyn process's own crash report is kept out of the
    logger's processes by `Rho.Dyn.LogGuard`;
  * not the router's name, `:mob_screen`: whatever arrives there is handed
    to the front host as a native reply, so a front screen sending to it
    could forge a capability grant (a picked file); nor `:mob_file_result`,
    the tag of a raw native file result, which the front host decodes and
    grants when one arrives; nor the native layers `:mob_nif` and
    `:mob_secure_store_nif` as values, since one held in a variable could
    be called without parentheses (`:forging`, atoms Dyn code may not hold);
  * the Core's own modules (`Rho.*` and the host's `:core` prefixes) are
    off limits except `Rho.Tool` (the behaviour a Dyn tool implements)
    and the host's `:allowed` ones, and so is the sign-in store
    (`MobSecureStore`, its NIF `:mob_secure_store_nif`); of Mob only the
    app-facing modules (screens, UI, theming, device features; the
    `:gated` defaults), not its native layer `:mob_nif` (Mob's Elixir
    modules are the API, and the host decides which of them Dyn code
    gets), and of `Mob.Screen` not the functions that start
    or drive screens outside the front (`start_root/3`, ...); the
    capability plugins (`MobCamera`, `MobLocation`, `MobSensors`,
    `MobMishka`, `MobThemes`, ...), `Nx` and `NxTfliteMob` are allowed;
  * `Process.exit/2` only on `self()`; `apply/3`, `spawn/3` and friends
    only with a module written out literally; no calls on a module held in
    a variable (`mod.fun()`); no `:"Elixir.Foo"` atoms; modules with
    forbidden functions (`System`, `:erlang`, `Process`, ...) may be
    called but not passed around as values or imported.

Aliases (`alias System, as: S`), multi-aliases and `~MOB` templates (whose
`{...}` expressions only exist once the sigil expands, `:sigils`) are
resolved first, so `S.halt()` and `<Text text={System.halt()} />` are
caught too.

The lists are a host seam (`config :rho, :dyn, check: [...]`, merged over
`defaults/0`: Operator's lists minus the entries naming Operator's own
code; see `Rho.Dyn.Config`).

# `target`

```elixir
@type target() :: :dyn | :dynamic | {:elixir, String.t()} | {:erlang, atom()}
```

# `violation`

```elixir
@type violation() :: %{
  file: String.t(),
  line: non_neg_integer() | nil,
  message: String.t()
}
```

# `beam`

```elixir
@spec beam([{module(), binary()}], pos_integer()) :: :ok | {:error, [violation()]}
```

The backstop on compiled generation `n`: every module of the result is
the generation's (`<ns>.G<n>.*`) or a protocol implementation for one of
them, named as Elixir names it (`Inspect.<ns>.G<n>.X`, from a `defimpl`
or a library's `use`), and none may call a forbidden function (import
table) or name a forbidden module (atom table), whatever macro produced
the code.

# `defaults`

```elixir
@spec defaults() :: keyword()
```

The default lists, as `config :rho, :dyn, check: [...]` takes them:
Operator's, except for the entries that name Operator's own code (its
sign-in NIF `:operator_secure_store`, and the caller-side functions of
`Operator.Core.Files`), which its config adds. The host's entries are
merged over these, keys written as modules or strings alike (a `nil`
value drops one; `:handlers` replaces the list).

# `format`

```elixir
@spec format([violation()]) :: String.t()
```

Violations as text, one `file:line: message` per line (what a rejection's `reason` says).

# `generation_module?`

```elixir
@spec generation_module?(module(), binary(), pos_integer(), MapSet.t(module())) ::
  boolean()
```

Is `mod` (`bin`) one of generation `n`'s modules, or a protocol
implementation for one of the build's (`mods`), named as Elixir names it?

# `policy`

```elixir
@spec policy() :: map()
```

The check's rules as they apply now: the defaults with the host's config over them.

# `run`

```elixir
@spec run(%{required(String.t()) =&gt; String.t()}) ::
  {:ok, [{String.t(), Macro.t()}]} | {:error, [violation()]}
```

Parses and checks `sources` (`%{relative_path => source}`). Returns the
parsed files, or every violation found.

# `sigils`

```elixir
@spec sigils() :: [atom()]
```

The template sigils whose expressions are code (`:sigils`), as their macro names.

---

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