a3s-box-sdk 2.5.1

Rust SDK for a3s-box. Includes a programmable CI/CD pipeline API (pipelines as code, one MicroVM kernel per step).
Documentation
# a3s-box-sdk

The Rust SDK for **a3s-box**. Today it provides a **programmable CI/CD pipeline** API
(`a3s_box_sdk::pipeline`) — a pipeline is a Rust program, not a YAML file, and each step
runs in its own MicroVM (one Linux kernel per step, so an untrusted step can't escape to
the host or a sibling step). More capabilities will be added over time; the crate is
intentionally not limited to CI.

Dependency-free: a thin wrapper over the `a3s-box` CLI (which owns the box lifecycle and
state). Set `A3S_BOX` if `a3s-box` is not on `PATH`.

## Pipelines

Warm a base box **once** (clone + install deps), snapshot it, fork per step:

```rust
use a3s_box_sdk::pipeline::{warm_base, WarmBase, FileCache, Step};

fn main() -> Result<(), a3s_box_sdk::pipeline::PipelineError> {
    let cache = FileCache::new(".ci-cache")?;          // skip a step when its inputs are unchanged
    let mut base = warm_base(
        WarmBase::new("node:20", "git clone $REPO /w && cd /w && npm ci")   // runs ONCE
            .env("REPO", "https://github.com/me/app")
            .cache(&cache),
    )?;
    base.step(Step::new("lint", "cd /w && npm run lint"))?;
    base.step(Step::new("test", "cd /w && npm test"))?;   // nonzero exit -> Err (fail-fast)
    base.step(Step::new("build", "cd /w && npm run build"))?;
    base.dispose();                                       // drops the snapshot
    Ok(())
}
```

`Step::allow_failure()` keeps the pipeline going on a non-zero exit; `Step::input(..)`
adds extra cache-key parts. Parallel steps = spawn threads (each `step` is blocking).

## Why forking is cheap

a3s-box's `snapshot restore` is **copy-on-write**: each fork mounts the snapshot's
pristine rootfs as a read-only overlay lower with its own upper — near-instant,
a few MB per fork, and isolated. So snapshot-per-step fan-out costs almost nothing.

## What it hides

CLI footguns verified on a real KVM host, so you don't hit them: `run`/`exec` need
`--` before the command; `snapshot restore` yields a *created* box (started before
exec); `snapshot rm` keys on snapshot ID, not name; `rm -f` of a missing box is a
no-op here (idempotent reruns).

## Run the example

```bash
cargo test -p a3s-box-sdk                                            # offline unit tests
A3S_BOX=/path/to/a3s-box cargo run -p a3s-box-sdk --example pipeline  # live, needs /dev/kvm
```