What Substrate promises, and where
Substrate publishes several contracts. Each one has its own consumers, its own source of truth and its own check. This page lists them, shows how they relate, and defines the confinement contract's families and facts.
The contracts at a glance
| Contract | Used by | Defined in | Checked by |
|---|---|---|---|
| Wire contract: HTTP and WebSocket | the Rust SDK, the command line, the MCP adapter, other callers of the daemon | immutable versioned bundles in contracts/substrate-wire/, newest 0.23.0, published as the signed OCI artifact ghcr.io/beyond10x/b10x-substrate-wire | the full gate; every response names its bundle and digest in headers |
| Confinement contract | embedders of the b10x-substrate-host crate and b10x-substrate confine run | the ESS domain substrate.confinement in spec/confinement/ | cargo xtask check-ess against the interpreted reference, and each adapter's lane against the adapter |
| Confinement consumer surface | Rust code that calls substrate_host::confinement | tests/confinement-public-items.txt in the host crate | tests/confinement_public_items.rs and tests/consumer_surface.rs |
| Command line | people and scripts in a shell | the ESS domain substrate.client and its ess-cli/2 binding in spec/client/ | cargo xtask check-ess against the generated package in spec/generated/cli |
| Daemon models | contributors to the daemon and store | the ESS domains substrate.operations, substrate.workspaces, substrate.host_processes and substrate.sessions in spec/ | cargo xtask check-ess, and the store's own conformance run for the operation ledger |
More columns: swipe horizontally, or focus the table and use the arrow keys.
How they relate
- The wire contract is the daemon's public surface. A caller reaches the daemon over its socket; the daemon admits the request, records an operation and dispatches it to the host driver. The wire contract reference lists its routes.
- The confinement contract is in process. An embedder opens an adapter with
confinement::openand admits a profile against what the adapter proved. It is the host crate's API, not the wire contract: no wire bundle changes with it. - The
linuxadapter wraps the host driver the daemon dispatches to. Its families are read from the same capability facts (exec.*,workspace.*) the daemon publishes on the wire, as its driver's probe proved them when the adapter opened. b10x-substrate confine runis the command line over the confinement contract, without a daemon: it builds one profile from its flags and lets the adapter admit or refuse it. The command line guide lists its flags.- The ESS domains are executable specifications:
cargo xtask check-essvalidates each, holds its synthesized suite and its generated Rust types to their regenerated bytes, and runs the suite against ESS's interpreted reference. That checks the model; the adapter lanes and the daemon's tests check the code.
The confinement contract
An adapter declares, when it opens, the families it serves and the facts it proved. A
profile names the families and facts it needs, explicitly or through its fields. Admission refuses
the first family or fact the adapter lacks, by name, before anything runs:
confinement.family-unserved or confinement.fact-unserved, both status 501. The
adapters page shows which adapter declares what.
The ten families
A refusal names the first missing family in this order.
| Family | What needs it |
|---|---|
process_run | every start: a start is refused on an adapter that does not serve it |
process_watch | a profile that lists it in families; no field implies it |
output_read | a profile that lists it in families; no field implies it |
wait | a profile that lists it in families; no field implies it |
workspace | the profile's workspace and workdir |
filesystem_view | writable_roots, read_only_roots and read_only_files |
network_egress | network: Egress, and network: Isolated on an adapter that proves host_network |
network_ingress | no adapter serves it; ingress_routes is reserved |
limits | process_bounds and io_bounds |
audit | an audit sink for admission, refusal and start records |
More columns: swipe horizontally, or focus the table and use the arrow keys.
Every adapter's declared set in the specification holds process_run, process_watch,
output_read, wait, workspace and
audit; the specification's model-* scenario refuses each by name on an adapter that lacks it.
The thirteen facts
A refusal names the first missing fact in this order.
| Fact | Meaning |
|---|---|
declared_insecure | the run is unconfined and every observation says so |
paths_are_host_paths | a path the program sees is the host path, with no remapped mount |
whole_tree_kill | a kill reaches every process of the tree |
local_permitted | the host's policy permits local; without it local is refused |
ipc_socket_allowlist | socket(2) families are an allowlist |
ipc_signals_contained | no signal reaches a process outside the tree |
sealed_paths | paths inside a writable workspace stay unwritable |
exec_subpaths | exec is admitted by declared subpath, not only by literal program |
private_tmpdir | a per-run TMPDIR, invisible to another run and gone after wait |
host_network | the run uses the host's network directly |
image_digest_pinned | the image runs by digest only |
stdin_pipe | the run's standard input is a pipe the embedder writes and closes; without it, /dev/null |
output_live | the run's output reaches the embedder while it runs, in order per stream, from a bounded queue whose stall ends the run; without it, output is read from the record |
More columns: swipe horizontally, or focus the table and use the arrow keys.
Two facts are checked when an adapter opens: local, bare or inside systemd, must prove
declared_insecure, and oci and pod must prove image_digest_pinned. An open without them is
refused confinement.fact-unserved naming the fact.
Admission order
Admission refuses in a fixed order: a reserved field (ingress_routes, rate_bounds), an adapter
that did not open, a missing reason on a declared-insecure adapter, an unpinned image on a
digest-pinning adapter, the host's policy against local, the first missing family, then the first
missing fact. The profile guide gives each code and
status.
Scenarios and lanes
The specification holds 65 authored scenarios. Each is named for its adapter and the lane it runs in, and each adapter's test file runs its lane's scenarios against the real adapter.
| Lane | Scenarios | Runs against |
|---|---|---|
portable | 20 | local, and the open refusals and absences any host can check |
delegated | 20 | linux and systemd on a host with a delegated cgroup v2 root |
macos | 10 | macos on the macos-15 CI job |
oci | 7 | oci against Docker in the gate workflow |
pod | 5 | pod against a pinned kind cluster in the gate workflow |
interpreted | 3 | ESS's interpreted reference only |
More columns: swipe horizontally, or focus the table and use the arrow keys.
The consumer surface
The public items of substrate_host::confinement are frozen: tests/confinement_public_items.rs
fails when an item is added, removed or changes signature without the same change to
tests/confinement-public-items.txt. tests/consumer_surface.rs chooses adapters by their
families and facts only, as a consumer must, and runs a read-only toolchain root, a workspace
writing target/, a working directory beneath it, a build into a declared writable root and a
--version run of the result: on local, which refuses each filesystem view, and on linux,
where every need runs.