# `Mutare.Sandbox.Command.Exit`
[🔗](https://github.com/foxbenjaminfox/mutare/blob/v0.2.1/lib/mutare/sandbox/command/exit.ex#L1)

The exit codes a sandbox `mix` run can end with, and what each one means.

A leaf on purpose: both halves of the exit-code contract read from here. The
*producing* half is `Mutare.Sandbox.Command.Invocation` — its watcher ASTs embed
`timeout/0` and `owner_lost/0` in the code the sandbox halts itself with — and the
*decoding* half is `Mutare.Sandbox.Command` (`outcome/2`), which refines
`decode/1` with the run's output. Keeping the codes below both means neither
module needs the other to name a code.

## The contract

  * `0` (`success?/1`) — every test passed: the mutation survived.
  * `failure/0` — a test failed: the mutation was killed. A `mix test` exits with
    whatever `--exit-status` it was given *only* on the `failures > 0` path;
    every other failure (a compile error, a missing dependency, a broken
    `test_helper`) exits with `1` (or a signal code). So forcing a distinctive
    `--exit-status` is what separates a clean test failure from a harness error
    — they are no longer both "non-zero".
  * `timeout/0` (`timed_out?/1`) — an injected watcher self-halted a run that
    overran its cap: a `:timeout` for a mutant (counted as a kill); for the one
    compile or the coverage probe, the overrun their callers name specially.
  * `owner_lost/0` — the injected owner-death watcher self-halted a run whose
    spawning Mutare process died (`Mutare.Sandbox.Command.Invocation.owner_watch_ast/0`).
    Never *decoded*: by construction it is only ever exited with after the process
    that would read it is gone — it exists so the halt has a documented,
    recognisable code rather than an arbitrary one.
  * `sigkill/0` (`137` = `128 + 9`) — the OS killed the run with SIGKILL. A
    `:sigkilled`: a harness error by verdict (the suite never reached one), but
    recognised by code because its dominant real-world cause — the kernel OOM
    killer reaping a mutant whose mutation made it allocate unboundedly — must
    **not** be retried back-to-back the way a transient harness error is (see
    `Mutare.Runner`).
  * anything else — the suite never returned a verdict: a `:harness_error`,
    excluded from the score.

`decode/1` is the single, total reading of the code alone. The two predicates exist
for the sandbox runs that are *not* mutant runs — the one compile, the baseline, the
coverage probe, the app-graph query — whose callers need only "did it succeed?" and
"was it the cap?", never the mutant verdict vocabulary; they read the same constants,
so the two readings cannot drift.

# `decoded`

```elixir
@type decoded() :: :passed | :failed | :timeout | :sigkilled | :harness_error
```

Run outcomes classified by exit code alone (see `decode/1`).

# `decode`

```elixir
@spec decode(non_neg_integer()) :: decoded()
```

Decode a mutant-run exit status by its code alone — the single, total reading of
the contract in the moduledoc. `Mutare.Sandbox.Command.outcome/2` refines the
`:harness_error` case with the run's output.

# `failure`

```elixir
@spec failure() :: non_neg_integer()
```

Exit code a clean ExUnit test failure is forced to (via `mix test
--exit-status`), so a killed mutant is distinguishable from a harness error.

Chosen distinct from the codes a *harness* failure produces — `1` (a compile
error, a missing dep, a broken helper, a no-tests-matched), `2` (ExUnit's
default, were the flag ever dropped), `timeout/0`, and the `128 + signal`
range — so only a genuine test failure ever lands on it.

# `owner_lost`

```elixir
@spec owner_lost() :: non_neg_integer()
```

Exit code the owner-death watcher uses when a sandbox run halts itself because
the Mutare process that spawned it died (its stdin pipe hit EOF).

Nobody is left to decode it — the owner is gone — so unlike `timeout/0` it has
no `decode/1` branch; it is reserved here so the halt is documented and
distinguishable in e.g. a wrapper script's logs. Chosen outside the codes that
carry meaning elsewhere in the contract: `0`, `1`/`2` (mix/ExUnit failures),
`failure/0`, `timeout/0`, and the `128 + signal` range.

# `sigkill`

```elixir
@spec sigkill() :: non_neg_integer()
```

Exit code of a run the OS killed with SIGKILL (`137` = `128 + 9`).

Unlike the other codes, nothing of Mutare's *produces* it — it is the kernel's,
and its signature real-world producer is the OOM killer reaping a mutant whose
mutation made it allocate without bound. Decoded to `:sigkilled` so the runner
can skip retries (repeating a deterministic allocation failure would exhaust memory again) and
point at the mitigation (`:max_heap_mb`).

# `success?`

```elixir
@spec success?(non_neg_integer()) :: boolean()
```

Whether `status` is the clean-success exit code (`0`).

# `timed_out?`

```elixir
@spec timed_out?(non_neg_integer()) :: boolean()
```

Whether `status` is the code a self-halt watcher exits with past its cap.

# `timeout`

```elixir
@spec timeout() :: non_neg_integer()
```

Exit code the self-halt watchers use, signalling a run that overran its cap.

---

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