1.0.1 — 2026-08-28

boundary.md moved to priv/, so the documented lookup resolves

1.0.0 shipped docs/boundary.md and told consumers to read it with Application.app_dir(:ex_sandbox, "docs/boundary.md"). That call cannot work. Mix links exactly ebin and priv into an application's build directory, so a file shipped under any other top-level directory is present in the tarball and absent from app_dir/2 -- and app_dir/2 is the only path a consumer has at runtime.

Found the first time a consumer actually made the call: File.exists? on the documented path returned false against an installed 1.0.0, while tar tzf on the same release listed the file. A packaging check that stops at "is it in the tarball" cannot see this, because the tarball was never the thing that was wrong.

The file is now priv/boundary.md. Its content is unchanged.

If you read the 1.0.0 path, update the call:

# before -- returns a path that does not exist
Application.app_dir(:ex_sandbox, "docs/boundary.md")

# after
Application.app_dir(:ex_sandbox, "priv/boundary.md")

Nothing else changed: no module, function, behaviour or configuration key differs from 1.0.0.

1.0.0 — 2026-08-28

Extracted from the Axonn umbrella; first public release

This library was an application inside a larger umbrella project. It is now its own repository and its own Hex package, with its own lockfile, config, CI and isolation harness. The extraction preserved history (git subtree split, 143 commits) rather than re-creating the tree.

What changed for a consumer, as opposed to for the umbrella:

  • storage_root now defaults to /var/lib/ex_sandbox/sandboxes, was /var/lib/axonn/sandboxes. ⚠️ For an existing deployment this is a data migration, not a cosmetic rename — sandbox storage moves. Set config :ex_sandbox, :beam, storage_root: "/var/lib/axonn/sandboxes" to keep the old path.
  • The verdict socket's default prefix is ex-sandbox-, was axonn-.
  • The dependency tree is :telemetry and nothing else, enforced by test/dependency_tree_test.exs, and the Elixir floor is ~> 1.14 rather than the platform's version.
  • The conformance suite's credentials group reports capability_unavailable here rather than passing, because this package has no data store to probe (012-FR-001). A consumer with a database instantiates the same suite with its own probe. The identifiers are explained in docs/requirement-ids.md.

No public function, callback, struct field or telemetry event changed in the move.

ExSandbox.Mechanism gained an optional callback, and the gate changed shape

ExSandbox.Mechanism.constructed_capabilities/0 declares the capabilities a mechanism builds for whatever it runs — the opposite claim from ExSandbox.Mechanism.required_capabilities/0, which says what it needs from the host. ExSandbox's private ensure_capable/2 now subtracts the second list from the first and asks the host probe only about the remainder.

⚠️ This is a change to a refusal, which is why it is a major version. The callback is optional and a mechanism that omits it is gated exactly as before, so nothing in the tree breaks — but a mechanism that declares one is now admitted on a host where it was previously refused, and a gate that admits more than it used to is a behavioural change to the thing this library exists to do. It is stated here rather than in the additive column.

The claim is not verified by the behaviour. ExSandbox.Conformance is what establishes it, by observing a breach being stopped; until a mechanism is run through the suite, what backs its list is whatever tests accompany it.

ExSandbox.Mechanism.Docker

A mechanism backed by a container runtime, for hosts whose own kernel cannot construct the confinement ExSandbox.Mechanism.Beam requires — every macOS host, where all five gating capabilities report unavailable and Beam is therefore refused before it is reached.

It declares :resource_limits, :filesystem_confinement and :network_restriction as constructed, each backed by an observed breach in test/mechanism/. It deliberately claims neither :disk_quota — MEASURED accepted-and-ignored on overlayfs — nor :privilege_separation; both omissions are stated reductions and are documented on the module.

ExSandbox.Sandbox gained workspace_path

An absolute host directory the sandbox's contents live in, supplied by the host and made reachable from inside by whatever means the mechanism has. Additive: nil means "no workspace", which a mechanism must read as mount nothing rather than as mount somewhere sensible.