# `SmolBox`
[🔗](https://github.com/hfiguera/smolbox/blob/v0.1.3/lib/smolbox.ex#L1)

Standalone managed sandbox executions over pinned, host-operated SmolVM workers.

Start a named child with `child_spec/1`. Submit a validated immutable execution
specification under a host-authorized scope. Acceptance is durable only with a
conforming durable store. An execution handle is `{scope, execution_id}`; it is
independent of the submitting process and is not an authorization token.

`await/3` observes stored evidence and has its own timeout. It never cancels the
guest. `cancel/3` persists intent; fetch the record to distinguish an observed
exit, unknown outcome, confirmed termination, and eventual cleanup. No API
automatically repeats an uncertain command.

Begin with the [Getting started](getting-started.html) walkthrough. Configure
the runtime with `child_spec/1`, describe work with `SmolBox.ExecutionSpec.new/1`,
then call `submit/2`. Use `SmolBox.Client` when your host already manages the
machine lifecycle and only needs individual worker operations.

# `handle`

```elixir
@type handle() :: SmolBox.Execution.key()
```

# `runtime`

```elixir
@type runtime() :: Supervisor.supervisor()
```

# `audit_worker`

```elixir
@spec audit_worker(runtime(), String.t(), keyword()) ::
  {:ok, map()} | {:error, SmolBox.Error.t()}
```

Read a bounded page of namespace candidates against stored machine assignments.

This is an operator API across scopes; hosts must authorize access. It sends
only a worker list request and store reads. Neither names nor findings authorize
adoption, stopping, or deletion. `:owned` still relies on weak upstream creation
evidence and exclusive namespace control. `:untracked`, `:unverified`,
`:conflict`, and `:cleanup_conflict` require investigation; `:unavailable` is not absence.
A cleanup conflict can be a concurrent deletion or a possible reappearance;
these separate worker/store reads cannot establish their chronological order.

Options are `:limit` (1..100, default 20) and `:cursor` (the previous page's
`next_cursor`). Pages are fresh observations, not a stable historical snapshot;
periodic scans should restart at nil. The worker list has a one-second budget;
store lookups have 500 ms each in groups of at most four. No stdout, code,
command environment, or artifact content is returned.

# `await`

```elixir
@spec await(runtime(), handle(), non_neg_integer()) :: SmolBox.Store.result()
```

Wait up to `timeout` milliseconds for a terminal or unknown stored outcome.

`timeout` is 0–900,000 ms. Returns `{:ok, %SmolBox.Execution{}}` for a terminal
state or `:unknown`, or `{:error, %SmolBox.Error{category: :expired}}` when this
caller's wait expires. Store and validation errors are returned normally.

A returned record can have a nonzero command exit, collection failure, or no
known exit. Cleanup and capacity release may still be pending. This function
never cancels the command; it is safe to await the same handle again. Use
`cancel/3` for cancellation and `fetch/3` to inspect continuing cleanup.

# `cancel`

```elixir
@spec cancel(runtime(), String.t(), String.t()) ::
  {:ok, handle()} | {:error, SmolBox.Error.t()}
```

Persist cancellation intent and return the existing execution handle.

Repeated calls preserve the first cancellation timestamp. An acknowledgment is
not proof the VM has stopped. After dispatch, missing exit evidence can remain
unknown even after confirmed termination. A concurrently observed exit is
preserved. Use `fetch/3` to follow termination, collection, and cleanup.

# `child_spec`

```elixir
@spec child_spec(keyword()) :: Supervisor.child_spec()
```

Build a named runtime child for your application's supervision tree.

Start the store before this child. The artifact adapter is `{module, context}`;
its context need not be a process. See the complete setup in
[Getting started](getting-started.html) and [Host integration](host-integration.html).

Required options:

| Option | Meaning |
|---|---|
| `:name` | An atom used to register this runtime and address the managed API |
| `:namespace` | Exclusive machine-name prefix: 1–10 lowercase letters/digits, starting with a letter |
| `:store` | `{adapter_module, context}` implementing `SmolBox.Store` |
| `:artifact_store` | `{adapter_module, context}` implementing `SmolBox.ArtifactStore` |
| `:fingerprint_key` | Stable host secret of 32–4096 bytes; keep it across durable restarts |

Optional options:

| Option | Default | Meaning |
|---|---|---|
| `:workers` | `[]` | Up to 64 `SmolBox.Runtime.WorkerConfig` values with unique worker IDs/endpoints; an empty list permits inspection but cannot admit new work |
| `:mode` | `:durable` | `:ephemeral` explicitly permits a non-durable store such as `SmolBox.Store.Memory` |
| `:max_pending` | `128` | Pending-queue bound, 1–10,000 |
| `:max_active` | `4` | Concurrent runtime work tasks, 1–64; worker reservations separately bound admitted guests |
| `:poll_ms` | `250` | Scan interval, 10–5000 ms |
| `:lease_ms` | `30_000` | Ownership lease, 1000–900,000 ms and at least four times `:poll_ms` |
| `:cleanup_attempts` | `5` | Automatic cleanup-attempt budget, 1–20 |
| `:telemetry_max_pending` | `128` | Notification bound, 1–1024 |
| `:telemetry_timeout_ms` | `100` | Handler delivery budget, 1–1000 ms |
| `:clock` | `SmolBox.Runtime.Clock` | Host clock module matching `SmolBox.Runtime.Clock.now/0` and `SmolBox.Runtime.Clock.monotonic/0`; primarily useful for tests |

Unknown options are rejected during startup. Stopping the runtime stops
observers; it does not establish guest termination. Use durable storage when
execution ownership must survive a controller restart.

# `drain_worker`

```elixir
@spec drain_worker(runtime(), String.t()) :: :ok | {:error, SmolBox.Error.t()}
```

Exclude a worker from new admission-task launches; already active work may continue.

# `fetch`

```elixir
@spec fetch(runtime(), String.t(), String.t()) :: SmolBox.Store.result()
```

Read `{:ok, %SmolBox.Execution{}}` for an existing scoped identity.

Returns a typed `:not_found` error only when the store reports absence.
This is a read, with no worker command or implicit cancellation. The full record
includes its specification and output; authorize access and avoid logging it.
Inspect `state`, `result`, `collection`, `cleanup`, and `reservation` separately.

# `reconcile`

```elixir
@spec reconcile(runtime(), String.t(), String.t()) ::
  :ok | {:error, SmolBox.Error.t()}
```

Schedule existing evidence for observation; this never authorizes command replay.

# `submit`

```elixir
@spec submit(runtime(), SmolBox.ExecutionSpec.t()) ::
  {:ok, handle()} | {:error, SmolBox.Error.t()}
```

Accept an immutable execution and return `{:ok, {scope, id}}`.

Acceptance records intent; it does not mean the guest has started or the command
has succeeded. The same scoped ID and semantic specification return the original
handle. A changed specification under that ID returns `:identity_conflict`.
At least one configured worker must support a new specification's exact profile
and approved artifact; otherwise acceptance returns `:unsupported_capability`.

Preserve the spec and identity after an ambiguous store/transport failure.
Inspect or resubmit the same identity to resolve acceptance; a fresh ID can
authorize another execution. Host code must authorize `spec.scope`.

# `telemetry_stats`

```elixir
@spec telemetry_stats(runtime()) :: {:ok, map()} | {:error, SmolBox.Error.t()}
```

Read ephemeral notification counters; an epoch change resets them.

# `workers`

```elixir
@spec workers(runtime()) :: {:ok, [map()]} | {:error, SmolBox.Error.t()}
```

Read configured workers and their latest controller health/drain observations.

Results include configured capacity, allocation floor, platform, architecture,
version, qualification and status. Capacity is declared admission capacity,
not a live host free-memory/disk measurement. See [Telemetry](telemetry.html).

---

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