# jouleclaw-sandbox-tier **Scope**: In-process closure isolation for cascade `Tier` wrappers. `TierIsolation::run_with(grants, label, body)` runs a Rust closure under capability grants + emits a sandbox attestation alongside the cascade `Answer`. ## Relation to `jouleclaw-sandbox-protocol` These two crates coexist because they operate at different layers: | Crate | Layer | Surface | |---|---|---| | **`jouleclaw-sandbox-protocol`** | Subprocess lifecycle | async `SandboxRuntime`: `create / start / exec(argv, stdin) / kill / destroy`. Implementations: Docker, K8s, gVisor, Firecracker, OpenSandbox. | | **`jouleclaw-sandbox-tier`** (this crate) | In-process closure wrap | sync `TierIsolation`: `run_with T>(grants, label, body)`. Implementations: `PassthroughRuntime` reference. | The wave-56 trait was originally named `SandboxRuntime` — renamed to `TierIsolation` to avoid the conceptual collision with the protocol crate's trait of the same name. ## When to use which - **`jouleclaw-sandbox-protocol`** — you have a binary or argv-shaped workload that must run in a real container / microVM. Backends marshall the args across the process boundary. - **`jouleclaw-sandbox-tier`** (this crate) — you have a cascade `Tier` impl that runs in-process Rust code; you want to record what device-class grants it ran under + emit a typed [`SandboxAttestation`] for the receipt. Neither replaces the other. A production stack typically uses both: the cascade walks tiers (some `TierIsolation`-wrapped), and tools the agent invokes that need subprocess execution go through `jouleclaw-sandbox-protocol`. ## What ships - `TierIsolation` trait + `Sandboxed` wrapper - `PassthroughRuntime` reference impl - `SandboxGrants` { device_classes, joule_budget_uj, timeout } - `SandboxAttestation` { receipt_id, tier, used_devices, joules_uj, outcome, content_hash } - All tests pass.