# The environment manifest A receipt says what a unit cost; an attestation says who ran it, where, at what tier. Neither says **in what environment** — which base image, which tool features, which variables and mounts. For short runs that gap is tolerable. For durable runs (§13) it is not: a run suspended on one host and resumed on another is only the *same run* if it resumes into the *same toolchain*, and "same" must be checkable by hash, not asserted by trust. The **environment manifest** closes the gap: one canonical object whose BLAKE3 content address stands for the whole declared environment. ## Ingestion: the devcontainer format Environments are declared in the open Development Containers specification (`devcontainer.json`) — already the common declaration format across the industry. This standard ingests its **declarative subset**: | accepted | meaning | |---|---| | `image` | base image reference; a `@sha256:` digest marks the manifest **pinned** | | `features` | tool features, normalized to (id, JCS-canonical options), sorted | | `containerEnv` | environment variables, sorted | | `mounts` | string or object form, normalized to (source, target, kind), sorted by target | | `remoteUser` | the user the unit runs as | Cosmetic and editor-facing fields (`name`, `customizations`, …) are accepted and excluded from identity. Networking fields (`forwardPorts`, `appPort`, …) are likewise excluded: the network surface is governed by the capability grant (§5), never by the environment declaration. **Refused fields.** Anything that executes commands or widens the sandbox is refused with an error, not skipped: `build`, `dockerFile`, `dockerComposeFile`, the five lifecycle `*Command` hooks, `runArgs`, `privileged`, `capAdd`, `securityOpt`. Lifecycle automation belongs to the unit and its grants, where it is metered and attested — not smuggled into an identity object. **Unknown fields are also refused**: silently dropping a field a future devcontainer revision makes execution-relevant would mint a false identity. ## Canonical form, address, signature The manifest core (`schema: "sandbox.env-manifest/1"`) is JCS-canonicalized; its BLAKE3 address is the environment's identity; it is signed Ed25519 over the canonical bytes, mirroring the attestation convention (§7.3). Key order and whitespace in the source document do not affect the address. A manifest without an image digest is **unpinned**: its address identifies a declaration, not bytes. Hosts SHOULD treat unpinned manifests the way the energy receipt treats estimates — usable, but labelled, and never the basis for a "same environment" claim across hosts. ## Use in durable runs `SnapshotRef.manifest` (§13) records the manifest address at suspension. Resumption MUST be refused when the offered environment's manifest address differs — same filesystem plus a drifted toolchain is a different snapshot. Conformance class **C7** exercises ingestion determinism, the refusals, and this linkage. The reference implementation is `sandbox_attest::manifest`.