# `ExSandbox.Mechanism.Beam`
[🔗](https://github.com/FoundryStack/ex_sandbox/blob/v1.2.0/lib/ex_sandbox/mechanism/beam.ex#L1)

Runs a tenant's Elixir application on its own hardened OS-level BEAM node
(005 T027).

## What makes this an isolation boundary

Not the BEAM. A supervised process tree inside the platform's VM would give a
tenant its own supervision, its own registry, and its own name — and none of
`FR-001` through `FR-011`. It would share the platform's atom table (never
garbage collected, so `FR-006` would be unachievable), its memory, its
process list, its cluster, and its environment; and `:erlang.halt/0` in tenant
code would take the platform down with it.

The boundary is the **operating system**: a separate OS process, launched
under `ExSandbox.Hardening`'s confinement wrapper, addressed over distribution
with a per-sandbox cookie. `ExSandbox.Mechanism.Beam.NodeLauncher` holds that
launch; this module is the `ExSandbox.Mechanism` face of it.

## Failures map into `003`'s closed set

Every error returned here is one of `003`'s five `failure_reason` atoms, with
the mechanism's own words carried separately in the detail. That closure is
what lets a host route on cause without knowing which mechanism answered
(`012-FR-008`) — and it is why `probe/1`'s `:unresponsive` becomes `:timeout`
rather than a sixth atom that only this mechanism could produce.

# `call`

```elixir
@spec call(ExSandbox.Sandbox.t(), module(), atom(), [term()], timeout()) ::
  {:ok, term()} | {:error, term()}
```

Evaluates `{module, function, args}` inside a sandbox and returns the result.

⚠️ Routed over `:peer`'s **stdio** control channel, never Erlang distribution.
A sandbox runs under `--unshare-net` and therefore has no network interfaces at
all, so `:erpc.call/5` raises `{:erpc, :noconnection}` against a perfectly
healthy sandbox — measured, not inferred. The failure is doubly misleading: it
is indistinguishable from a crashed node, and it gets *more* likely the better
the confinement works.

Like `provision_failure_reason/1`, this is deliberately **not** an
`ExSandbox.Mechanism` callback: "evaluate this in the sandbox's runtime" is
meaningful for a BEAM node and meaningless for a mechanism whose tenant is a
container running arbitrary code.

Returns `{:error, :unknown_sandbox}` for an id this mechanism never launched
rather than raising, since a caller racing `destroy/1` is an ordinary outcome.

# `cast`

```elixir
@spec cast(ExSandbox.Sandbox.t(), module(), atom(), [term()]) ::
  :ok | {:error, term()}
```

Evaluates `{module, function, args}` in a sandbox without waiting for a result.

For work whose *effect* is the point and whose reply will never arrive —
halting the node being the motivating case. A blocking `call/5` there waits out
its full timeout on a node that is already gone, turning a fast assertion into
a slow one and reporting a timeout for an operation that did exactly what was
asked.

Same stdio routing, and the same reason, as `call/5`.

# `host_pid`

```elixir
@spec host_pid(ExSandbox.Sandbox.t()) :: {:ok, pos_integer()} | {:error, term()}
```

The **host's** OS pid for a running sandbox.

⚠️ Not what the sandbox reports about itself. Under `--unshare-pid` a sandbox's
`:os.getpid()` returns its namespace-local pid — `2` — while the host knows it
by an unrelated number. Anything that reads `/proc/<pid>` on the host and takes
the sandbox's own answer is inspecting a different process entirely, and will
happily report on one that is not confined.

Exposed so that verification does not have to ask the thing being verified: a
compromised sandbox cannot misreport this.

# `provision_failure_reason`

```elixir
@spec provision_failure_reason(ExSandbox.Sandbox.t()) :: {:error, atom()} | :ok
```

Why a sandbox that is no longer running stopped (`FR-009`).

Deliberately **not** an `ExSandbox.Mechanism` callback. `012`'s behaviour is a
frozen contract shared by every mechanism, and widening it for one of them
would oblige every future mechanism to answer a question only this one can.
A host that wants this asks the Beam mechanism by name.

The distinction it carries is the one an operator acts on: `:resource_cap`
means the tenant hit its own limit, `:mechanism_error` means the platform
broke. Reporting a cap breach as the latter sends someone debugging the
platform for a tenant's memory leak.

---

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