use std::cell::RefCell;
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::OnceLock;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};

mod layer;
mod oci;
mod registry;
pub(crate) mod squashfs;
use layer::*;
use oci::*;
use registry::*;

pub struct BakeRequest {
    pub image: String,
    pub name: Option<String>,
    pub runtime: String,
    pub guest_port: u16,
    pub memory_mib: u32,
    /// Number of vCPUs in the guest. Default 1. Multi-vCPU is
    /// gated on a per-bake basis because snapshot-with-multiple-
    /// vCPUs hit an HVF `ICH_LR_EL2` round-trip bug; see
    /// docs/design/multi-vcpu-snapshot-intermittency-2026-04-27.md.
    /// Listener-mode bakes (today's default for non-volume runs)
    /// snapshot only after secondaries are quiescent in WFI, so
    /// that should be safe — but the matrix is wide. Treat
    /// `vcpus > 1` as opt-in for HTTP-throughput-sensitive
    /// workloads.
    pub vcpus: u32,
    pub pull_policy: String,
    pub snapshots_dir: PathBuf,
    pub cmd_override: Option<String>,
    pub extra_args: Vec<String>,
    /// Target platform for the image pull, in `docker pull --platform`
    /// format (e.g. `"linux/arm64"`, `"linux/amd64"`). Drives:
    ///   - which manifest is picked from a multi-arch index
    ///   - the `--platform` arg on Docker pull/save
    ///   - whether init-oci should auto-bind the Rosetta runtime
    ///     share (amd64 only)
    /// Default `"linux/arm64"` — the supermachine kernel + init
    /// pipeline is arm64-native and the user is on Apple Silicon.
    /// `"linux/amd64"` enables Rosetta-in-VM via the M1 plumbing.
    pub platform: String,
}

/// Pipelined-bake hook: the worker is kept alive after init so the
/// caller can run a warmup workload between the base and warm
/// snapshot captures. The caller's `callback` is invoked while the
/// guest is live and the base snapshot's disk-write runs in
/// background — this is what cuts the with-warmup bake time from
/// ~2.4 s to ~1.5 s on rust:1-slim.
///
/// The callback receives the worker's vsock paths so it can talk
/// to the in-guest agent (exec, write_file, read_file, etc.).
/// On return, the bake function captures a *warm* snapshot
/// reflecting whatever state the warmup left behind, then writes
/// metadata.json into both `base_dir` (the snapshot named by
/// `request.name`) and `warm_dir`.
pub struct PipelinedWarmup<'a> {
    /// Output directory for the warm snapshot. Sibling of the base
    /// snapshot's directory, typically `<base>__warm__<tag>`.
    pub warm_dir: PathBuf,
    /// Stable identifier used in the warm dir's metadata.json so a
    /// downstream `Image::from_snapshot` can tell base apart from
    /// warm.
    pub warm_tag: String,
    /// User callback. Runs against the live (post-init) guest.
    /// Errors abort the warm capture and bubble out as a
    /// pipelined-bake failure (the base snapshot may still land on
    /// disk if its async save was already in flight, but no warm
    /// metadata is written).
    pub callback: Box<dyn FnOnce(&PipelinedWarmupContext) -> Result<(), String> + Send + 'a>,
    /// When `true`, the bake driver returns the live worker process
    /// to the caller instead of sending `QUIT`. The caller (typically
    /// `OciImageBuilder::build()`) stashes it on the returned
    /// `Image` so the first `Pool::acquire()` can claim it as a
    /// warm idle worker — saving the ~50 ms spawn + ~5 ms restore
    /// of a fresh worker for the first cycle. Subsequent acquires
    /// fall through to the normal spawn-and-restore-from-disk
    /// path. Default `false` — caller must explicitly opt in.
    ///
    /// Race-safety contract: when `keep_alive=true` AND
    /// `skip_warm_snapshot=false`, the warm SNAPSHOT (sync) is
    /// what guarantees the in-flight base save is drained — the
    /// warm save's diff-via-clone path joins the base save thread
    /// before writing. So at the moment the driver returns the
    /// BakedWorker, both `snap_base` and `snap_warm` are fully on
    /// disk and a `Pool::spawn_one` for a SECOND acquire (which
    /// restores from disk) will see a complete file. No additional
    /// drain step needed.
    ///
    /// When `skip_warm_snapshot=true`, the base save MAY still be
    /// in flight when the BakedWorker is returned. The first
    /// acquire claims the warm worker (which has in-memory state),
    /// so it doesn't care about disk. Subsequent acquires that hit
    /// `Pool::spawn_one` (restore-from-disk) MUST poll for
    /// `snapshot_path.is_file()` before invoking the worker —
    /// `save_compact_to_file` writes to `<path>.partial` then
    /// atomic-renames, so file existence ↔ save complete.
    pub keep_alive: bool,
    /// When `true`, skip the warm SNAPSHOT phase entirely: after
    /// the warmup callback returns (typically a no-op for plain
    /// `.build()` users), hand the live worker back via
    /// `keep_alive_out` without capturing a separate warm
    /// snapshot. The base SNAPSHOT_ASYNC's bg save may still be
    /// in flight; the worker's in-memory state is the warm
    /// handoff.
    ///
    /// This is the "always-pipelined for plain `.build()`" path:
    /// gives the first `Pool::acquire()` a sub-100 ms cycle even
    /// when the user didn't supply a warmup, without paying the
    /// ~400 ms warm-SNAPSHOT round-trip that an empty-warmup
    /// pipelined bake would.
    ///
    /// Only meaningful when `keep_alive=true` (otherwise the
    /// caller asked for a snapshot on disk and we have to write
    /// the warm one, since base alone is intermediate scaffolding
    /// in the non-skip path's contract). When `skip_warm_snapshot
    /// && !keep_alive`, we still skip the warm capture — the base
    /// snapshot is the user's snapshot — and the QUIT path waits
    /// for the bg save to drain before returning.
    pub skip_warm_snapshot: bool,
    /// When `true` AND `skip_warm_snapshot=true`, pass
    /// `--snapshot-on-pre-exec` to the worker so init-oci's
    /// "workload-pre-exec" marker triggers BAKE_READY before the
    /// workload runs. Saves 50-150 ms on slow-listener bakes
    /// (python heavy imports, JVM, rust toolchain) and ~10× on
    /// workloads that never bind a listener (which would otherwise
    /// hit the `--snapshot-after-ms` 7 s wall-clock fallback).
    ///
    /// Trade-off: the captured snapshot has init-oci IN nanosleep,
    /// not post-fork. On restore, init-oci wakes immediately
    /// (CLOCK_REALTIME has advanced past the deadline), forks, and
    /// execs the workload. So workload startup happens
    /// per-restore, not at bake time. This is what most agent-
    /// runtime users want (fresh state each cycle); but for
    /// service images (nginx) where the user wants the listener
    /// pre-bound at restore time, set this to `false` and let the
    /// existing on_listener trigger fire.
    ///
    /// Ignored when `skip_warm_snapshot=false` (the with_warmup
    /// pipeline always uses listener-ready, or the warmup callback
    /// would run against a not-ready guest).
    pub use_pre_exec_trigger: bool,
}

/// Live worker handle returned by the pipelined-bake driver when
/// `PipelinedWarmup.keep_alive == true`. The `Debug` impl below is
/// hand-rolled because `std::process::Child` and `UnixStream` don't
/// derive `Debug` cleanly across the platforms this crate
/// supports. Encapsulates everything
/// `Worker` needs in api.rs so the first `Pool::acquire()` can
/// reuse the bake-time worker as an idle pool entry instead of
/// spawning a fresh one.
///
/// Drop semantics: if not claimed by a pool, the bake driver sends
/// `QUIT` on the supervisor channel and `child.wait()`s — saves
/// any in-flight bg work cleanly; no `.partial` leaks. Caller
/// handling lives in api.rs because dropping straight from bake.rs
/// would lose the QUIT-then-wait ordering on panic-paths the
/// caller may want different semantics for (e.g., immediate kill
/// on Image drop vs. graceful drain on transient errors).
pub struct BakedWorker {
    /// Underlying worker subprocess. Owns the lifecycle.
    pub child: std::process::Child,
    pub vsock_mux_path: PathBuf,
    pub vsock_exec_path: PathBuf,
    pub control_path: PathBuf,
    /// Writer half of the supervisor channel. The lib's `Worker`
    /// type wraps this in a `Mutex<ControlChannel>` so concurrent
    /// RESTORE / SNAPSHOT / QUIT paths don't race.
    pub control_writer: std::os::unix::net::UnixStream,
    /// Reader half. Returned separately so callers can construct
    /// their own `BufReader` without us pinning the buffer
    /// implementation here.
    pub control_reader: std::os::unix::net::UnixStream,
    /// Per-bake socks dir; caller is responsible for
    /// `remove_dir_all` after the worker exits. We don't clean it
    /// up because the dir contains live socket nodes the worker
    /// may still be serving.
    pub socks_dir: PathBuf,
    /// Path of the warm snapshot (the user-facing artifact). Used
    /// as the diff-via-clone `base=` hint on subsequent cycle
    /// SNAPSHOT RPCs.
    pub last_restore_path: PathBuf,
}

impl std::fmt::Debug for BakedWorker {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("BakedWorker")
            .field("child_pid", &self.child.id())
            .field("vsock_mux_path", &self.vsock_mux_path)
            .field("vsock_exec_path", &self.vsock_exec_path)
            .field("control_path", &self.control_path)
            .field("socks_dir", &self.socks_dir)
            .field("last_restore_path", &self.last_restore_path)
            .finish()
    }
}

impl BakedWorker {
    /// Clean shutdown — sends QUIT on the supervisor channel,
    /// waits for the child, removes the socks dir. Used by the
    /// `WarmStash::drop` path (when no Pool ever claimed this
    /// worker) and only there: when a Pool DOES claim, the
    /// `BakedWorker` is destructured field-by-field into a
    /// `Worker`, which has its own existing shutdown path.
    ///
    /// Best-effort throughout. Bounded at 5 s on the
    /// child-exit wait so a hung worker degrades to SIGKILL
    /// rather than blocking the caller's drop forever.
    pub fn shutdown(mut self) {
        use std::io::Write;
        let _ = writeln!(&mut self.control_writer, "QUIT");
        let _ = self.control_writer.flush();
        let kill_deadline = std::time::Instant::now() + std::time::Duration::from_secs(5);
        loop {
            match self.child.try_wait() {
                Ok(Some(_)) => break,
                Ok(None) if std::time::Instant::now() < kill_deadline => {
                    std::thread::sleep(std::time::Duration::from_millis(20));
                }
                _ => {
                    let _ = self.child.kill();
                    let _ = self.child.wait();
                    break;
                }
            }
        }
        let _ = std::fs::remove_dir_all(&self.socks_dir);
    }
}

/// Arc-tracked owner of the warm-handoff worker. When the last
/// `Image` clone drops (and no `Pool::build()` ever claimed the
/// worker via `WarmStash::take`), this type's `Drop` impl runs
/// `BakedWorker::shutdown` so the worker process is reaped
/// cleanly. Wrapping the `Mutex<Option<BakedWorker>>` here
/// (instead of putting `Drop` directly on `BakedWorker`) is what
/// lets `warm_baked_to_worker` field-destructure the BakedWorker
/// when a Pool DOES claim it — Rust forbids moving out of a type
/// with `Drop` impl.
#[derive(Debug)]
pub struct WarmStash {
    pub inner: std::sync::Mutex<Option<BakedWorker>>,
}

impl WarmStash {
    pub fn new(bw: Option<BakedWorker>) -> Self {
        Self {
            inner: std::sync::Mutex::new(bw),
        }
    }
    /// Atomic claim — returns `Some(bw)` exactly once across all
    /// callers (or until put back). All subsequent calls return
    /// `None`. This is the primitive `PoolBuilder::build` uses to
    /// race-safely take the warm worker.
    pub fn take(&self) -> Option<BakedWorker> {
        self.inner.lock().ok().and_then(|mut g| g.take())
    }
}

impl Drop for WarmStash {
    fn drop(&mut self) {
        if let Ok(mut g) = self.inner.lock() {
            if let Some(bw) = g.take() {
                bw.shutdown();
            }
        }
    }
}

/// Worker-paths-only view passed to the warmup callback. We
/// intentionally do not expose the supervisor control socket — the
/// pipelined-bake driver in `bake.rs` owns that exclusively.
pub struct PipelinedWarmupContext {
    pub vsock_mux_path: PathBuf,
    pub vsock_exec_path: PathBuf,
}

trait ImageSource {
    fn local_arch(&self, image: &str) -> Option<String>;
    fn pull(&self, image: &str, force_refresh: bool) -> Result<(), String>;
    fn inspect(&self, image: &str) -> Result<serde_json::Value, String>;
    fn save(&self, image: &str, work_dir: &Path) -> Result<PathBuf, String>;
    /// Architecture the source was constructed for ("arm64", "amd64").
    /// Drives Docker's `--platform` flag and registry manifest
    /// selection. Set at `select_image_source(image, arch)` time and
    /// fixed for the source's lifetime.
    fn arch(&self) -> &str;
}

struct DockerImageSource {
    arch: String,
}

/// 0.7.44+ process-wide cache for `docker image inspect` results.
///
/// On macOS Docker Desktop, each `docker image inspect` is a CLI
/// round-trip through the Docker daemon VM — ~150-400 ms even
/// when the answer is local. Both `local_arch` and `inspect` fire
/// on every bake (incl. sibling-clone, since `resolve_image` runs
/// before the fast-path check), so caching saves ~300-800 ms on
/// any repeat bake of the same image in a session.
///
/// Key: `(image_ref, arch)`. Two cached fields per key:
///   * `local_arch` (the `{{.Architecture}}`-format inspect result)
///   * `inspect` (the full JSON record)
///
/// Invalidation:
///   1. TTL — default 60 s (env `SUPERMACHINE_DOCKER_INSPECT_CACHE_SECS`).
///      Bounds the staleness window if the user `docker pull`s
///      outside our process.
///   2. Pull-invalidate — `pull()` clears the entry for that key
///      before returning, so a `force_refresh=true` flow gets a
///      fresh inspect on the next call within the same process.
///
/// Disable entirely with `SUPERMACHINE_DOCKER_INSPECT_CACHE_SECS=0`.
#[derive(Default)]
struct DockerInspectCacheEntry {
    local_arch: Option<(Option<String>, Instant)>,
    inspect: Option<(serde_json::Value, Instant)>,
}

fn docker_inspect_cache(
) -> &'static std::sync::Mutex<std::collections::HashMap<(String, String), DockerInspectCacheEntry>>
{
    static CACHE: OnceLock<
        std::sync::Mutex<std::collections::HashMap<(String, String), DockerInspectCacheEntry>>,
    > = OnceLock::new();
    CACHE.get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()))
}

fn docker_inspect_cache_ttl() -> Duration {
    let secs = std::env::var("SUPERMACHINE_DOCKER_INSPECT_CACHE_SECS")
        .ok()
        .and_then(|s| s.parse::<u64>().ok())
        .unwrap_or(60);
    Duration::from_secs(secs)
}

impl ImageSource for DockerImageSource {
    fn arch(&self) -> &str {
        &self.arch
    }

    fn local_arch(&self, image: &str) -> Option<String> {
        let key = (image.to_owned(), self.arch.clone());
        let ttl = docker_inspect_cache_ttl();
        if !ttl.is_zero() {
            if let Ok(g) = docker_inspect_cache().lock() {
                if let Some(entry) = g.get(&key) {
                    if let Some((val, ts)) = &entry.local_arch {
                        if ts.elapsed() < ttl {
                            return val.clone();
                        }
                    }
                }
            }
        }
        let mut cmd = Command::new("docker");
        cmd.arg("image")
            .arg("inspect")
            .arg("--format")
            .arg("{{.Architecture}}")
            .arg(image)
            .stderr(Stdio::null());
        let result = command_output(cmd, "docker image inspect architecture")
            .ok()
            .map(|s| s.trim().to_owned())
            .filter(|s| !s.is_empty() && s != "<no value>");
        if !ttl.is_zero() {
            if let Ok(mut g) = docker_inspect_cache().lock() {
                g.entry(key).or_default().local_arch = Some((result.clone(), Instant::now()));
            }
        }
        result
    }

    fn pull(&self, image: &str, _force_refresh: bool) -> Result<(), String> {
        let mut cmd = Command::new("docker");
        cmd.arg("pull")
            .arg(format!("--platform=linux/{}", self.arch))
            .arg(image)
            .stdout(Stdio::null());
        let r = run_status(cmd, "docker pull");
        // Pull may have replaced the local image (different sha,
        // different config). Invalidate this image's inspect cache
        // entries so the next lookup re-fetches truth from the daemon.
        if let Ok(mut g) = docker_inspect_cache().lock() {
            g.remove(&(image.to_owned(), self.arch.clone()));
        }
        r
    }

    fn inspect(&self, image: &str) -> Result<serde_json::Value, String> {
        let key = (image.to_owned(), self.arch.clone());
        let ttl = docker_inspect_cache_ttl();
        if !ttl.is_zero() {
            if let Ok(g) = docker_inspect_cache().lock() {
                if let Some(entry) = g.get(&key) {
                    if let Some((val, ts)) = &entry.inspect {
                        if ts.elapsed() < ttl {
                            return Ok(val.clone());
                        }
                    }
                }
            }
        }
        let mut inspect_cmd = Command::new("docker");
        inspect_cmd.arg("image").arg("inspect").arg(image);
        let inspect = command_output(inspect_cmd, "docker image inspect")?;
        let inspect_json: serde_json::Value = serde_json::from_str(&inspect)
            .map_err(|e| format!("docker image inspect JSON: {e}"))?;
        let record = inspect_json
            .as_array()
            .and_then(|a| a.first())
            .cloned()
            .ok_or_else(|| format!("docker image inspect returned no records for {image}"))?;
        if !ttl.is_zero() {
            if let Ok(mut g) = docker_inspect_cache().lock() {
                g.entry(key).or_default().inspect = Some((record.clone(), Instant::now()));
            }
        }
        Ok(record)
    }

    fn save(&self, image: &str, work_dir: &Path) -> Result<PathBuf, String> {
        let save_tar = work_dir.join("image.tar");
        let mut save = Command::new("docker");
        save.arg("save")
            .arg(format!("--platform=linux/{}", self.arch))
            .arg(image)
            .arg("-o")
            .arg(&save_tar)
            .stderr(Stdio::null());
        run_status(save, "docker save")?;

        let save_dir = work_dir.join("_save");
        std::fs::create_dir_all(&save_dir)
            .map_err(|e| format!("create save dir {}: {e}", save_dir.display()))?;
        // In-process untar (no `tar` subprocess) — the archive is `docker
        // save` output, locally produced.
        oci::extract_tar_archive(&save_tar, &save_dir)?;
        let _ = std::fs::remove_file(&save_tar);

        Ok(save_dir)
    }
}

/// Apple `container` CLI source — macOS 26+'s native
/// containerization stack. CLI shape matches Docker's modern
/// `image` subcommand layout (`container image pull`,
/// `container image inspect`, `container image save`) so the
/// implementation parallels `DockerImageSource` 1:1.
///
/// Lets integrators on container-only Macs (no Docker Desktop)
/// use `Image.build({ ref: "alpine:latest" })` natively, instead
/// of having to manually run `container image save -o foo.tar` +
/// `Image.build({ ref: "oci-archive:foo.tar" })`.
///
/// Selected via `SUPERMACHINE_IMAGE_SOURCE=container` or
/// auto-detected when `container` is on PATH AND `docker` isn't
/// (see `select_image_source`).
struct ContainerImageSource {
    arch: String,
}

/// Mirror of `docker_inspect_cache` for the `container` CLI.
/// Apple `container` is also a daemon round-trip per inspect
/// (~80-200 ms cold), so the same TTL caching wins apply.
fn container_inspect_cache(
) -> &'static std::sync::Mutex<std::collections::HashMap<(String, String), DockerInspectCacheEntry>>
{
    static CACHE: OnceLock<
        std::sync::Mutex<std::collections::HashMap<(String, String), DockerInspectCacheEntry>>,
    > = OnceLock::new();
    CACHE.get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()))
}

impl ImageSource for ContainerImageSource {
    fn arch(&self) -> &str {
        &self.arch
    }

    fn local_arch(&self, image: &str) -> Option<String> {
        let key = (image.to_owned(), self.arch.clone());
        let ttl = docker_inspect_cache_ttl();
        if !ttl.is_zero() {
            if let Ok(g) = container_inspect_cache().lock() {
                if let Some(entry) = g.get(&key) {
                    if let Some((val, ts)) = &entry.local_arch {
                        if ts.elapsed() < ttl {
                            return val.clone();
                        }
                    }
                }
            }
        }
        let mut cmd = Command::new("container");
        cmd.arg("image")
            .arg("inspect")
            .arg("--format")
            .arg("{{.Architecture}}")
            .arg(image)
            .stderr(Stdio::null());
        let result = command_output(cmd, "container image inspect architecture")
            .ok()
            .map(|s| s.trim().to_owned())
            .filter(|s| !s.is_empty() && s != "<no value>");
        if !ttl.is_zero() {
            if let Ok(mut g) = container_inspect_cache().lock() {
                g.entry(key).or_default().local_arch = Some((result.clone(), Instant::now()));
            }
        }
        result
    }

    fn pull(&self, image: &str, _force_refresh: bool) -> Result<(), String> {
        let mut cmd = Command::new("container");
        cmd.arg("image")
            .arg("pull")
            .arg(format!("--platform=linux/{}", self.arch))
            .arg(image)
            .stdout(Stdio::null());
        let r = run_status(cmd, "container image pull");
        if let Ok(mut g) = container_inspect_cache().lock() {
            g.remove(&(image.to_owned(), self.arch.clone()));
        }
        r
    }

    fn inspect(&self, image: &str) -> Result<serde_json::Value, String> {
        let key = (image.to_owned(), self.arch.clone());
        let ttl = docker_inspect_cache_ttl();
        if !ttl.is_zero() {
            if let Ok(g) = container_inspect_cache().lock() {
                if let Some(entry) = g.get(&key) {
                    if let Some((val, ts)) = &entry.inspect {
                        if ts.elapsed() < ttl {
                            return Ok(val.clone());
                        }
                    }
                }
            }
        }
        let mut inspect_cmd = Command::new("container");
        inspect_cmd.arg("image").arg("inspect").arg(image);
        let inspect = command_output(inspect_cmd, "container image inspect")?;
        let inspect_json: serde_json::Value = serde_json::from_str(&inspect)
            .map_err(|e| format!("container image inspect JSON: {e}"))?;
        let record = inspect_json
            .as_array()
            .and_then(|a| a.first())
            .cloned()
            .ok_or_else(|| format!("container image inspect returned no records for {image}"))?;
        if !ttl.is_zero() {
            if let Ok(mut g) = container_inspect_cache().lock() {
                g.entry(key).or_default().inspect = Some((record.clone(), Instant::now()));
            }
        }
        Ok(record)
    }

    fn save(&self, image: &str, work_dir: &Path) -> Result<PathBuf, String> {
        let save_tar = work_dir.join("image.tar");
        let mut save = Command::new("container");
        save.arg("image")
            .arg("save")
            .arg(format!("--platform=linux/{}", self.arch))
            .arg("-o")
            .arg(&save_tar)
            .arg(image)
            .stderr(Stdio::null());
        run_status(save, "container image save")?;

        let save_dir = work_dir.join("_save");
        std::fs::create_dir_all(&save_dir)
            .map_err(|e| format!("create save dir {}: {e}", save_dir.display()))?;
        // In-process untar (no `tar` subprocess) — the archive is `container
        // image save` output, locally produced.
        oci::extract_tar_archive(&save_tar, &save_dir)?;
        let _ = std::fs::remove_file(&save_tar);

        Ok(save_dir)
    }
}

#[derive(Clone)]
struct RegistryImageRef {
    registry: String,
    repository: String,
    reference: String,
}

/// Username + password resolved from `~/.docker/config.json`'s
/// `auths.<registry>.auth` field (or `--registry-auth USER:PASS`).
/// Sent as HTTP Basic auth on the registry's token endpoint —
/// the token returned then carries the user's pull permissions.
#[derive(Clone)]
struct RegistryCreds {
    user: String,
    pass: String,
}

impl RegistryCreds {
    fn basic_header_value(&self) -> String {
        let mut joined = String::with_capacity(self.user.len() + self.pass.len() + 1);
        joined.push_str(&self.user);
        joined.push(':');
        joined.push_str(&self.pass);
        format!("Basic {}", b64_encode(joined.as_bytes()))
    }
}

/// Look up credentials for `registry` host in `~/.docker/config.json`.
/// Honors `$DOCKER_CONFIG` if set. Tries common key shapes so
/// existing config files (typed via `docker login`) Just Work:
///   - `registry` (e.g. `ghcr.io`)
///   - `https://registry/v1/` (legacy docker hub style)
///   - `https://registry`
fn docker_config_auth(registry: &str) -> Option<RegistryCreds> {
    let config_path = std::env::var_os("DOCKER_CONFIG")
        .map(|d| PathBuf::from(d).join("config.json"))
        .unwrap_or_else(|| home_join(".docker/config.json"));
    let text = std::fs::read_to_string(&config_path).ok()?;
    let json: serde_json::Value = serde_json::from_str(&text).ok()?;
    let auths = json.get("auths")?.as_object()?;
    // Docker Hub special-case: `index.docker.io` is the v2 host;
    // legacy `docker login` still writes its key as
    // `https://index.docker.io/v1/`.
    let docker_hub_aliases = ["docker.io", "index.docker.io", "registry-1.docker.io"];
    let is_docker_hub = docker_hub_aliases.contains(&registry);
    let mut keys: Vec<String> = vec![
        registry.to_string(),
        format!("https://{registry}"),
        format!("https://{registry}/"),
        format!("https://{registry}/v1/"),
        format!("https://{registry}/v2/"),
    ];
    if is_docker_hub {
        for alias in &docker_hub_aliases {
            keys.push(format!("https://{alias}/v1/"));
            keys.push(format!("https://{alias}/v2/"));
            keys.push(format!("https://{alias}"));
            keys.push((*alias).to_string());
        }
    }
    for key in &keys {
        let entry = match auths.get(key) {
            Some(e) => e,
            None => continue,
        };
        let auth_b64 = entry.get("auth").and_then(|v| v.as_str())?;
        let decoded = b64_decode(auth_b64.trim())?;
        let s = String::from_utf8(decoded).ok()?;
        let (user, pass) = s.split_once(':')?;
        return Some(RegistryCreds {
            user: user.to_owned(),
            pass: pass.to_owned(),
        });
    }
    None
}

/// Tiny no-deps base64 decoder. Used only for `auth` strings out
/// of `~/.docker/config.json`, which are short and well-formed.
fn b64_decode(s: &str) -> Option<Vec<u8>> {
    let mut lookup = [255u8; 256];
    let table = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    for (i, &b) in table.iter().enumerate() {
        lookup[b as usize] = i as u8;
    }
    let mut out = Vec::new();
    let mut buf: u32 = 0;
    let mut bits: u32 = 0;
    for &b in s.as_bytes() {
        if b == b'=' {
            break;
        }
        if matches!(b, b'\n' | b'\r' | b' ' | b'\t') {
            continue;
        }
        let v = lookup[b as usize];
        if v == 255 {
            return None;
        }
        buf = (buf << 6) | u32::from(v);
        bits += 6;
        if bits >= 8 {
            bits -= 8;
            out.push((buf >> bits) as u8);
            buf &= (1u32 << bits).wrapping_sub(1);
        }
    }
    Some(out)
}

/// Tiny base64 encoder. Used to format the `Basic` auth header.
fn b64_encode(bytes: &[u8]) -> String {
    let table = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    let mut out = String::with_capacity(((bytes.len() + 2) / 3) * 4);
    let mut i = 0;
    while i + 3 <= bytes.len() {
        let n = ((bytes[i] as u32) << 16) | ((bytes[i + 1] as u32) << 8) | (bytes[i + 2] as u32);
        out.push(table[((n >> 18) & 0x3f) as usize] as char);
        out.push(table[((n >> 12) & 0x3f) as usize] as char);
        out.push(table[((n >> 6) & 0x3f) as usize] as char);
        out.push(table[(n & 0x3f) as usize] as char);
        i += 3;
    }
    let rem = bytes.len() - i;
    if rem == 1 {
        let n = (bytes[i] as u32) << 16;
        out.push(table[((n >> 18) & 0x3f) as usize] as char);
        out.push(table[((n >> 12) & 0x3f) as usize] as char);
        out.push('=');
        out.push('=');
    } else if rem == 2 {
        let n = ((bytes[i] as u32) << 16) | ((bytes[i + 1] as u32) << 8);
        out.push(table[((n >> 18) & 0x3f) as usize] as char);
        out.push(table[((n >> 12) & 0x3f) as usize] as char);
        out.push(table[((n >> 6) & 0x3f) as usize] as char);
        out.push('=');
    }
    out
}

struct RegistryImageSource {
    image: RegistryImageRef,
    cached_layout: RefCell<Option<RegistryLayoutCache>>,
    /// Architecture to pick from a multi-arch manifest. Set at
    /// construction (from `BakePlan::arch()`) and used by
    /// `fetch_registry_to_oci_layout` / `find_registry_manifest_descriptor`.
    arch: String,
}

struct RegistryLayoutCache {
    work_dir: PathBuf,
    layout: PathBuf,
}

impl RegistryImageSource {
    fn cached_layout(&self, force_refresh: bool) -> Result<PathBuf, String> {
        if let Some(cache) = self.cached_layout.borrow().as_ref() {
            return Ok(cache.layout.clone());
        }
        let work_dir = temp_work_dir("supermachine-registry-layout")?;
        let layout = work_dir.join("_oci");
        let result = fetch_registry_to_oci_layout(&self.image, &self.arch, &layout, force_refresh);
        if let Err(err) = result {
            let _ = std::fs::remove_dir_all(work_dir);
            return Err(err);
        }
        *self.cached_layout.borrow_mut() = Some(RegistryLayoutCache {
            work_dir,
            layout: layout.clone(),
        });
        Ok(layout)
    }
}

impl Drop for RegistryImageSource {
    fn drop(&mut self) {
        if let Some(cache) = self.cached_layout.get_mut().take() {
            let _ = std::fs::remove_dir_all(cache.work_dir);
        }
    }
}

impl ImageSource for RegistryImageSource {
    fn arch(&self) -> &str {
        &self.arch
    }

    fn local_arch(&self, _image: &str) -> Option<String> {
        None
    }

    fn pull(&self, _image: &str, force_refresh: bool) -> Result<(), String> {
        self.cached_layout(force_refresh).map(|_| ())
    }

    fn inspect(&self, image: &str) -> Result<serde_json::Value, String> {
        let layout = self.cached_layout(false)?;
        inspect_oci_layout(image, &layout, &self.arch)
    }

    fn save(&self, _image: &str, work_dir: &Path) -> Result<PathBuf, String> {
        if let Some(cache) = self.cached_layout.borrow().as_ref() {
            return Ok(cache.layout.clone());
        }
        let layout = work_dir.join("_oci");
        fetch_registry_to_oci_layout(&self.image, &self.arch, &layout, false)?;
        Ok(layout)
    }
}

struct HttpResponse {
    status: u16,
    headers: String,
    body: Vec<u8>,
}

struct RegistryManifest {
    manifest_digest: String,
    manifest_bytes: Vec<u8>,
    manifest: serde_json::Value,
    config_digest: String,
    config_bytes: Vec<u8>,
    blob_cache_hits: usize,
    blob_downloads: usize,
    ref_cache_hit: bool,
    ref_cache_age_ms: Option<u128>,
    ref_cache_ttl_ms: u128,
}

struct OciLayoutImageSource {
    path: PathBuf,
    arch: String,
}

impl ImageSource for OciLayoutImageSource {
    fn arch(&self) -> &str {
        &self.arch
    }

    fn local_arch(&self, _image: &str) -> Option<String> {
        // An on-disk OCI layout might contain manifests for multiple
        // arches. The honest answer is "we don't know without
        // reading the index" — but every existing caller checks this
        // only to decide whether to skip a pull. Returning None
        // forces the caller into the pull path which we then no-op,
        // so semantics are preserved.
        None
    }

    fn pull(&self, _image: &str, _force_refresh: bool) -> Result<(), String> {
        Ok(())
    }

    fn inspect(&self, image: &str) -> Result<serde_json::Value, String> {
        inspect_oci_layout(image, &self.path, &self.arch)
    }

    fn save(&self, _image: &str, _work_dir: &Path) -> Result<PathBuf, String> {
        Ok(self.path.clone())
    }
}

struct OciArchiveImageSource {
    path: PathBuf,
    arch: String,
}

impl ImageSource for OciArchiveImageSource {
    fn arch(&self) -> &str {
        &self.arch
    }

    fn local_arch(&self, _image: &str) -> Option<String> {
        None
    }

    fn pull(&self, _image: &str, _force_refresh: bool) -> Result<(), String> {
        Ok(())
    }

    fn inspect(&self, image: &str) -> Result<serde_json::Value, String> {
        let work_dir = temp_work_dir("supermachine-oci-archive-inspect")?;
        let arch = self.arch.clone();
        let result = (|| {
            let layout = extract_oci_archive(&self.path, &work_dir)?;
            inspect_oci_layout(image, &layout, &arch)
        })();
        let _ = std::fs::remove_dir_all(work_dir);
        result
    }

    fn save(&self, _image: &str, work_dir: &Path) -> Result<PathBuf, String> {
        extract_oci_archive(&self.path, work_dir)
    }
}

/// Path to the VMM worker binary. Tried in order:
///   - $SUPERMACHINE_WORKER_BIN (explicit override)
///   - sibling of the running binary (`cargo install` layout —
///     `~/.cargo/bin/supermachine-worker` next to
///     `~/.cargo/bin/supermachine`)
///   - dev-tree:        `<root>/target/release/supermachine-worker`
///   - tarball install: `<root>/bin/supermachine-worker`
///
/// Returns the dev-tree path as a last-resort default so error
/// messages from the caller point somewhere actionable.
pub(crate) fn supermachine_worker_bin(root: &Path) -> PathBuf {
    // Delegate to the single canonical locator so this and the
    // `Image::acquire` pool-spawn path can't drift apart. The
    // unified locator handles env-var override, sibling-of-exe,
    // canonicalize-fallback for symlinked installs, $CARGO_HOME,
    // and $PATH walk. Falls through to `root.join(...)` only if
    // none of those work, and emits the dev-tree fallback as a
    // last resort so error messages from the caller still point
    // at a well-known location instead of a bogus default.
    // Resolution mirrors `api.rs::spawn_one`'s three modes:
    //   1. SUPERMACHINE_WORKER_BIN explicit → use as-is.
    //   2. SUPERMACHINE_WORKER_BIN_BUNDLED (npm JS shim) → copy
    //      to user-data dir + sign the copy.
    //   3. Neither → locate_worker_bin walk → copy to user-data
    //      dir + sign the copy.
    //
    // The user-dir copy means node_modules / ~/.cargo/bin is
    // never codesign-rewritten by the autopilot. Critical for
    // recovery from the stuck-`.cstemp`-with-uchg loop that
    // wedged 0.7.27 installs.
    #[cfg(target_os = "macos")]
    {
        if let Some(p) = std::env::var_os("SUPERMACHINE_WORKER_BIN") {
            let p = PathBuf::from(p);
            if p.is_file() {
                let _ = crate::codesign::ensure_worker_signed(&p);
                return p;
            }
        }
        let source = std::env::var_os("SUPERMACHINE_WORKER_BIN_BUNDLED")
            .map(PathBuf::from)
            .filter(|p| p.is_file())
            .or_else(crate::codesign::locate_worker_bin);
        if let Some(p) = source {
            if let Ok(user_dir_copy) = crate::assets::ensure_worker_in_user_dir(&p) {
                let _ = crate::codesign::ensure_worker_signed(&user_dir_copy);
                return user_dir_copy;
            }
            // user-dir copy failed (no $HOME / disk full / perms) —
            // fall back to in-place sign of the located path. The
            // bake will surface any downstream error with the same
            // diagnostic as the pre-user-dir flow.
            let _ = crate::codesign::ensure_worker_signed(&p);
            return p;
        }
    }
    #[cfg(not(target_os = "macos"))]
    if let Some(p) = crate::codesign::locate_worker_bin() {
        return p;
    }
    for candidate in [
        "target/release/supermachine-worker",
        "bin/supermachine-worker",
    ] {
        let p = root.join(candidate);
        if p.is_file() {
            return p;
        }
    }
    root.join("target/release/supermachine-worker")
}

/// Path to the Linux kernel image used by every microVM. Tried in
/// order:
///   - dev-tree:        `<root>/crates/supermachine-kernel/kernel`
///   - tarball install: `<root>/share/supermachine/kernel`
///   - bundled extract: `$XDG_DATA_HOME/supermachine/v{VERSION}/kernel`
///     auto-extracted from the linked-in `supermachine-kernel` crate
///     on first call (the cargo-install path)
///
/// Returns the dev-tree path as a last-resort default so error
/// messages from the caller point somewhere actionable.
pub(crate) fn supermachine_kernel(root: &Path) -> PathBuf {
    for candidate in [
        "crates/supermachine-kernel/kernel",
        "share/supermachine/kernel",
    ] {
        let p = root.join(candidate);
        if p.is_file() {
            return p;
        }
    }
    if let Some(p) = crate::assets::AssetPaths::discover().kernel {
        return p;
    }
    root.join("crates/supermachine-kernel/kernel")
}

fn select_image_source(image: &str, arch: &str) -> Result<Box<dyn ImageSource>, String> {
    if let Some(path) = image.strip_prefix("oci-layout:") {
        let path = PathBuf::from(path);
        if !path.join("index.json").is_file() {
            return Err(format!("OCI layout missing index.json: {}", path.display()));
        }
        return Ok(Box::new(OciLayoutImageSource {
            path,
            arch: arch.to_owned(),
        }));
    }
    if let Some(path) = image.strip_prefix("oci-archive:") {
        let path = PathBuf::from(path);
        if !path.is_file() {
            return Err(format!("OCI archive not found: {}", path.display()));
        }
        return Ok(Box::new(OciArchiveImageSource {
            path,
            arch: arch.to_owned(),
        }));
    }
    // SUPERMACHINE_IMAGE_SOURCE — explicit override.
    //
    // Values:
    //   docker     — call out to the local Docker daemon
    //   container  — call out to Apple's `container` CLI
    //   registry   — pull from the OCI registry directly (= default)
    //   auto       — probe docker → container → registry; first
    //                reachable wins
    //
    // Default (unset): `registry`. This is the FASTEST path in the
    // common case AND has zero external-tool dependency:
    //
    //   * `docker save` + `tar -xf` serialize the whole image into a
    //     temp tarball every bake. Registry-direct reads layer
    //     blobs directly from `~/.local/supermachine-layer-cache/
    //     registry/blobs/sha256/` into the bake pipeline — no CLI
    //     subprocess, no IPC roundtrip, no tarball materialization.
    //   * Measured (alpine:latest, M-series host, May 2026):
    //       registry cold 1400 ms / warm 290 ms
    //       docker   cold 3350 ms / warm 350 ms
    //   * `auto` adds 80-300 ms probe latency on the first bake of
    //     each process; we'd rather not pay that when registry is
    //     usually faster anyway.
    //
    // Use `docker`/`container` when you specifically want to reuse
    // the host's existing image cache (skip a one-time pull) or
    // share creds with that runtime's auth setup. Use `auto` if
    // you want the legacy probe behavior. Registry-direct itself
    // already reads `~/.docker/config.json` for private-registry
    // creds, so for most workflows you don't need either daemon.
    let explicit = std::env::var("SUPERMACHINE_IMAGE_SOURCE")
        .ok()
        .map(|s| s.to_lowercase());
    match explicit.as_deref() {
        Some("docker") => Ok(Box::new(DockerImageSource {
            arch: arch.to_owned(),
        })),
        Some("container") => Ok(Box::new(ContainerImageSource {
            arch: arch.to_owned(),
        })),
        Some("registry") | None => Ok(Box::new(RegistryImageSource {
            image: parse_registry_image_ref(image)?,
            cached_layout: RefCell::new(None),
            arch: arch.to_owned(),
        })),
        Some("auto") => {
            // Probe local daemons; fall through to registry if
            // nothing is reachable.
            if let Some(rt) = detect_local_image_runtime() {
                return match rt {
                    LocalImageRuntime::Docker => Ok(Box::new(DockerImageSource {
                        arch: arch.to_owned(),
                    })),
                    LocalImageRuntime::Container => Ok(Box::new(ContainerImageSource {
                        arch: arch.to_owned(),
                    })),
                };
            }
            Ok(Box::new(RegistryImageSource {
                image: parse_registry_image_ref(image)?,
                cached_layout: RefCell::new(None),
                arch: arch.to_owned(),
            }))
        }
        Some(other) => Err(format!(
            "SUPERMACHINE_IMAGE_SOURCE={other:?} unrecognized; expected one of \
                 docker | container | registry | auto"
        )),
    }
}

/// Which local image runtime, if any, is reachable on this host.
/// `None` means we should hit the registry direct.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum LocalImageRuntime {
    Docker,
    Container,
}

/// Probe both runtimes once per process and memoize. Each probe is
/// a fast metadata round-trip (~50-200 ms cold); we don't want to
/// pay that on every bake. The result is fixed for the process —
/// if the user starts/stops Docker mid-process they can flip the
/// behavior by setting `SUPERMACHINE_IMAGE_SOURCE` explicitly.
fn detect_local_image_runtime() -> Option<LocalImageRuntime> {
    static DETECTED: OnceLock<Option<LocalImageRuntime>> = OnceLock::new();
    *DETECTED.get_or_init(|| {
        if probe_runtime_reachable("docker", &["info"]) {
            Some(LocalImageRuntime::Docker)
        } else if probe_runtime_reachable("container", &["system", "info"]) {
            Some(LocalImageRuntime::Container)
        } else {
            None
        }
    })
}

fn probe_runtime_reachable(bin: &str, args: &[&str]) -> bool {
    let mut cmd = Command::new(bin);
    cmd.args(args).stdout(Stdio::null()).stderr(Stdio::null());
    // Don't hang if the daemon is busy: 5 s cap is plenty for an
    // `info` call when things are healthy and short enough that a
    // wedged daemon doesn't gate the whole bake.
    match cmd.spawn() {
        Ok(mut child) => {
            let deadline = Instant::now() + Duration::from_secs(5);
            loop {
                match child.try_wait() {
                    Ok(Some(status)) => return status.success(),
                    Ok(None) => {
                        if Instant::now() >= deadline {
                            let _ = child.kill();
                            return false;
                        }
                        std::thread::sleep(Duration::from_millis(50));
                    }
                    Err(_) => return false,
                }
            }
        }
        Err(_) => false,
    }
}

fn sha256_bytes(bytes: &[u8]) -> Result<String, String> {
    let work_dir = temp_work_dir("supermachine-json-sha")?;
    let path = work_dir.join("blob");
    let result = (|| {
        std::fs::write(&path, bytes).map_err(|e| format!("write digest blob: {e}"))?;
        sha256_file(&path)
    })();
    let _ = std::fs::remove_dir_all(work_dir);
    result
}

/// Supply-chain root of trust for digest-pinned image references.
///
/// When a reference is pinned by digest (`repo@sha256:…`), the
/// top-level document the registry returns at `manifests/<digest>` MUST
/// hash to that digest, or the pin is worthless. The downstream
/// per-layer digest checks only prove the layers are consistent with
/// *this* manifest — not that this manifest is the one the caller
/// pinned. A no-op for tag references (nothing to verify) and for
/// non-sha256 digest algorithms (left unverified rather than silently
/// accepted). Returns `Err` on mismatch; the caller must refuse to bake.
fn epoch_ms() -> Result<u128, String> {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|d| d.as_millis())
        .map_err(|e| format!("system time before Unix epoch: {e}"))
}

// pub(crate) to match the visibility of the kernel_boot_cache_* helper
// fns that take `&BakePlan` in their signatures — otherwise the
// `private_interfaces` lint fires (a hard error under the CI's
// `-D warnings`). The struct stays crate-private; this is purely a
// visibility-consistency fix.
pub(crate) struct BakePlan<'a> {
    image: &'a str,
    name: Option<&'a str>,
    runtime: &'a str,
    guest_port: u16,
    memory_mib: u32,
    vcpus: u32,
    pull_policy: &'a str,
    snapshots_dir: &'a Path,
    cmd_override: Option<&'a str>,
    extra_args: &'a [String],
    /// `linux/arm64` or `linux/amd64`. See [`BakeRequest::platform`].
    platform: &'a str,
}

impl<'a> BakePlan<'a> {
    fn from_request(request: &'a BakeRequest) -> Self {
        Self {
            image: &request.image,
            name: request.name.as_deref(),
            runtime: &request.runtime,
            guest_port: request.guest_port,
            memory_mib: request.memory_mib,
            vcpus: request.vcpus,
            pull_policy: &request.pull_policy,
            snapshots_dir: &request.snapshots_dir,
            cmd_override: request.cmd_override.as_deref(),
            extra_args: &request.extra_args,
            platform: &request.platform,
        }
    }

    /// Extract the architecture from `platform` (e.g. "linux/amd64"
    /// → "amd64"). Falls back to "arm64" for unknown / malformed
    /// values so misconfigured callers degrade to today's behavior.
    fn arch(&self) -> &str {
        self.platform.split('/').nth(1).unwrap_or("arm64")
    }

    fn snapshot_name(&self) -> String {
        self.name
            .map(ToOwned::to_owned)
            .unwrap_or_else(|| sanitized_snapshot_name(self.image))
    }

    fn metadata_path(&self) -> PathBuf {
        self.snapshots_dir
            .join(self.snapshot_name())
            .join("metadata.json")
    }

    fn command(&self, root: &Path) -> Result<Command, String> {
        let push = root.join("tools/supermachine-push");
        if !push.is_file() {
            return Err(format!("missing image bake tool: {}", push.display()));
        }
        let mut cmd = Command::new(push);
        cmd.arg(self.image)
            .arg("--runtime")
            .arg(self.runtime)
            .arg("--port")
            .arg(self.guest_port.to_string())
            .arg("--memory")
            .arg(self.memory_mib.to_string())
            .arg("--pull")
            .arg("never")
            .env("SUPERMACHINE_SNAPSHOTS", self.snapshots_dir);
        if let Some(name) = self.name {
            cmd.arg("--name").arg(name);
        }
        cmd.args(self.extra_args);
        Ok(cmd)
    }
}

struct ImageResolution {
    local_arch: Option<String>,
    architecture: Option<String>,
    image_id: Option<String>,
    effective_cmd: Vec<String>,
    working_dir: Option<String>,
    user: Option<String>,
    env: Vec<String>,
    env_count: usize,
    pull_action: String,
    inspect_ms: u128,
}

struct LayerPlan {
    cache_dir: PathBuf,
    index_path: Option<PathBuf>,
    layer_shas: Vec<String>,
    save_work_dir: Option<PathBuf>,
    save_dir: Option<PathBuf>,
    cached_layers: usize,
    missing_layers: usize,
    manifest_cache_hit: bool,
    plan_ms: u128,
}

struct LayerMaterialization {
    materialize_ms: u128,
    built_layers: usize,
    reused_layers: usize,
}

struct DeltaMaterialization {
    prepare_ms: u128,
    materialize_ms: u128,
    cache_hit: bool,
    skipped: Option<String>,
    key: Option<String>,
    cache_path: Option<PathBuf>,
}

struct NativeBakeResult {
    total_ms: u128,
    timings: serde_json::Value,
    reused: bool,
}

static TEMP_COUNTER: AtomicU64 = AtomicU64::new(0);

fn emit_image_resolution_trace(resolution: &ImageResolution) {
    let cmd_json =
        serde_json::to_string(&resolution.effective_cmd).unwrap_or_else(|_| "[]".to_owned());
    eprintln!(
        "supermachine: image resolved inspect_ms={} pull={} local_arch={} arch={} image_id={} env_count={} cmd={}",
        resolution.inspect_ms,
        resolution.pull_action,
        resolution.local_arch.as_deref().unwrap_or(""),
        resolution.architecture.as_deref().unwrap_or(""),
        resolution
            .image_id
            .as_deref()
            .map(|s| &s[..s.len().min(16)])
            .unwrap_or(""),
        resolution.env_count,
        cmd_json
    );
}

pub fn run_push(request: &BakeRequest, run_t0: Instant, root: &Path) -> Result<(), String> {
    let _span = tracing::info_span!(
        "supermachine.bake",
        image = %request.image,
        memory_mib = request.memory_mib,
        vcpus = request.vcpus,
    )
    .entered();
    // HARD: verify the worker binary version BEFORE any bake step.
    // Catches the deadlock pattern where a stale
    // ~/.cargo/bin/supermachine-worker (from an older `cargo
    // install supermachine`) is silently picked up — the
    // pipelined-bake supervisor protocol added BAKE_READY +
    // SNAPSHOT_ASYNC in 0.4.6, and a 0.4.5 worker hangs forever
    // talking to a 0.4.6+ library because each side waits on the
    // other's first message. Failing fast with an upgrade hint is
    // the only humane diagnostic.
    #[cfg(target_os = "macos")]
    {
        let worker_bin = supermachine_worker_bin(root);
        crate::codesign::verify_worker_version(&worker_bin)?;
    }
    let plan = BakePlan::from_request(request);
    if trace_enabled() {
        eprintln!(
            "supermachine: bake plan image={} name={} runtime={} port={} memory={}MiB snapshots={} pull={}",
            plan.image,
            plan.snapshot_name(),
            plan.runtime,
            plan.guest_port,
            plan.memory_mib,
            plan.snapshots_dir.display(),
            plan.pull_policy
        );
    }

    // Fast cache-hit check: if the snapshot's stored bake inputs
    // already match the cheap subset of inputs we can compute
    // without hitting the registry, return immediately. Skips
    // `select_image_source` + `resolve_image` (the slow path for
    // a fresh process — manifest fetch / `docker inspect`). User
    // can force the full resolve via `--pull always`.
    if plan.runtime == "supermachine"
        && std::env::var("SUPERMACHINE_NATIVE_BAKE_TAIL")
            .map(|v| v != "0" && v != "false")
            .unwrap_or(true)
    {
        if let Some(result) = try_fast_cache_hit(&plan, root, run_t0)? {
            if trace_enabled() {
                eprintln!(
                    "supermachine: fast cache-hit after {}ms total={}ms",
                    result.total_ms,
                    elapsed_ms(run_t0)
                );
                eprintln!("supermachine: bake timings {}", result.timings);
            }
            return Ok(());
        }
        // Sibling-clone fast path (0.7.42+): no same-name cache, but
        // another snapshot dir under `snapshots_dir` already matches
        // the same cheap inputs. Clone it instead of baking. Saves
        // ~95% of the bake wall-clock for re-bakes of stable images
        // under different names.
        if let Some(result) = try_sibling_clone_fast_path(&plan, root, run_t0)? {
            if trace_enabled() {
                eprintln!(
                    "supermachine: sibling-clone fast path after {}ms total={}ms",
                    result.total_ms,
                    elapsed_ms(run_t0)
                );
                eprintln!("supermachine: bake timings {}", result.timings);
            }
            return Ok(());
        }
    }

    // Per-snapshot-name bake lock (CORRECTNESS). Past the fast-path
    // checks we're about to do a cold bake that writes the snapshot's
    // rootfs intermediates. Serialize same-name bakes across processes so
    // two cold bakes to one name can't interleave those writes and leave
    // a corrupt snapshot (the guest then panics mounting a half-written
    // root). Held for the rest of the bake.
    let _name_lock = acquire_bake_name_lock(&plan, run_t0)?;

    // Double-checked locking: while we waited for the name lock, a
    // concurrent baker may have produced the snapshot. Re-run the
    // fast-path checks under the lock and reuse the result if so —
    // otherwise we're the elected baker and proceed.
    if plan.runtime == "supermachine"
        && std::env::var("SUPERMACHINE_NATIVE_BAKE_TAIL")
            .map(|v| v != "0" && v != "false")
            .unwrap_or(true)
    {
        if try_fast_cache_hit(&plan, root, run_t0)?.is_some() {
            if trace_enabled() {
                eprintln!(
                    "supermachine: snapshot {} produced by a concurrent baker; reusing (total={}ms)",
                    plan.snapshot_name(),
                    elapsed_ms(run_t0)
                );
            }
            return Ok(());
        }
        if try_sibling_clone_fast_path(&plan, root, run_t0)?.is_some() {
            return Ok(());
        }
    }

    // 0.7.44+ concurrent-bake throttle. Past the fast-path checks
    // we're committed to a heavy cold bake: select_image_source +
    // resolve_image + mksquashfs + cold kernel boot + snapshot
    // capture, ~1-3 GiB working set + multi-second wall clock.
    //
    // Under high test-suite parallelism (FORKS=64 on a 24 GiB host)
    // many forks reach this point simultaneously after a cache-key
    // invalidation. The combined working set exceeds physical RAM,
    // jetsam triggers, processes die mid-bake with EAGAIN cascades
    // (smpark_park: "Resource temporarily unavailable", downstream
    // workers die mid-protocol, etc.).
    //
    // Acquire a host-wide slot before the heavy work; release on
    // function return via the guard's Drop. flock-based file slots
    // give us cross-process coordination without a daemon. Default
    // limit scales with host RAM (~1.5 GiB per concurrent bake).
    // Override via SUPERMACHINE_CONCURRENT_BAKES=N; set to 0 to
    // disable the throttle entirely.
    let _bake_slot = acquire_bake_slot(run_t0)?;

    let source = select_image_source(plan.image, plan.arch())?;
    let resolution = resolve_image(&plan, source.as_ref())?;
    if trace_enabled() {
        emit_image_resolution_trace(&resolution);
    }
    if plan.runtime == "supermachine"
        && std::env::var("SUPERMACHINE_NATIVE_BAKE_TAIL")
            .map(|v| v != "0" && v != "false")
            .unwrap_or(true)
    {
        if let Some(result) =
            native_supermachine_early_reuse_snapshot(&plan, &resolution, root, run_t0)?
        {
            if trace_enabled() {
                eprintln!(
                    "supermachine: native bake reused snapshot after {}ms total={}ms",
                    result.total_ms,
                    elapsed_ms(run_t0)
                );
                eprintln!("supermachine: bake timings {}", result.timings);
                eprintln!(
                    "supermachine: push finished after {}ms total={}ms",
                    result.total_ms,
                    elapsed_ms(run_t0)
                );
            }
            return Ok(());
        }
    }
    let layer_plan = plan_layers(&plan, &resolution, source.as_ref())?;
    let layer_materialization_result = match layer_plan.as_ref() {
        Some(layer_plan) => {
            materialize_missing_layers(plan.image, layer_plan, source.as_ref()).map(Some)
        }
        None => Ok(None),
    };
    if let Some(work_dir) = layer_plan
        .as_ref()
        .and_then(|layer_plan| layer_plan.save_work_dir.as_deref())
    {
        let _ = std::fs::remove_dir_all(work_dir);
    }
    let layer_materialization = layer_materialization_result?;
    let delta_materialization = materialize_delta_cache(&plan, &resolution, root)?;

    if trace_enabled() {
        if let Some(layer_plan) = layer_plan.as_ref() {
            let first_layer = layer_plan
                .layer_shas
                .first()
                .map(|s| &s[..s.len().min(16)])
                .unwrap_or("");
            eprintln!(
                "supermachine: layer plan plan_ms={} layers={} cached={} missing={} manifest_cache_hit={} cache={} index={} first_layer={}",
                layer_plan.plan_ms,
                layer_plan.layer_shas.len(),
                layer_plan.cached_layers,
                layer_plan.missing_layers,
                layer_plan.manifest_cache_hit,
                layer_plan.cache_dir.display(),
                layer_plan
                    .index_path
                    .as_deref()
                    .map(|p| p.display().to_string())
                    .unwrap_or_default(),
                first_layer
            );
        }
        if let Some(materialization) = layer_materialization.as_ref() {
            eprintln!(
                "supermachine: layer materialize materialize_ms={} built={} reused={}",
                materialization.materialize_ms,
                materialization.built_layers,
                materialization.reused_layers
            );
        }
        if let Some(delta) = delta_materialization.as_ref() {
            eprintln!(
                "supermachine: delta materialize prepare_ms={} materialize_ms={} cache_hit={} skipped={} key={} cache={}",
                delta.prepare_ms,
                delta.materialize_ms,
                delta.cache_hit,
                delta.skipped.as_deref().unwrap_or(""),
                delta
                    .key
                    .as_deref()
                    .map(|key| &key[..key.len().min(16)])
                    .unwrap_or(""),
                delta
                    .cache_path
                    .as_deref()
                    .map(|path| path.display().to_string())
                    .unwrap_or_default()
            );
        }
    }

    if plan.runtime == "supermachine"
        && std::env::var("SUPERMACHINE_NATIVE_BAKE_TAIL")
            .map(|v| v != "0" && v != "false")
            .unwrap_or(true)
    {
        let layer_plan = layer_plan
            .as_ref()
            .ok_or_else(|| "missing layer plan".to_owned())?;
        let delta = delta_materialization
            .as_ref()
            .ok_or_else(|| "missing delta cache".to_owned())?;
        let (native_bake_key, native_bake_inputs) =
            native_supermachine_bake_key(&plan, &resolution, layer_plan, delta, root)?;
        if let Some(result) = native_supermachine_reuse_snapshot(&plan, &native_bake_key, run_t0)? {
            if trace_enabled() {
                eprintln!(
                    "supermachine: native bake reused snapshot after {}ms total={}ms",
                    result.total_ms,
                    elapsed_ms(run_t0)
                );
                eprintln!("supermachine: bake timings {}", result.timings);
                eprintln!(
                    "supermachine: push finished after {}ms total={}ms",
                    result.total_ms,
                    elapsed_ms(run_t0)
                );
            }
            return Ok(());
        }
        let native_t0 = Instant::now();
        let result = run_native_supermachine_bake(
            &plan,
            &resolution,
            layer_plan,
            delta,
            root,
            &native_bake_key,
            &native_bake_inputs,
        )?;
        if trace_enabled() {
            if result.reused {
                eprintln!(
                    "supermachine: native bake reused snapshot after {}ms total={}ms",
                    result.total_ms,
                    elapsed_ms(run_t0)
                );
            } else {
                eprintln!(
                    "supermachine: native bake finished after {}ms total={}ms",
                    result.total_ms,
                    elapsed_ms(run_t0)
                );
            }
            eprintln!("supermachine: bake timings {}", result.timings);
        }
        if trace_enabled() {
            eprintln!(
                "supermachine: push finished after {}ms total={}ms",
                elapsed_ms(native_t0),
                elapsed_ms(run_t0)
            );
        }
        return Ok(());
    }

    let mut cmd = plan.command(root)?;
    let push_t0 = Instant::now();
    let status = cmd
        .status()
        .map_err(|e| format!("supermachine-push: {e}"))?;
    let push_ms = elapsed_ms(push_t0);
    if trace_enabled() {
        eprintln!(
            "supermachine: push finished after {}ms total={}ms",
            push_ms,
            elapsed_ms(run_t0)
        );
    }
    if status.success() {
        emit_bake_timing_metadata(&plan);
        Ok(())
    } else {
        Err(format!(
            "supermachine-push failed with exit code {}",
            status.code().unwrap_or(1)
        ))
    }
}

fn home_join(path: &str) -> PathBuf {
    std::env::var_os("HOME")
        .map(PathBuf::from)
        .unwrap_or_else(|| PathBuf::from("."))
        .join(path)
}

/// RAII guard for a cross-process concurrent-bake slot. Drop releases
/// the flock (the OS reclaims it on process death too, so a crashed
/// bake doesn't leak its slot indefinitely).
struct BakeSlotGuard {
    _file: Option<std::fs::File>,
}

/// Held for the duration of a cold bake to serialize same-name bakes
/// across processes. The flock is released when `_file` (and thus the
/// fd) drops.
struct BakeNameLock {
    _file: Option<std::fs::File>,
}

/// Per-snapshot-NAME bake lock. Serializes concurrent COLD bakes to the
/// SAME snapshot name across processes. Without it, two cold bakes to one
/// name interleave their rootfs writes (delta.squashfs / init.cpio.gz /
/// the rootfs CAS) and the guest panics mounting a half-written root
/// ("VFS: Unable to mount root fs on unknown-block(0,0)"). The in-process
/// path already coordinates via the Image handle / warm-handoff; this
/// extends the same guarantee across processes.
///
/// Distinct from [`acquire_bake_slot`] (a host-wide *counting* throttle,
/// OFF by default): this is per-name and ALWAYS on — a correctness lock,
/// not a throughput knob. Uncontended (the common one-bake-per-name case)
/// it is a single `open` + `flock` + close, ~µs. Contenders block until
/// the holder finishes; the caller then re-checks the cache and loads the
/// holder's finished snapshot instead of re-baking.
///
/// The lock file is a SIBLING of the snapshot dir
/// (`<snapshots_dir>/.<name>.bakelock`), so the snapshot dir's
/// atomic-rename-into-place never disturbs the lock.
fn acquire_bake_name_lock(plan: &BakePlan<'_>, run_t0: Instant) -> Result<BakeNameLock, String> {
    use std::os::fd::AsRawFd;
    let dir = plan.snapshots_dir;
    std::fs::create_dir_all(dir)
        .map_err(|e| format!("mkdir snapshots dir {}: {e}", dir.display()))?;
    let lock_path = dir.join(format!(".{}.bakelock", plan.snapshot_name()));
    let file = std::fs::OpenOptions::new()
        .create(true)
        .read(true)
        .write(true)
        .truncate(false)
        .open(&lock_path)
        .map_err(|e| format!("open bake name-lock {}: {e}", lock_path.display()))?;
    // Poll with non-blocking flock + a hard deadline (rather than a
    // blocking LOCK_EX) so a wedged holder can't gate forever, matching
    // acquire_bake_slot's posture.
    let deadline = Instant::now() + Duration::from_secs(300);
    let mut waited = false;
    loop {
        let r = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) };
        if r == 0 {
            if waited && trace_enabled() {
                eprintln!(
                    "supermachine: bake name-lock for {} acquired (wait={}ms)",
                    plan.snapshot_name(),
                    elapsed_ms(run_t0)
                );
            }
            return Ok(BakeNameLock { _file: Some(file) });
        }
        if !waited {
            if trace_enabled() {
                eprintln!(
                    "supermachine: another process is baking {}; waiting for the bake lock",
                    plan.snapshot_name()
                );
            }
            waited = true;
        }
        if Instant::now() > deadline {
            return Err(format!(
                "bake name-lock timeout after 5 min for {}; a same-name bake \
                 elsewhere may be wedged",
                plan.snapshot_name()
            ));
        }
        std::thread::sleep(Duration::from_millis(50));
    }
}

/// Returns the configured limit on concurrent COLD bakes across all
/// supermachine processes on this host.
///
/// **Default: 0 (DISABLED).** Empirical measurement showed that the
/// flock-slot semaphore consistently HURT throughput even at loose
/// N values that should have been near no-op:
///
///   FORKS=50, N=0 (default)  74 s    0-1 failures (baseline)
///   FORKS=50, N=48 (=2×ram)  86 s    8 failures   (timing pressure)
///   FORKS=64, N=24 (=ram)    87 s    avg 7 failures
///   FORKS=64, N=0            70 s    avg 8 failures (jetsam cascade)
///
/// The hypothesis was that throttling concurrent cold-bakes would
/// smooth the jetsam-cascade pattern at FORKS=64. In practice the
/// flock contention, even at "loose" N values, added more latency
/// to bake-time-sensitive tests (bake_timing<3s, perf P3<100ms,
/// signal-delivery hang tests) than the jetsam-prevention saved.
/// macOS memory compression + cooperative-yield scheduling handle
/// the overcommit better than coarse-grained host-wide throttling.
///
/// The code is retained — set `SUPERMACHINE_CONCURRENT_BAKES=N` to
/// opt in for diagnostic / long-lived-host scenarios where jetsam
/// cascades are observed empirically.
fn bake_concurrency_limit() -> usize {
    if let Ok(s) = std::env::var("SUPERMACHINE_CONCURRENT_BAKES") {
        if let Ok(n) = s.parse::<usize>() {
            return n;
        }
    }
    0
}

fn sysinfo_total_ram_bytes() -> u64 {
    // macOS / BSD: sysctl `hw.memsize`. Linux: sysconf SC_PHYS_PAGES *
    // SC_PAGE_SIZE. We only run on macOS so the sysctl path is the
    // common case; fall back to a 16 GiB assumption if it fails.
    #[cfg(target_os = "macos")]
    {
        let mut size: u64 = 0;
        let mut len = std::mem::size_of::<u64>();
        let name = c"hw.memsize";
        let r = unsafe {
            libc::sysctlbyname(
                name.as_ptr(),
                &mut size as *mut _ as *mut libc::c_void,
                &mut len,
                std::ptr::null_mut(),
                0,
            )
        };
        if r == 0 && size > 0 {
            return size;
        }
    }
    16 * 1024 * 1024 * 1024
}

/// Acquire one of N host-wide bake slots via flock on a slot file.
///
/// Probing order: start at `pid % N` so 64 forks don't all collide
/// on slot 0 first; round-robin from there. Non-blocking `flock` on
/// each slot — if any one is free we hold it and return. If all N
/// are held, sleep briefly and retry. Hard 5-minute deadline so a
/// genuinely-wedged bake elsewhere can't gate the whole host
/// forever (slot-holder crashed mid-bake without releasing → kernel
/// reclaims, retry succeeds; slot-holder genuinely making progress
/// finishes within minutes anyway).
fn acquire_bake_slot(run_t0: Instant) -> Result<BakeSlotGuard, String> {
    use std::os::fd::AsRawFd;
    let n = bake_concurrency_limit();
    if n == 0 {
        // Throttle disabled — back to legacy unbounded behavior.
        return Ok(BakeSlotGuard { _file: None });
    }
    let dir = home_join(".local/supermachine-bake-slots");
    std::fs::create_dir_all(&dir)
        .map_err(|e| format!("mkdir bake-slots {}: {e}", dir.display()))?;
    let start = std::process::id() as usize % n;
    let deadline = Instant::now() + Duration::from_secs(300);
    let mut waited_first = false;
    loop {
        for offset in 0..n {
            let i = (start + offset) % n;
            let slot = dir.join(format!("slot.{i}"));
            let file = match std::fs::OpenOptions::new()
                .create(true)
                .read(true)
                .write(true)
                .truncate(false)
                .open(&slot)
            {
                Ok(f) => f,
                Err(e) => {
                    return Err(format!("open bake slot {}: {e}", slot.display()));
                }
            };
            let r = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) };
            if r == 0 {
                if waited_first && trace_enabled() {
                    eprintln!(
                        "supermachine: bake slot acquired (slot={}, wait={}ms)",
                        i,
                        elapsed_ms(run_t0)
                    );
                }
                return Ok(BakeSlotGuard { _file: Some(file) });
            }
            // flock failed (EWOULDBLOCK). Drop the fd before trying the next.
            drop(file);
        }
        if !waited_first {
            if trace_enabled() {
                eprintln!(
                    "supermachine: all {n} bake slots busy, waiting (set SUPERMACHINE_CONCURRENT_BAKES=N to tune)"
                );
            }
            waited_first = true;
        }
        if Instant::now() > deadline {
            return Err(format!(
                "bake-slot acquire timeout after 5 min ({n} slots, all held). \
                 A bake elsewhere may be wedged; consider \
                 SUPERMACHINE_CONCURRENT_BAKES=0 to disable the throttle."
            ));
        }
        std::thread::sleep(Duration::from_millis(50));
    }
}

fn layer_cache_dir() -> PathBuf {
    std::env::var_os("SUPERMACHINE_LAYER_CACHE")
        .map(PathBuf::from)
        .unwrap_or_else(|| home_join(".local/supermachine-layer-cache"))
}

fn volumes_dir() -> PathBuf {
    std::env::var_os("SUPERMACHINE_VOLUMES")
        .map(PathBuf::from)
        .unwrap_or_else(|| home_join(".local/supermachine/volumes"))
}

/// One entry from the user's `--volume HOST:GUEST[:SIZE_BYTES]` flag.
///
/// HOST can be either:
/// - a name (no `/`, no `.` prefix): resolved to
///   `~/.local/supermachine/volumes/<name>.img`
/// - an absolute path / relative-with-slashes: used as-is
///
/// GUEST is the absolute mount path inside the guest.
///
/// SIZE_BYTES (optional, defaults to `VolumeSpec::DEFAULT_SIZE_BYTES`)
/// is the volume's sparse-allocation cap. The backing file is created
/// sparse at this size if missing; existing files are left alone.
#[derive(Debug, Clone)]
pub(crate) struct VolumeMapping {
    /// Resolved host file path.
    pub host_file: PathBuf,
    /// Mount point inside the guest, must start with `/`.
    pub guest_path: String,
    /// Size cap in bytes for the sparse host file.
    #[allow(dead_code)] // wired up in a follow-up; today the worker
    // creates the file at this size if missing
    // via VolumeSpec::new + with_size_bytes
    pub size_bytes: u64,
}

/// Parse all `--volume HOST:GUEST[:SIZE_BYTES]` entries from
/// `extra_args` and return them in the order they were specified.
/// Validates that HOST and GUEST are well-formed; does not yet
/// create the host file. SIZE_BYTES is optional for backward
/// compatibility with pre-0.7.0 snapshots; absent → default 1 GiB.
pub(crate) fn parse_volume_args(extra_args: &[String]) -> Result<Vec<VolumeMapping>, String> {
    let mut out = Vec::new();
    for raw in arg_values(extra_args, "--volume") {
        let parts: Vec<&str> = raw.splitn(3, ':').collect();
        if parts.len() < 2 {
            return Err(format!(
                "--volume expects HOST:GUEST[:SIZE_BYTES], got {raw:?}"
            ));
        }
        let host = parts[0];
        let guest = parts[1];
        let size_bytes = if let Some(s) = parts.get(2) {
            s.parse::<u64>()
                .map_err(|_| format!("--volume SIZE_BYTES not a u64: {s:?}"))?
        } else {
            crate::vmm::resources::VolumeSpec::DEFAULT_SIZE_BYTES
        };
        if host.is_empty() || guest.is_empty() {
            return Err(format!("--volume HOST:GUEST has empty side: {raw:?}"));
        }
        if !guest.starts_with('/') {
            return Err(format!("--volume guest path must be absolute: {guest:?}"));
        }
        if guest.contains('\n') || guest.contains('\r') {
            return Err(format!("--volume guest path contains newline: {guest:?}"));
        }
        let host_file = if host.contains('/') || host.starts_with('.') {
            PathBuf::from(host)
        } else {
            // Sanitize the name so it's safe as a filename.
            if host.chars().any(|c| matches!(c, '/' | '\\' | ':' | '\0')) {
                return Err(format!("--volume name {host:?} contains forbidden chars"));
            }
            volumes_dir().join(format!("{host}.img"))
        };
        out.push(VolumeMapping {
            host_file,
            guest_path: guest.to_owned(),
            size_bytes,
        });
    }
    Ok(out)
}

/// Ensure the host file backing this volume exists and is
/// formatted ext4. Idempotent: if the file already exists at
/// `>= size_bytes`, leave it alone.
pub(crate) fn ensure_volume_host_file(
    mapping: &VolumeMapping,
    size_bytes: u64,
) -> Result<(), String> {
    let path = &mapping.host_file;
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent)
            .map_err(|e| format!("create volume dir {}: {e}", parent.display()))?;
    }
    let already_existed = path.is_file();
    if already_existed {
        let len = std::fs::metadata(path)
            .map_err(|e| format!("stat {}: {e}", path.display()))?
            .len();
        if len >= size_bytes {
            return Ok(());
        }
        // Existing-but-too-small: don't grow silently — that
        // requires an in-place resize2fs which we don't ship.
        // Surface the mismatch so the user can pick a larger
        // size or rm the file.
        return Err(format!(
            "volume {} exists at {len} bytes, smaller than requested {size_bytes}; \
             remove it or pass a smaller size",
            path.display()
        ));
    }

    // Synthesize an empty, journal-less ext4 in-process (sparse file;
    // cleans up after itself on failure). No host mke2fs needed — see
    // `crate::ext4` for the format choices and why no journal.
    crate::ext4::make_empty_ext4(path, size_bytes)
        .map_err(|e| format!("format volume {}: {e}", path.display()))?;
    Ok(())
}

fn trace_enabled() -> bool {
    crate::trace::enabled("run")
}

fn elapsed_ms(t0: Instant) -> u128 {
    t0.elapsed().as_millis()
}

/// Public version of `sanitized_snapshot_name` for callers that
/// need to predict the snapshot directory layout before/after a
/// bake (e.g. `Image::from_oci`'s cache lookup).
pub fn snapshot_name_for_image(image: &str) -> String {
    sanitized_snapshot_name(image)
}

fn sanitized_snapshot_name(image: &str) -> String {
    image
        .chars()
        .map(|c| if matches!(c, ':' | '/' | '.') { '_' } else { c })
        .take(60)
        .collect()
}

fn emit_bake_timing_metadata(plan: &BakePlan<'_>) {
    if !trace_enabled() {
        return;
    }
    let meta_path = plan.metadata_path();
    let Ok(text) = std::fs::read_to_string(&meta_path) else {
        return;
    };
    let Ok(json) = serde_json::from_str::<serde_json::Value>(&text) else {
        return;
    };
    if let Some(timings) = json.get("timings") {
        eprintln!("supermachine: bake timings {timings}");
    }
}

fn command_output(mut cmd: Command, label: &str) -> Result<String, String> {
    let output = cmd.output().map_err(|e| format!("{label}: {e}"))?;
    if output.status.success() {
        return String::from_utf8(output.stdout)
            .map_err(|e| format!("{label}: non-utf8 stdout: {e}"));
    }
    let stderr = String::from_utf8_lossy(&output.stderr);
    let stdout = String::from_utf8_lossy(&output.stdout);
    let detail = stderr
        .lines()
        .find(|line| !line.trim().is_empty())
        .or_else(|| stdout.lines().find(|line| !line.trim().is_empty()))
        .unwrap_or("command failed");
    Err(format!("{label}: {detail}"))
}

fn run_status(mut cmd: Command, label: &str) -> Result<(), String> {
    let status = cmd.status().map_err(|e| format!("{label}: {e}"))?;
    if status.success() {
        Ok(())
    } else {
        Err(format!(
            "{label} failed with exit code {}",
            status.code().unwrap_or(1)
        ))
    }
}

fn spawn_status(mut cmd: Command, label: &str) -> Result<std::process::ExitStatus, String> {
    cmd.status().map_err(|e| format!("{label}: {e}"))
}

fn value_string_array(value: Option<&serde_json::Value>) -> Vec<String> {
    match value {
        Some(serde_json::Value::Array(items)) => items
            .iter()
            .filter_map(|v| v.as_str().map(ToOwned::to_owned))
            .collect(),
        Some(serde_json::Value::String(s)) if !s.is_empty() => vec![s.to_owned()],
        _ => Vec::new(),
    }
}

/// POSIX-shell single-quote a string so it survives `sh` word-splitting/expansion
/// verbatim. Wrap in `'…'` and rewrite each embedded `'` as `'\''` (close quote,
/// escaped literal quote, reopen). Used to bake an OCI workload's argv + env into
/// a `run-workload` script without any injection surface.
#[cfg_attr(
    not(all(target_os = "linux", target_arch = "x86_64")),
    allow(dead_code)
)]
fn sh_single_quote(s: &str) -> String {
    let mut out = String::with_capacity(s.len() + 2);
    out.push('\'');
    for c in s.chars() {
        if c == '\'' {
            out.push_str("'\\''");
        } else {
            out.push(c);
        }
    }
    out.push('\'');
    out
}

/// Derive the guest workload-launch script from an OCI image's runtime config.
///
/// Reads the materialized OCI `layout`, assembles the process argv as
/// `Entrypoint ++ Cmd` (Docker/OCI semantics), and renders a self-contained
/// `sh` script that exports the image `Env`, `cd`s into `WorkingDir`, and
/// `exec`s the argv. Returns `None` when the image declares no command (nothing
/// to launch — the agent then runs alone, as before). Everything is
/// single-quote-escaped via [`sh_single_quote`], so arbitrary env/argv content
/// is inert.
///
/// This is the KVM counterpart of HVF's init-oci, which launches the OCI PID-1
/// workload from the image config. The rendered script is written into the
/// squashfs rootfs at `/.supermachine/run-workload` (see
/// [`build_kvm_rootfs_squashfs`]) and forked by the initramfs `launch` shim
/// before it execs the agent (see [`build_kvm_agent_initramfs`]).
#[cfg_attr(
    not(all(target_os = "linux", target_arch = "x86_64")),
    allow(dead_code)
)]
pub(crate) fn kvm_workload_script(
    image: &str,
    layout: &Path,
    arch: &str,
) -> Result<Option<String>, String> {
    let info = oci::inspect_oci_layout(image, layout, arch)?;
    let cfg = info.get("Config");
    let cfg = match cfg {
        Some(c) => c,
        None => return Ok(None),
    };
    let mut argv = value_string_array(cfg.get("Entrypoint"));
    argv.extend(value_string_array(cfg.get("Cmd")));
    if argv.is_empty() {
        // No command in the image config → nothing to launch.
        return Ok(None);
    }
    let env = value_string_array(cfg.get("Env"));
    let workdir = cfg
        .get("WorkingDir")
        .and_then(|v| v.as_str())
        .filter(|s| !s.is_empty());

    let mut script = String::from("#!/.supermachine/busybox sh\n");
    // Export each VAR=VALUE from the image config. Split on the FIRST '=' only
    // (values may themselves contain '='); skip malformed entries with no '='.
    for kv in &env {
        if let Some(eq) = kv.find('=') {
            let (k, v) = (&kv[..eq], &kv[eq + 1..]);
            // Keys are conventionally [A-Za-z_][A-Za-z0-9_]*; emit verbatim and
            // single-quote the value.
            script.push_str(&format!("export {k}={}\n", sh_single_quote(v)));
        }
    }
    if let Some(dir) = workdir {
        // Best-effort cd; if the image's WorkingDir doesn't exist we still try
        // to exec (matches Docker, which would error — but we favor liveness).
        script.push_str(&format!(
            "cd {} 2>/dev/null || true\n",
            sh_single_quote(dir)
        ));
    }
    script.push_str("exec");
    for a in &argv {
        script.push(' ');
        script.push_str(&sh_single_quote(a));
    }
    script.push('\n');
    Ok(Some(script))
}

/// Render a `/.supermachine/run-workload` script from an EXPLICIT argv + env +
/// workdir (rather than reading them from the OCI image config like
/// [`kvm_workload_script`]). Used by the sentry bake path when the
/// [`crate::api::OciImageBuilder`] overrides the image's command/env (the
/// `with_cmd` / `with_env` builder methods): the effective env is `image Env`
/// merged-under the builder's `env`, the effective argv is the builder's `cmd`
/// (Entrypoint ++ Cmd from the image when the builder gives none), and the
/// effective workdir is the builder's `working_dir` (image's otherwise).
///
/// `argv` empty → `None` (nothing to launch, like [`kvm_workload_script`]).
/// Every value is single-quote-escaped via [`sh_single_quote`], so arbitrary
/// env/argv content is inert. The first line is the same
/// `#!/.supermachine/busybox sh` shebang the KVM script uses (an ordinary
/// comment to the sentry's `/bin/sh`, which is what runs it).
#[cfg_attr(
    not(all(target_os = "linux", target_arch = "x86_64")),
    allow(dead_code)
)]
pub(crate) fn sentry_workload_script(
    argv: &[String],
    env: &[(String, String)],
    workdir: Option<&str>,
) -> Option<String> {
    if argv.is_empty() {
        return None;
    }
    let mut script = String::from("#!/.supermachine/busybox sh\n");
    for (k, v) in env {
        // Skip a malformed (empty) key defensively; values may contain '='
        // and any metachar — single-quote them verbatim.
        if k.is_empty() {
            continue;
        }
        script.push_str(&format!("export {k}={}\n", sh_single_quote(v)));
    }
    if let Some(dir) = workdir.filter(|s| !s.is_empty()) {
        script.push_str(&format!(
            "cd {} 2>/dev/null || true\n",
            sh_single_quote(dir)
        ));
    }
    script.push_str("exec");
    for a in argv {
        script.push(' ');
        script.push_str(&sh_single_quote(a));
    }
    script.push('\n');
    Some(script)
}

fn temp_work_dir(prefix: &str) -> Result<PathBuf, String> {
    let n = TEMP_COUNTER.fetch_add(1, Ordering::Relaxed);
    let path = std::env::temp_dir().join(format!("{prefix}-{}-{n}", std::process::id()));
    std::fs::create_dir_all(&path)
        .map_err(|e| format!("create temp dir {}: {e}", path.display()))?;
    Ok(path)
}

fn strip_sha256(s: &str) -> String {
    s.strip_prefix("sha256:").unwrap_or(s).to_owned()
}

/// Strip `sha256:` and validate the result is safe as a SINGLE path
/// component — a content digest is pure lowercase hex, so any `/`, `..`,
/// or empty value is a malformed or hostile manifest trying to traverse
/// out of `blobs/sha256/` (or the layer cache). `blob_path` already
/// guarded the registry path; this is the shared guard for every digest
/// → path join, including the `docker save` / OCI-layout enumeration.
fn sha256_path_component(digest: &str) -> Result<String, String> {
    let sha = strip_sha256(digest);
    if sha.is_empty() || sha.contains('/') || sha.contains("..") {
        return Err(format!("unsafe OCI digest (path traversal): {digest:?}"));
    }
    Ok(sha)
}

/// Join a manifest-supplied RELATIVE path under `root`, rejecting any
/// element that would escape it (an absolute path, or a `..` / root
/// component). Guards arbitrary-file access via a crafted local image
/// tarball manifest — e.g. a legacy `manifest.json` whose `"Config"` or
/// `"Layers"` entry is `"../../../../etc/passwd"`.
fn confined_join(root: &Path, rel: &str) -> Result<PathBuf, String> {
    let p = Path::new(rel);
    let escapes = p.is_absolute()
        || p.components().any(|c| {
            matches!(
                c,
                std::path::Component::ParentDir | std::path::Component::RootDir
            )
        });
    if escapes {
        return Err(format!("unsafe manifest path (traversal): {rel:?}"));
    }
    Ok(root.join(p))
}

#[cfg(unix)]
fn rootfs_user_group(root: &Path, user: &str) -> Option<(u32, u32)> {
    if user == "-" {
        return None;
    }
    if let Ok(uid) = user.parse::<u32>() {
        return Some((uid, uid));
    }
    let passwd = std::fs::read_to_string(root.join("etc/passwd")).ok()?;
    for line in passwd.lines() {
        let mut fields = line.split(':');
        let name = fields.next()?;
        if name != user {
            continue;
        }
        let _passwd = fields.next()?;
        let uid = fields.next()?.parse().ok()?;
        let gid = fields.next()?.parse().ok()?;
        return Some((uid, gid));
    }
    None
}

#[cfg(unix)]
fn rootfs_group(root: &Path, group: &str) -> Option<u32> {
    if group == "-" {
        return None;
    }
    if let Ok(gid) = group.parse::<u32>() {
        return Some(gid);
    }
    let groups = std::fs::read_to_string(root.join("etc/group")).ok()?;
    for line in groups.lines() {
        let mut fields = line.split(':');
        let name = fields.next()?;
        if name != group {
            continue;
        }
        let _passwd = fields.next()?;
        return fields.next()?.parse().ok();
    }
    None
}

#[cfg(unix)]
fn chown_rootfs_path(path: &Path, uid: u32, gid: u32) -> Result<(), String> {
    use std::os::unix::ffi::OsStrExt as _;
    let c = std::ffi::CString::new(path.as_os_str().as_bytes())
        .map_err(|_| format!("tmpfiles path contains NUL: {}", path.display()))?;
    let r = unsafe { libc::chown(c.as_ptr(), uid as libc::uid_t, gid as libc::gid_t) };
    if r == 0 {
        Ok(())
    } else {
        Err(format!(
            "chown {} to {uid}:{gid}: {}",
            path.display(),
            std::io::Error::last_os_error()
        ))
    }
}

#[cfg(unix)]
fn apply_tmpfiles_dirs(root: &Path) -> Result<(), String> {
    use std::os::unix::fs::PermissionsExt as _;

    let mut files = Vec::new();
    for dir in ["usr/lib/tmpfiles.d", "etc/tmpfiles.d"] {
        let d = root.join(dir);
        let Ok(entries) = std::fs::read_dir(&d) else {
            continue;
        };
        for entry in entries.flatten() {
            if entry.path().extension().and_then(|e| e.to_str()) == Some("conf") {
                files.push(entry.path());
            }
        }
    }
    files.sort();

    for file in files {
        let text = std::fs::read_to_string(&file)
            .map_err(|e| format!("read tmpfiles {}: {e}", file.display()))?;
        for raw in text.lines() {
            let line = raw.split('#').next().unwrap_or("").trim();
            if line.is_empty() {
                continue;
            }
            let fields: Vec<&str> = line.split_whitespace().collect();
            if fields.len() < 2 {
                continue;
            }
            let typ = fields[0].trim_start_matches(['!', '-']);
            if typ != "d" && typ != "D" {
                continue;
            }
            let path = fields[1];
            if !path.starts_with('/') || path.contains('*') {
                continue;
            }
            let rel = path.trim_start_matches('/');
            let host = confined_join(root, rel)?;
            std::fs::create_dir_all(&host)
                .map_err(|e| format!("tmpfiles create {}: {e}", host.display()))?;
            if let Some(mode) = fields
                .get(2)
                .filter(|m| **m != "-")
                .and_then(|m| u32::from_str_radix(m, 8).ok())
            {
                std::fs::set_permissions(&host, std::fs::Permissions::from_mode(mode))
                    .map_err(|e| format!("tmpfiles chmod {}: {e}", host.display()))?;
            }
            let mut uid = 0;
            let mut gid = 0;
            let mut have_owner = false;
            if let Some(user) = fields.get(3).and_then(|u| rootfs_user_group(root, u)) {
                uid = user.0;
                gid = user.1;
                have_owner = true;
            }
            if let Some(group) = fields.get(4).and_then(|g| rootfs_group(root, g)) {
                gid = group;
                have_owner = true;
            }
            if have_owner {
                chown_rootfs_path(&host, uid, gid)?;
            }
        }
    }
    Ok(())
}

/// Parse a single `--mount HOST:TAG:GUEST_PATH[:POLICY]` value into
/// the JSON object form persisted under `mounts[]` in snapshot
/// metadata. Returns `None` on malformed input — the caller filters
/// those out (defensive against forwarded garbage).
///
/// POLICY (optional) is one of `opaque` | `deny` | `follow`;
/// omitted = `opaque` (default).
/// Normalize a request's `--mount HOST:TAG:GUEST[:POLICY]` extra args
/// into the same JSON shape the bake driver records as
/// `metadata.mounts` — so reuse checks can compare a CURRENT request's
/// mount config against a cached snapshot's recorded one without a
/// second encoding path drifting.
pub(crate) fn mounts_meta_from_extra_args(extra_args: &[String]) -> Vec<serde_json::Value> {
    arg_values(extra_args, "--mount")
        .into_iter()
        .filter_map(parse_mount_arg)
        .collect()
}

fn parse_mount_arg(raw: &str) -> Option<serde_json::Value> {
    let parts: Vec<&str> = raw.splitn(4, ':').collect();
    let (host_path, guest_tag, guest_path, symlinks) = match parts.len() {
        3 => (parts[0], parts[1], parts[2], None),
        4 => (parts[0], parts[1], parts[2], Some(parts[3])),
        _ => return None,
    };
    if guest_tag.is_empty() || guest_path.is_empty() {
        return None;
    }
    let mut obj = serde_json::Map::new();
    obj.insert("host_path".to_owned(), host_path.into());
    obj.insert("guest_tag".to_owned(), guest_tag.into());
    obj.insert("guest_path".to_owned(), guest_path.into());
    if let Some(p) = symlinks {
        obj.insert("symlinks".to_owned(), p.into());
    }
    Some(serde_json::Value::Object(obj))
}

fn arg_values<'a>(args: &'a [String], flag: &str) -> Vec<&'a str> {
    let mut values = Vec::new();
    let mut i = 0;
    while i < args.len() {
        if args[i] == flag && i + 1 < args.len() {
            values.push(args[i + 1].as_str());
            i += 2;
        } else {
            i += 1;
        }
    }
    values
}

fn arg_present(args: &[String], flag: &str) -> bool {
    args.iter().any(|arg| arg == flag)
}

fn arg_value<'a>(args: &'a [String], flag: &str) -> Option<&'a str> {
    arg_values(args, flag).into_iter().last()
}

/// SHA-256 of a file's contents, hex-encoded (64 lowercase chars).
///
/// 0.7.44+ uses in-process `ring::digest::SHA256` instead of forking
/// `shasum -a 256`. ring + chunked read is consistently ~3-10× faster
/// than fork+exec+shasum-cli for files in the 1-50 MiB range we hit
/// in practice (worker binary, --extra-file payloads, --inbound-tls-*).
/// Net win is dominated by avoiding the `fork+execve` of `/usr/bin/shasum`
/// — that alone is ~5-15 ms on macOS even before any actual hashing.
///
/// Cache: keyed on `(canonical_path, len, mtime_ns)`. The hot caller
/// (`sha256_file(&sm22_bin)` at `run_native_supermachine_bake_pipelined`)
/// fires once per bake; with the cache, only the FIRST bake in a
/// supermachine session pays the hash cost. Other call sites benefit
/// proportional to how often the same file is hashed.
///
/// Invalidation is purely "input changed" — no TTL, no manual flush.
/// If the underlying file is replaced (mtime bumps) or grows/shrinks
/// (len changes), the cache key no longer matches and we recompute.
fn sha256_file(path: &Path) -> Result<String, String> {
    use ring::digest::{Context, SHA256};
    use std::collections::HashMap;
    use std::io::Read;
    use std::sync::RwLock;
    static CACHE: OnceLock<RwLock<HashMap<(PathBuf, u64, u128), String>>> = OnceLock::new();

    let canon = path
        .canonicalize()
        .map_err(|e| format!("canonicalize {}: {e}", path.display()))?;
    let meta = std::fs::metadata(&canon).map_err(|e| format!("stat {}: {e}", canon.display()))?;
    let len = meta.len();
    let mtime_ns = meta
        .modified()
        .map_err(|e| format!("mtime {}: {e}", canon.display()))?
        .duration_since(UNIX_EPOCH)
        .map(|d| d.as_nanos())
        .unwrap_or(0);
    let key = (canon.clone(), len, mtime_ns);

    let cache = CACHE.get_or_init(|| RwLock::new(HashMap::new()));
    if let Ok(g) = cache.read() {
        if let Some(hit) = g.get(&key) {
            return Ok(hit.clone());
        }
    }

    let mut f =
        std::fs::File::open(&canon).map_err(|e| format!("open {}: {e}", canon.display()))?;
    let mut ctx = Context::new(&SHA256);
    // 64 KiB stays well inside L1/L2; bigger buffers don't measurably
    // help on the file sizes we hash here.
    let mut buf = [0u8; 64 * 1024];
    loop {
        let n = f
            .read(&mut buf)
            .map_err(|e| format!("read {}: {e}", canon.display()))?;
        if n == 0 {
            break;
        }
        ctx.update(&buf[..n]);
    }
    let digest = ctx.finish();
    let mut hex = String::with_capacity(64);
    for b in digest.as_ref() {
        use std::fmt::Write;
        let _ = write!(hex, "{:02x}", b);
    }
    if let Ok(mut g) = cache.write() {
        // Bound the cache so a long-lived embedder doesn't pile up
        // entries from temp-file hashing. 1024 entries × ~200 bytes
        // each ≈ 200 KiB — negligible. Drop oldest-arbitrary on
        // overflow (no LRU, hash ordering is fine for our access pattern).
        if g.len() >= 1024 {
            if let Some(k) = g.keys().next().cloned() {
                g.remove(&k);
            }
        }
        g.insert(key, hex.clone());
    }
    Ok(hex)
}

/// SHA-256 of an in-memory string, hex-encoded (64 lowercase chars).
///
/// Pre-0.7.44 wrote `text` to a tempfile and shelled out to `shasum`
/// — multiple ms even for tiny strings due to fork+exec overhead.
/// In-process digest is essentially free for the sub-KiB strings we
/// pass here (cache keys, manifest hashes).
fn sha256_text(text: &str) -> Result<String, String> {
    use ring::digest::{digest, SHA256};
    let d = digest(&SHA256, text.as_bytes());
    let mut hex = String::with_capacity(64);
    for b in d.as_ref() {
        use std::fmt::Write;
        let _ = write!(hex, "{:02x}", b);
    }
    Ok(hex)
}

/// Generate a fresh per-snapshot TSI control-channel auth token.
/// Returns 64 lowercase hex chars (32 random bytes from
/// `ring::rand::SystemRandom`, which on macOS pulls from
/// /dev/urandom via getentropy(2) — same source the kernel uses
/// for its own crypto). Called once at fresh bake; the result is
/// (a) appended to the booting kernel's `--cmdline` so the af_tsi
/// driver captures it in module BSS before snapshot, and
/// (b) persisted in metadata.json so every restore re-injects the
/// same value (the snapshot's captured BSS already has the bytes;
/// restoring with a different token would break the muxer check).
/// See `kernel-build/patches/af-tsi/0014-*.patch` for context.
fn generate_tsi_token_hex() -> Result<String, String> {
    use ring::rand::SecureRandom;
    let rng = ring::rand::SystemRandom::new();
    let mut buf = [0u8; 32];
    rng.fill(&mut buf)
        .map_err(|_| "ring::rand::SystemRandom::fill failed".to_owned())?;
    let mut out = String::with_capacity(64);
    for b in &buf {
        use std::fmt::Write;
        let _ = write!(out, "{:02x}", b);
    }
    Ok(out)
}

fn file_mode_octal(path: &Path) -> Result<String, String> {
    let mode = std::fs::metadata(path)
        .map_err(|e| format!("stat {}: {e}", path.display()))?
        .permissions()
        .mode()
        & 0o7777;
    Ok(format!("{mode:o}"))
}

fn file_size_mtime(path: &Path) -> Result<String, String> {
    let meta = std::fs::metadata(path).map_err(|e| format!("stat {}: {e}", path.display()))?;
    let mtime = meta
        .modified()
        .map_err(|e| format!("mtime {}: {e}", path.display()))?
        .duration_since(UNIX_EPOCH)
        .map_err(|e| format!("mtime before epoch {}: {e}", path.display()))?
        .as_secs();
    Ok(format!("{}:{mtime}", meta.len()))
}

fn set_mode(path: &Path, mode: u32) -> Result<(), String> {
    let mut permissions = std::fs::metadata(path)
        .map_err(|e| format!("stat {}: {e}", path.display()))?
        .permissions();
    permissions.set_mode(mode);
    std::fs::set_permissions(path, permissions)
        .map_err(|e| format!("chmod {:o} {}: {e}", mode, path.display()))
}

fn write_lines(path: &Path, lines: &[String]) -> Result<(), String> {
    let mut text = String::new();
    for line in lines {
        text.push_str(line);
        text.push('\n');
    }
    std::fs::write(path, text).map_err(|e| format!("write {}: {e}", path.display()))
}

/// Kernel-boot cache directory. The cache file for a given
/// (kernel, init-oci, memory_mib) tuple lives here as a single
/// `.snap` file (the snapshot file the worker can `--restore-from`).
///
/// Layout:
///   ~/.local/share/supermachine/kernel-boot-cache/
///     <key>_mib<memory_mib>.snap        — the cache snapshot
///     <key>_mib<memory_mib>.cache-key   — text file: full inputs
///                                          recorded for debugging
///
/// Override via `SUPERMACHINE_KERNEL_BOOT_CACHE_DIR` env var.
fn kernel_boot_cache_dir() -> PathBuf {
    if let Some(d) = std::env::var_os("SUPERMACHINE_KERNEL_BOOT_CACHE_DIR") {
        return PathBuf::from(d);
    }
    let home = std::env::var_os("HOME")
        .map(PathBuf::from)
        .unwrap_or_else(|| PathBuf::from("."));
    home.join(".local/share/supermachine/kernel-boot-cache")
}

/// Compute the cache key for a given bake plan. The key is a
/// SHA-256 of the size+mtime of every input that affects the
/// post-boot pre-pivot guest state:
///
///   * kernel binary (kernel.xz contents → kernel image)
///   * init-oci binary (the userspace pid-1 binary)
///   * supermachine-worker binary fingerprint (since it controls
///     virtio devices visible to the guest at boot)
///   * memory_mib (different RAM size = different kernel memory
///     layout = incompatible snapshot)
///   * vcpus (per-vCPU register state is captured)
///
/// Notes:
///   * Layer file is NOT part of the key. The cache point is
///     pre-`try_pivot_to_overlay()`, before the guest has read
///     /dev/vda. Different bakes can swap in different layer
///     files on restore.
///   * Image ref / cmd / env / mounts are NOT part of the key
///     for the same reason — they only affect post-pivot
///     behaviour. The cache snapshot is image-agnostic.
///
/// Returns: hex-encoded SHA-256 (64 chars) — the cache-key
/// component of the filename.
pub(crate) fn kernel_boot_cache_key(plan: &BakePlan<'_>, root: &Path) -> Result<String, String> {
    let kernel = supermachine_kernel(root);
    let init_oci = native_init_oci_fingerprint(root)?;
    let busybox = native_busybox_fingerprint()?;
    let worker_bin = supermachine_worker_bin(root);
    let agent = ensure_supermachine_agent(root)?;
    let parts = format!(
        "kernel={}\ninit_oci={}\nbusybox={}\nworker_bin={}\nagent={}\nmemory_mib={}\nvcpus={}\n",
        file_size_mtime(&kernel)?,
        init_oci,
        busybox,
        file_size_mtime(&worker_bin)?,
        file_size_mtime(&agent)?,
        plan.memory_mib,
        plan.vcpus,
    );
    sha256_text(&parts)
}

/// Full path to the kernel-boot cache snapshot file for the given
/// bake plan. Caller checks `is_file()` to detect cache hit.
pub(crate) fn kernel_boot_cache_snap_path(
    plan: &BakePlan<'_>,
    root: &Path,
) -> Result<PathBuf, String> {
    let key = kernel_boot_cache_key(plan, root)?;
    let dir = kernel_boot_cache_dir();
    Ok(dir.join(format!("{key}_mib{}.snap", plan.memory_mib)))
}

/// Companion debug file recording the full inputs that produced
/// the cache key. Written alongside the .snap so a human can
/// diff cache misses to figure out what input changed.
#[allow(dead_code)] // wired by the cache-write path (next commit)
pub(crate) fn kernel_boot_cache_key_path(
    plan: &BakePlan<'_>,
    root: &Path,
) -> Result<PathBuf, String> {
    let key = kernel_boot_cache_key(plan, root)?;
    let dir = kernel_boot_cache_dir();
    Ok(dir.join(format!("{key}_mib{}.cache-key", plan.memory_mib)))
}

#[cfg_attr(all(target_os = "linux", target_arch = "x86_64"), allow(dead_code))]
fn ensure_init_oci(root: &Path) -> Result<PathBuf, String> {
    // Resolve the oci/ dir, in order:
    //   - dev-tree:        crates/supermachine/oci/
    //   - installed-tree:  share/supermachine/oci/  (from release tarball)
    //   - legacy dev-tree: crates/supermachine/oci/
    let dir = [
        root.join("crates/supermachine/oci"),
        root.join("share/supermachine/oci"),
        root.join("crates/supermachine/oci"),
    ]
    .into_iter()
    .find(|p| p.is_dir())
    .unwrap_or_else(|| root.join("crates/supermachine/oci"));
    let src = dir.join("init-oci.c");
    let bin = dir.join("init-oci");
    // Fast path: dev-tree or release-tarball already has the binary.
    if bin.is_file() {
        let executable = std::fs::metadata(&bin)
            .ok()
            .map(|m| m.permissions().mode() & 0o111 != 0)
            .unwrap_or(false);
        let src_meta = std::fs::metadata(&src).ok();
        let up_to_date = match src_meta {
            Some(s) => std::fs::metadata(&bin)
                .ok()
                .and_then(|b| Some((b.modified().ok()?, s.modified().ok()?)))
                .map(|(b, s)| b >= s)
                .unwrap_or(false),
            None => true, // No source file means we trust whatever binary is here
        };
        if executable && up_to_date {
            return Ok(bin);
        }
    }
    // Prefer the bundled binary when the dev-tree binary is absent or stale.
    // CI/source checkouts usually have init-oci.c but not zig; the bundled
    // asset is the release-grade path. Maintainers editing init-oci can force
    // a source rebuild with SUPERMACHINE_REBUILD_INIT_OCI=1.
    if std::env::var_os("SUPERMACHINE_REBUILD_INIT_OCI").is_none() {
        let assets = crate::assets::AssetPaths::discover();
        if let Some(p) = assets.init_oci_bin {
            return Ok(p);
        }
    }
    // Last resort: compile from source via zig (dev-tree workflow
    // when a maintainer is editing init-oci.c).
    if src.is_file() {
        let mut cmd = Command::new("zig");
        cmd.current_dir(&dir)
            .arg("cc")
            .arg("--target=aarch64-linux-musl")
            .arg("-static")
            .arg("-O2")
            .arg("-o")
            .arg("init-oci")
            .arg("init-oci.c")
            .stdout(Stdio::null())
            .stderr(Stdio::null());
        run_status(cmd, "build init-oci")?;
        if bin.is_file() {
            return Ok(bin);
        }
    }
    Err(format!(
        "missing init-oci: not found in dev tree ({}), bundled assets, or via zig build",
        bin.display()
    ))
}

fn native_init_oci_fingerprint(root: &Path) -> Result<String, String> {
    #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
    {
        let _ = root;
        Ok("none".to_owned())
    }

    #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))]
    {
        let init_oci = ensure_init_oci(root)?;
        file_size_mtime(&init_oci)
    }
}

fn native_busybox_fingerprint() -> Result<String, String> {
    #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
    {
        sha256_bytes(supermachine_kernel::BUSYBOX_BYTES)
    }

    #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))]
    {
        Ok("none".to_owned())
    }
}

/// Locate (and build, if missing) the `supermachine-agent` binary
/// that ships inside the guest. Mirrors `ensure_init_oci`'s shape:
///
///   - dev-tree:        `crates/supermachine-guest-agent/target/aarch64-unknown-linux-musl/release/supermachine-agent`
///   - installed-tree:  `share/supermachine/supermachine-agent`
///
/// On dev hosts, builds via `cargo build --release` from the agent
/// crate dir. The crate's `.cargo/config.toml` already pins the
/// musl target and the zig-cc linker shim; we just need PATH to
/// include the crate dir so the shim resolves.
fn ensure_supermachine_agent(root: &Path) -> Result<PathBuf, String> {
    // Installed-tree fast path: tarball ships the prebuilt binary.
    let installed = root.join("share/supermachine/supermachine-agent");
    if installed.is_file() {
        return Ok(installed);
    }

    // The guest agent must match the GUEST arch, which for a same-arch
    // hypervisor (HVF aarch64 / KVM x86_64) is the host arch. The crate's
    // `.cargo/config.toml` defaults to aarch64-musl (the Mac dev target); on an
    // x86_64 host that default builds the wrong arch AND trips the aarch64
    // cross-linker shim (exit 101). Pick the musl triple for THIS host.
    let musl_target = match std::env::consts::ARCH {
        "aarch64" => "aarch64-unknown-linux-musl",
        "x86_64" => "x86_64-unknown-linux-musl",
        other => return Err(format!("supermachine-agent: unsupported host arch {other}")),
    };

    // Dev-tree fast path: the agent crate's prebuilt host-arch musl binary.
    let crate_dir = root.join("crates/supermachine-guest-agent");
    let dev_bin = crate_dir.join(format!("target/{musl_target}/release/supermachine-agent"));
    if dev_bin.is_file() {
        // If we have source, only use the binary if it's newer.
        let src = crate_dir.join("src/main.rs");
        let up_to_date = match (
            std::fs::metadata(&dev_bin).ok(),
            std::fs::metadata(&src).ok(),
        ) {
            (Some(b), Some(s)) => b.modified().ok() >= s.modified().ok(),
            (Some(_), None) => true,
            _ => false,
        };
        if up_to_date {
            return Ok(dev_bin);
        }
    }

    // Prefer the bundled agent when the dev-tree binary is absent or stale.
    // Source checkouts often do not have the musl cross toolchain available;
    // maintainers editing the agent can force a source rebuild explicitly.
    if std::env::var_os("SUPERMACHINE_REBUILD_GUEST_AGENT").is_none() {
        let assets = crate::assets::AssetPaths::discover();
        if let Some(p) = assets.supermachine_agent {
            return Ok(p);
        }
    }
    if !crate_dir.is_dir() {
        return Err(format!(
            "supermachine-agent: not found at {} (release tarball), \
             {} (dev tree), or in the bundled assets from supermachine-kernel",
            installed.display(),
            dev_bin.display()
        ));
    }

    // Last resort: rebuild the dev-tree binary via cargo. The
    // crate's .cargo/config.toml pins the musl target and the
    // linker shim; PATH must include the shim's location.
    let path = std::env::var("PATH").unwrap_or_default();
    let new_path = format!("{}:{}", crate_dir.display(), path);
    let mut cmd = Command::new("cargo");
    cmd.current_dir(&crate_dir)
        .arg("build")
        .arg("--release")
        // Explicit target overrides the crate's aarch64-pinned default so the
        // agent is built for THIS host's arch (and uses that target's linker
        // config, not the aarch64 cross-linker shim).
        .arg(format!("--target={musl_target}"))
        .env("PATH", new_path)
        .stdout(Stdio::null())
        .stderr(Stdio::piped());
    run_status(cmd, "build supermachine-agent")?;
    if !dev_bin.is_file() {
        return Err(format!(
            "supermachine-agent build did not produce {}",
            dev_bin.display()
        ));
    }
    Ok(dev_bin)
}

#[cfg_attr(all(target_os = "linux", target_arch = "x86_64"), allow(dead_code))]
fn build_initramfs(root: &Path, out_dir: &Path) -> Result<(PathBuf, u128), String> {
    let t0 = Instant::now();
    let init = ensure_init_oci(root)?;
    // The cpio cache lives next to init-oci itself, which already
    // resolves to either the new (crates/supermachine/oci) or
    // legacy (crates/supermachine/oci) location.
    let init_dir = init
        .parent()
        .map(|p| p.to_path_buf())
        .unwrap_or_else(|| root.join("crates/supermachine/oci"));
    let cache = init_dir.join("init-oci.cpio.gz");
    // smpark.ko — optional in-guest kernel module that drives
    // secondary vCPUs into a known parked-WFI state with empty
    // LRs before snapshot capture. Required for multi-vCPU
    // (vcpus > 1) reliability: HVF's snapshot/restore can't
    // round-trip ICH_LR_EL2 cleanly, so we have to make sure the
    // captured state has empty LRs. Single-vCPU bakes ignore the
    // module (init-oci loads it but it no-ops, host never RPCs
    // park/unpark). If the file isn't present, init-oci silently
    // skips loading and multi-vCPU stays on the existing partial-
    // workaround path. See docs/design/multi-vcpu-cooperative-park-
    // 2026-05-08.md and docs/design/extras/smpark/.
    // Prefer the dev-tree copy in `init_dir/` (which build.rs glues
    // onto the working tree), but fall back to the bundled-extracted
    // copy that `AssetPaths::discover` writes under
    // `$XDG_DATA_HOME/supermachine/v{VERSION}/supermachine-smpark.ko`
    // when supermachine ships via npm/crates.io (no init-oci dir to
    // colocate with).
    let smpark_ko_src = {
        let dev_tree = init_dir.join("supermachine-smpark.ko");
        if dev_tree.is_file() {
            dev_tree
        } else {
            crate::assets::AssetPaths::discover()
                .smpark_ko
                .unwrap_or(dev_tree)
        }
    };
    let smpark_present = smpark_ko_src.is_file();
    let rebuild = match (std::fs::metadata(&cache), std::fs::metadata(&init)) {
        (Ok(cache_meta), Ok(init_meta)) => {
            // 0.7.44+ ALWAYS rebuild if the cache is zero bytes.
            // This catches stale-from-an-earlier-race 0-byte caches
            // that pre-date the atomic-rename fix below; without
            // the size check, the mtime-only freshness logic would
            // serve the empty file forever (it's "newer than init"
            // by accident of being touched recently), and the
            // hardlink in the next block would propagate the empty
            // bytes into every new bake's init.cpio.gz → kernel
            // boots with empty initramfs → `VFS: Unable to mount
            // root fs on unknown-block(0,0)` panic.
            if cache_meta.len() == 0 {
                true
            } else {
                let init_newer = cache_meta.modified().ok() < init_meta.modified().ok();
                // Also rebuild if smpark.ko has been updated since the
                // last cpio bake — its mtime invalidates the cache the
                // same way init-oci's does. Without this check, a fresh
                // smpark.ko sitting next to a stale init-oci.cpio.gz
                // wouldn't propagate into the initramfs.
                let smpark_newer = if smpark_present {
                    std::fs::metadata(&smpark_ko_src)
                        .ok()
                        .and_then(|m| m.modified().ok())
                        .map(|smt| {
                            cache_meta
                                .modified()
                                .ok()
                                .map(|ct| ct < smt)
                                .unwrap_or(true)
                        })
                        .unwrap_or(false)
                } else {
                    false
                };
                init_newer || smpark_newer
            }
        }
        _ => true,
    };
    if rebuild {
        let stage = temp_work_dir("supermachine-initramfs-stage")?;
        // 0.7.44+ atomic cache write. The cache path
        // (init_dir/init-oci.cpio.gz) is shared across ALL bakes on
        // this host — concurrent bakes that all need to rebuild
        // would otherwise race on the `cpio > $cache` redirect, and
        // one of them could end up reading a partial / zero-byte
        // file when the subsequent hardlink (line ~4336) fires. The
        // result is an empty initramfs → kernel boots → can't find
        // root → `VFS: Unable to mount root fs on unknown-block(0,0)`.
        // Write to a per-process tmp file, then atomic-rename to
        // the canonical cache path. Last writer wins under
        // contention; neither emits a partial file.
        let tmp_seq = TEMP_COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
        let cache_tmp = init_dir.join(format!(
            ".init-oci.cpio.gz.{}.{}.tmp",
            std::process::id(),
            tmp_seq
        ));
        let _ = std::fs::remove_file(&cache_tmp);
        let result: Result<(), String> = (|| {
            std::fs::copy(&init, stage.join("init"))
                .map_err(|e| format!("copy init into initramfs stage: {e}"))?;
            set_mode(&stage.join("init"), 0o755)?;
            for dir in ["proc", "sys", "dev", "newroot"] {
                std::fs::create_dir_all(stage.join(dir))
                    .map_err(|e| format!("create initramfs dir {dir}: {e}"))?;
            }
            // Stage smpark.ko at the path init-oci looks for it
            // (`/supermachine-smpark.ko` post-pivot, i.e. at the
            // initramfs root pre-pivot). 644 perms are fine —
            // finit_module reads the fd, doesn't exec the file.
            if smpark_present {
                std::fs::copy(&smpark_ko_src, stage.join("supermachine-smpark.ko")).map_err(
                    |e| format!("copy supermachine-smpark.ko into initramfs stage: {e}"),
                )?;
                set_mode(&stage.join("supermachine-smpark.ko"), 0o644)?;
            }
            let script =
                "cd \"$1\" && find . -mindepth 1 -print | sed 's|^\\./||' | cpio -o -H newc 2>/dev/null | gzip > \"$2\"";
            let mut cmd = Command::new("sh");
            cmd.arg("-c")
                .arg(script)
                .arg("sh")
                .arg(&stage)
                .arg(&cache_tmp)
                .stdout(Stdio::null());
            run_status(cmd, "build initramfs cpio")?;
            // Atomic-rename into the shared cache path. Two
            // concurrent bakes racing here both atomically commit
            // their own complete cpio output; the second
            // rename(2) replaces the first's bytes (which are
            // identical anyway — same input → same cpio).
            std::fs::rename(&cache_tmp, &cache).map_err(|e| {
                let _ = std::fs::remove_file(&cache_tmp);
                format!(
                    "rename initramfs cpio {} -> {}: {e}",
                    cache_tmp.display(),
                    cache.display()
                )
            })?;
            Ok(())
        })();
        let _ = std::fs::remove_dir_all(&stage);
        let _ = std::fs::remove_file(&cache_tmp);
        result?;
    }
    let out = out_dir.join("init.cpio.gz");
    // 0.7.44+ hardlink instead of copy. The cache file is
    // content-addressable (path includes a hash) and the worker
    // reads `out` once at boot then doesn't mutate it. Hardlinking
    // is O(metadata) on APFS; copy is O(file_size) — ~200-400 KiB
    // here, ~3-10 ms saved per novel bake. Fall back to copy if
    // hardlink fails (different filesystem, permission, etc.).
    let _ = std::fs::remove_file(&out);
    if std::fs::hard_link(&cache, &out).is_err() {
        std::fs::copy(&cache, &out).map_err(|e| {
            format!(
                "copy initramfs cache {} -> {}: {e}",
                cache.display(),
                out.display()
            )
        })?;
    }
    Ok((out, elapsed_ms(t0)))
}

fn native_initramfs(root: &Path, out_dir: &Path) -> Result<(PathBuf, u128), String> {
    #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
    {
        let t0 = Instant::now();
        let assets = out_dir.join("agent-assets");
        std::fs::create_dir_all(&assets)
            .map_err(|e| format!("create KVM initramfs assets {}: {e}", assets.display()))?;
        let agent_bin = ensure_supermachine_agent(root)?;
        let busybox_bin = assets.join("busybox");
        supermachine_kernel::extract_busybox_to(&busybox_bin)
            .map_err(|e| format!("extract bundled busybox {}: {e}", busybox_bin.display()))?;
        let init_cpio = out_dir.join("agent.cpio");
        build_kvm_agent_initramfs(&agent_bin, &busybox_bin, &[], &init_cpio)?;
        let _ = std::fs::remove_dir_all(&assets);
        Ok((init_cpio, elapsed_ms(t0)))
    }

    #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))]
    {
        build_initramfs(root, out_dir)
    }
}

fn write_env_json(
    plan: &BakePlan<'_>,
    resolution: &ImageResolution,
    out_dir: &Path,
) -> Result<PathBuf, String> {
    let json = env_json_value(plan, resolution);
    let path = out_dir.join("env.json");
    std::fs::write(
        &path,
        serde_json::to_vec_pretty(&json).map_err(|e| format!("encode env.json: {e}"))?,
    )
    .map_err(|e| format!("write {}: {e}", path.display()))?;
    Ok(path)
}

fn env_json_value(plan: &BakePlan<'_>, resolution: &ImageResolution) -> serde_json::Value {
    let mut env = serde_json::Map::new();
    for line in &resolution.env {
        if let Some((k, v)) = line.split_once('=') {
            env.insert(k.to_owned(), serde_json::Value::String(v.to_owned()));
        }
    }
    for line in arg_values(plan.extra_args, "--env") {
        if let Some((k, v)) = line.split_once('=') {
            env.insert(k.to_owned(), serde_json::Value::String(v.to_owned()));
        }
    }
    serde_json::json!({ "env": env, "secrets": {} })
}

#[cfg_attr(
    not(all(target_os = "linux", target_arch = "x86_64")),
    allow(dead_code)
)]
fn env_pairs_for_workload_script(
    plan: &BakePlan<'_>,
    resolution: &ImageResolution,
) -> Vec<(String, String)> {
    let mut env = std::collections::BTreeMap::new();
    for line in &resolution.env {
        if let Some((k, v)) = line.split_once('=') {
            env.insert(k.to_owned(), v.to_owned());
        }
    }
    for line in arg_values(plan.extra_args, "--env") {
        if let Some((k, v)) = line.split_once('=') {
            env.insert(k.to_owned(), v.to_owned());
        }
    }
    env.into_iter().collect()
}

fn read_to_string_lossy(path: &Path) -> String {
    std::fs::read(path)
        .map(|bytes| String::from_utf8_lossy(&bytes).into_owned())
        .unwrap_or_default()
}

/// Add `-processors N` to `cmd` if SUPERMACHINE_MKSQUASHFS_PROCESSORS
/// is set. Default behavior (env unset) lets mksquashfs use all
/// available CPUs, which is what a single foreground bake wants.
///
/// Use case: concurrent test runs (vitest at FORKS=10) fire ~10
/// bakes in parallel, each fanning out to N layers (parallel-
/// mksquashfs after 0.7.44's T5). With every mksquashfs default to
/// all cores, host CPU is oversubscribed N × forks × cores. Setting
/// the env to a small value (e.g. ceil(host_cores / forks) ≈ 2-4)
/// keeps cumulative parallelism under control.
///
/// Single-bake users don't set it → no change.
/// Per-path tail-tracker: keeps a per-process buffer of bytes seen
/// so far, plus the file offset reached on the last read. Each call
/// reads only the NEW bytes (file_size − last_offset) into the
/// buffer and scans the buffer.
///
/// Why: the pre-0.7.44 `log_has` re-read the entire `bake.log` on
/// every call. The bake-time polling loop hits it 5 times per 50 ms
/// iteration; on slow bakes the log grows past 100 KiB. That's
/// up to ~10 MiB of redundant reads per bake (~5-30 ms cumulative
/// on M-series).
///
/// Bounded growth: if the file is truncated (rotation, manual `rm`)
/// the buffer detects size-shrink and resets. The buffer caps at
/// 1 MiB per path; older bytes are dropped on overflow so a runaway
/// log doesn't pin a permanent ~unbounded allocation.
///
/// Cache scope is process-wide — a single bake's log is the same
/// path across all callers in this module, so they share the buffer.
fn log_tail_get_text(path: &Path) -> String {
    use std::collections::HashMap;
    use std::io::{Read, Seek, SeekFrom};
    use std::sync::Mutex;
    static TAILS: OnceLock<Mutex<HashMap<PathBuf, (u64, String)>>> = OnceLock::new();
    const MAX_BUF: usize = 1024 * 1024;

    let canon = path.to_path_buf();
    let tails = TAILS.get_or_init(|| Mutex::new(HashMap::new()));
    let mut g = match tails.lock() {
        Ok(g) => g,
        Err(_) => return read_to_string_lossy(path),
    };
    // Cap the total entry count so a long-lived embedder running
    // many bakes doesn't accumulate unbounded paths. Each bake uses
    // a unique log path (timestamp-suffixed), so without this cap
    // every bake leaks ~hundreds of KiB. Drop a single old entry
    // (hash-iteration order; not LRU but bounded).
    const MAX_ENTRIES: usize = 64;
    if g.len() >= MAX_ENTRIES && !g.contains_key(&canon) {
        if let Some(k) = g.keys().next().cloned() {
            g.remove(&k);
        }
    }
    let entry = g.entry(canon).or_insert_with(|| (0u64, String::new()));

    let cur_len = match std::fs::metadata(path) {
        Ok(m) => m.len(),
        Err(_) => return entry.1.clone(),
    };
    if cur_len < entry.0 {
        // Truncated (or replaced) — restart.
        entry.0 = 0;
        entry.1.clear();
    }
    if cur_len > entry.0 {
        match std::fs::File::open(path) {
            Ok(mut f) => {
                if f.seek(SeekFrom::Start(entry.0)).is_ok() {
                    let mut new_bytes: Vec<u8> = Vec::new();
                    if f.read_to_end(&mut new_bytes).is_ok() {
                        entry.0 = cur_len;
                        entry.1.push_str(&String::from_utf8_lossy(&new_bytes));
                        if entry.1.len() > MAX_BUF {
                            // Drop the oldest half — needles we care
                            // about land in the recent tail, and
                            // detect_kernel_panic / log_has_failure
                            // re-scan the kept window each call.
                            let drop_to = entry.1.len() - MAX_BUF / 2;
                            entry.1 = entry.1.split_off(drop_to);
                        }
                    }
                }
            }
            Err(_) => return entry.1.clone(),
        }
    }
    entry.1.clone()
}

fn log_has(path: &Path, needle: &str) -> bool {
    log_tail_get_text(path).contains(needle)
}

fn log_has_failure(path: &Path) -> bool {
    let text = log_tail_get_text(path);
    text.lines().any(|line| {
        line.starts_with("FATAL:")
            || line.starts_with("error:")
            || line.contains("panic")
            || line.contains("snapshot") && line.contains("ERR")
    })
}

/// Kernel-panic banners we watch for on the worker's serial console
/// during a bake. Any match is treated as a definitive fatal —
/// killing the worker child immediately rather than waiting out
/// the bake's 240s deadline.
///
/// Kept narrow: these are the strings the upstream arm64 kernel
/// emits on the four panic flavors we've actually seen during bake
/// development (NULL deref in init, BSS-write protection fault,
/// idle-task kill, generic `panic()` call). Adding more is safe;
/// they're matched as substrings.
const KERNEL_PANIC_PATTERNS: &[&str] = &[
    "Kernel panic",
    "Unable to handle kernel NULL pointer dereference",
    "Unable to handle kernel paging request",
    "Internal error: Oops",
    "Attempted to kill the idle task",
];

/// Returns `Some((first_line, stack_lines))` if `path`'s contents
/// contain any kernel-panic banner. `stack_lines` is up to
/// [`PANIC_STACK_LINES`] lines following the first matched banner —
/// usually the Call-Trace block printk'd by the arm64 panic handler.
///
/// Pure function over file contents; safe to call concurrently from
/// the bake's watcher thread and the polling loop.
fn detect_kernel_panic(path: &Path) -> Option<(String, Vec<String>)> {
    let text = log_tail_get_text(path);
    let lines: Vec<&str> = text.lines().collect();
    for (idx, line) in lines.iter().enumerate() {
        if KERNEL_PANIC_PATTERNS.iter().any(|p| line.contains(p)) {
            let first_line = (*line).to_owned();
            let stack: Vec<String> = lines
                .iter()
                .skip(idx + 1)
                .take(PANIC_STACK_LINES)
                .map(|s| (*s).to_owned())
                .collect();
            return Some((first_line, stack));
        }
    }
    None
}

const PANIC_STACK_LINES: usize = 40;

/// Encode a detected kernel panic into the sentinel string format
/// that `api.rs::map_bake_error` decodes into `Error::KernelPanic`.
/// Keeping the inter-module hand-off as a single error string keeps
/// the call sites simple — every existing `Err(format!(...))` path
/// flows through `map_bake_error`, so no signature changes.
fn encode_kernel_panic_err(first_line: &str, stack: &[String]) -> String {
    // `|` separates first_line from stack; `\x1F` (ASCII unit
    // separator) separates stack lines. Both are absent from
    // typical kernel printks.
    let stack_joined = stack.join("\x1F");
    format!("KERNEL_PANIC|{first_line}|{stack_joined}")
}

fn last_line_containing(path: &Path, needle: &str) -> Option<String> {
    log_tail_get_text(path)
        .lines()
        .filter(|line| line.contains(needle))
        .last()
        .map(ToOwned::to_owned)
}

fn parse_snapshot_reason(line: &str) -> Option<String> {
    let start = line.find("snapshot (")? + "snapshot (".len();
    let end = line[start..].find("):")? + start;
    Some(line[start..end].to_owned())
}

fn parse_capture_save_us(line: &str) -> (Option<u64>, Option<u64>) {
    let capture = line
        .split("capture ")
        .nth(1)
        .and_then(|s| s.split(" us").next())
        .and_then(|s| s.trim().parse().ok());
    let save = line
        .split("save ")
        .nth(1)
        .and_then(|s| s.split(" us").next())
        .and_then(|s| s.trim().parse().ok());
    (capture, save)
}

fn parse_ram_mib(line: &str) -> (Option<u64>, Option<u64>) {
    let Some(after_data) = line.split("data ").nth(1) else {
        return (None, None);
    };
    let data = after_data
        .split(" MiB")
        .next()
        .and_then(|s| s.trim().parse().ok());
    let zero = line
        .split("zero ")
        .nth(1)
        .and_then(|s| s.split(" MiB").next())
        .and_then(|s| s.trim().parse().ok());
    (data, zero)
}

fn file_physical_bytes(path: &Path) -> Option<u64> {
    let mut cmd = Command::new("du");
    cmd.arg("-sk").arg(path);
    command_output(cmd, "du -sk")
        .ok()
        .and_then(|out| {
            out.split_whitespace()
                .next()
                .and_then(|s| s.parse::<u64>().ok())
        })
        .map(|kib| kib * 1024)
}

fn now_utc_iso() -> Result<String, String> {
    let mut cmd = Command::new("date");
    cmd.arg("-u").arg("+%Y-%m-%dT%H:%M:%SZ");
    Ok(command_output(cmd, "date utc")?.trim().to_owned())
}

fn native_listener_settle_ms(plan: &BakePlan<'_>) -> u64 {
    arg_value(plan.extra_args, "--supermachine-listener-settle-ms")
        .and_then(|s| s.parse::<u64>().ok())
        .or_else(|| {
            std::env::var("SUPERMACHINE_LISTENER_SETTLE_MS")
                .ok()
                .and_then(|s| s.parse::<u64>().ok())
        })
        .unwrap_or(50)
}

/// Compute the virtio-balloon target (in 4 KB pages) for a guest of
/// `memory_mib` megabytes.
///
/// The original formula reclaimed 70% of guest memory unconditionally.
/// That works for the typical 256 MiB+ guest (leaves ~77 MiB free,
/// enough for the agent's accept loop + a fork+exec workload). For
/// 128 MiB guests the same 70% reclaim leaves only ~39 MiB free; the
/// kernel's OOM killer then fires inside the agent's `fork()` for
/// the first exec request, and the host sees "agent closed connection
/// before sending EXIT". Restored workers manifest this bug because
/// they re-apply the bake's recorded balloon target on every spawn,
/// while the warm-handoff worker (which never restored) never had
/// its memory reclaimed.
///
/// Fix: keep at least `MIN_FREE_MIB` of guest memory unclaimed, so
/// fork+exec has room regardless of the configured guest size. The
/// 70% cap still applies for larger guests where the safety floor
/// is already satisfied.
const MIN_FREE_MIB_AFTER_BALLOON: u64 = 96;
const PAGES_PER_MIB: u64 = 256;
pub(crate) fn compute_balloon_target_pages(memory_mib: u32) -> u64 {
    let m = memory_mib as u64;
    let cap_70pct = m * PAGES_PER_MIB * 70 / 100;
    let floor_keep = MIN_FREE_MIB_AFTER_BALLOON * PAGES_PER_MIB;
    let max_reclaim = m
        .saturating_sub(MIN_FREE_MIB_AFTER_BALLOON)
        .saturating_mul(PAGES_PER_MIB);
    // Reclaim min(70% of total, total - MIN_FREE_MIB).
    // For tiny guests (memory_mib < MIN_FREE_MIB), max_reclaim is 0
    // → no ballooning at all. That's the right behavior; ballooning
    // a guest smaller than the safety floor would always OOM.
    let _ = floor_keep;
    cap_70pct.min(max_reclaim)
}

/// Build a single KVM-bootable rootfs squashfs from OCI layer blobs.
///
/// The host side of the KVM bake: each layer (`tar`, `tar.gz`, or `tar.zst`) is
/// extracted bottom-up into `work_root`, OCI whiteouts are applied, and the
/// merged tree is written as a root:root zstd squashfs at `out_squashfs`. The
/// product attaches this image as `/dev/vda`; the guest mounts it as its
/// container rootfs.
///
/// This reuses the same extractor + squashfs writer as the HVF bake (so the
/// supply-chain hardening — size caps, tar-slip confinement — applies here too).
/// The remaining guest side of the KVM bake is a self-contained agent initramfs
/// (the agent + busybox + virtio-vsock modules + an init that mounts `/dev/vda`
/// and execs into it), plus the OCI-source plumbing (registry pull / local
/// archive → the ordered `layer_blobs` passed here).
// Caller (`Image::bake_kvm`) is Linux/x86_64-gated; allow-dead elsewhere.
#[cfg_attr(
    not(all(target_os = "linux", target_arch = "x86_64")),
    allow(dead_code)
)]
pub(crate) fn build_kvm_rootfs_squashfs(
    layer_blobs: &[PathBuf],
    work_root: &Path,
    out_squashfs: &Path,
    workload_script: Option<&str>,
) -> Result<(), String> {
    let t_extract = std::time::Instant::now();
    stage_oci_rootfs(layer_blobs, work_root, workload_script)?;
    let extract_ms = t_extract.elapsed().as_millis();
    let t_squash = std::time::Instant::now();
    let r = squashfs::write_squashfs(work_root, out_squashfs, &squashfs::Ownership::AllRoot);
    if trace_enabled() {
        let sz = std::fs::metadata(out_squashfs)
            .map(|m| m.len())
            .unwrap_or(0);
        eprintln!(
            "supermachine: kvm rootfs build extract_ms={} squashfs_ms={} layers={} squashfs_bytes={}",
            extract_ms,
            t_squash.elapsed().as_millis(),
            layer_blobs.len(),
            sz,
        );
    }
    r
}

/// Write an already-staged merged KVM rootfs directory as a squashfs disk.
///
/// Used by the Linux/KVM `OciImageBuilder` path after it has applied builder
/// overrides that are not expressible as plain OCI layers (for example
/// `with_extra_file`). Keeps the final KVM disk format identical to
/// [`build_kvm_rootfs_squashfs`].
#[cfg_attr(
    not(all(target_os = "linux", target_arch = "x86_64")),
    allow(dead_code)
)]
pub(crate) fn write_kvm_rootfs_squashfs(
    work_root: &Path,
    out_squashfs: &Path,
) -> Result<(), String> {
    let t_squash = std::time::Instant::now();
    let r = squashfs::write_squashfs(work_root, out_squashfs, &squashfs::Ownership::AllRoot);
    if trace_enabled() {
        let sz = std::fs::metadata(out_squashfs)
            .map(|m| m.len())
            .unwrap_or(0);
        eprintln!(
            "supermachine: kvm rootfs squashfs_ms={} squashfs_bytes={}",
            t_squash.elapsed().as_millis(),
            sz,
        );
    }
    r
}

/// Stage OCI layer blobs into a merged rootfs DIRECTORY at `work_root`: each
/// layer (`tar`, `tar.gz`, or `tar.zst`) is extracted bottom-up, OCI whiteouts
/// are applied, and the OCI workload-launch script (when given) is baked at
/// `/.supermachine/run-workload`, mode 0755. Shared by the KVM bake (which
/// squashes the tree onto `/dev/vda`) and the sentry bake (which uses the tree
/// directly). The extractor's supply-chain hardening (size caps, tar-slip
/// confinement) applies to both.
fn stage_oci_rootfs(
    layer_blobs: &[PathBuf],
    work_root: &Path,
    workload_script: Option<&str>,
) -> Result<(), String> {
    std::fs::create_dir_all(work_root)
        .map_err(|e| format!("create rootfs dir {}: {e}", work_root.display()))?;
    // Bottom-up: lower layers first, upper layers overlay/replace them.
    for blob in layer_blobs {
        extract_layer_tar(blob, work_root)?;
    }
    remove_oci_whiteouts(work_root)?;
    #[cfg(unix)]
    apply_tmpfiles_dirs(work_root)?;
    if let Some(script) = workload_script {
        let sm_dir = work_root.join(".supermachine");
        std::fs::create_dir_all(&sm_dir)
            .map_err(|e| format!("create {} : {e}", sm_dir.display()))?;
        let run = sm_dir.join("run-workload");
        std::fs::write(&run, script.as_bytes())
            .map_err(|e| format!("write {}: {e}", run.display()))?;
        #[cfg(unix)]
        {
            use std::os::unix::fs::PermissionsExt as _;
            std::fs::set_permissions(&run, std::fs::Permissions::from_mode(0o755))
                .map_err(|e| format!("chmod {}: {e}", run.display()))?;
        }
    }
    Ok(())
}

/// Build the sentry backend's rootfs from OCI layer blobs.
///
/// The staged DIRECTORY at `rootfs_dir` *is* the artifact: the sentry has no
/// guest kernel — its per-tenant supervisor serves the guest's files straight
/// from this tree via `openat2(RESOLVE_IN_ROOT)` (see `crate::sentry`) — so
/// there is no squashfs/disk-image step and the tree must NOT be deleted after
/// the bake (unlike the KVM bake's `rootfs-stage`).
// Not yet reachable: the `api::Vm::start` backend-selection branch
// (`kvm_usable() → kvm, else sentry`) is the next integration step — see
// docs/design/no-virt-sentry-HANDOFF.md §5b.
#[allow(dead_code)]
pub(crate) fn build_sentry_rootfs(
    layer_blobs: &[PathBuf],
    rootfs_dir: &Path,
    workload_script: Option<&str>,
) -> Result<(), String> {
    stage_oci_rootfs(layer_blobs, rootfs_dir, workload_script)
}

/// Resolve an OCI image reference to its ordered layer-blob paths (bottom-up).
///
/// Registry-direct by default (no Docker daemon needed); honors
/// `SUPERMACHINE_IMAGE_SOURCE` and the `oci-layout:` / `oci-archive:` ref
/// prefixes, reusing the same puller the HVF bake uses. `work_dir` receives the
/// materialized OCI layout. The returned blobs feed
/// [`build_kvm_rootfs_squashfs`] for the KVM bake.
///
/// Also returns the image's workload-launch script (`Entrypoint`/`Cmd`/`Env`/
/// `WorkingDir` rendered into an `sh` script — see [`kvm_workload_script`]), or
/// `None` when the image declares no command. The config is read here, while
/// the OCI layout (a temp dir owned by `source` for the registry-direct path,
/// freed on `source` drop) is still alive.
#[cfg_attr(
    not(all(target_os = "linux", target_arch = "x86_64")),
    allow(dead_code)
)]
pub(crate) fn pull_oci_layers(
    image: &str,
    arch: &str,
    work_dir: &Path,
) -> Result<(Vec<PathBuf>, Option<String>, Option<serde_json::Value>), String> {
    std::fs::create_dir_all(work_dir)
        .map_err(|e| format!("create oci pull dir {}: {e}", work_dir.display()))?;
    let source = select_image_source(image, arch)?;
    source.pull(image, false)?;
    let layout = source.save(image, work_dir)?;
    // The image's runtime config (Env/Cmd/Entrypoint/WorkingDir/User) — the
    // KVM bake persists it into metadata.json so consumers that read the
    // config back from a cached snapshot (the in-VM builder's FROM-base env
    // inheritance, `supermachine images`, …) see it. Without this the KVM
    // bake recorded NO config and builder #7's inheritance silently read an
    // empty env on linux while working on HVF.
    let oci_config = oci::inspect_oci_layout(image, &layout, arch)
        .ok()
        .and_then(|info| info.get("Config").cloned());
    let workload = kvm_workload_script(image, &layout, arch)?;
    let shas = layer_shas_from_save_dir(image, &layout, arch)?;
    let blobs = shas
        .iter()
        .map(|sha| {
            // The registry-direct source streams blobs into the PERSISTENT
            // shared layer cache; its returned `layout` is a self-managed temp
            // dir that's deleted when `source` drops, so resolve the cache
            // first. OCI archive/layout sources put blobs under the (persistent)
            // layout's blobs/sha256 instead — fall back to that.
            let cached = registry_blob_cache_path(sha)?;
            if cached.is_file() {
                return Ok(cached);
            }
            let in_layout = blob_path(&layout, sha)?;
            if in_layout.is_file() {
                return Ok(in_layout);
            }
            Err(format!(
                "layer blob {sha} not found in cache ({}) or layout ({})",
                cached.display(),
                in_layout.display()
            ))
        })
        .collect::<Result<Vec<PathBuf>, String>>()?;
    Ok((blobs, workload, oci_config))
}

/// One entry for the in-process cpio builder: a regular file or a directory.
pub(crate) enum CpioEntry<'a> {
    /// `(path, mode_perms, contents)` — a regular file (S_IFREG | perms).
    File(&'a str, u32, &'a [u8]),
    /// `(path, mode_perms)` — a directory (S_IFDIR | perms).
    Dir(&'a str, u32),
}

/// Build an initramfs cpio archive (the kernel's `newc` format) entirely
/// in-process — no external `cpio` tool. Entries are emitted in order, so the
/// caller must list each parent directory before its children. A `TRAILER!!!`
/// record terminates the archive. The kernel's initramfs unpacker is lenient
/// about ino/nlink, so we use a monotonic ino and nlink 1 (files) / 2 (dirs).
///
/// This is the KVM counterpart to the HVF bake's `init-oci.cpio.gz`: it lets
/// the KVM bake produce a self-contained agent initramfs (the PID-1 agent + any
/// guest kernel modules) without shelling out.
#[cfg_attr(
    not(all(target_os = "linux", target_arch = "x86_64")),
    allow(dead_code)
)]
pub(crate) fn build_newc_cpio(entries: &[CpioEntry<'_>]) -> Vec<u8> {
    const S_IFREG: u32 = 0o100000;
    const S_IFDIR: u32 = 0o040000;
    let mut out = Vec::new();
    let mut ino: u32 = 1;

    fn field(out: &mut Vec<u8>, v: u32) {
        out.extend_from_slice(format!("{v:08x}").as_bytes());
    }
    fn pad4(out: &mut Vec<u8>) {
        while out.len() % 4 != 0 {
            out.push(0);
        }
    }
    let mut emit = |out: &mut Vec<u8>, name: &str, mode: u32, nlink: u32, data: &[u8]| {
        let namesize = name.len() as u32 + 1; // includes trailing NUL
        out.extend_from_slice(b"070701");
        field(out, ino); // c_ino
        field(out, mode); // c_mode
        field(out, 0); // c_uid (root)
        field(out, 0); // c_gid (root)
        field(out, nlink); // c_nlink
        field(out, 0); // c_mtime
        field(out, data.len() as u32); // c_filesize
        field(out, 0); // c_devmajor
        field(out, 0); // c_devminor
        field(out, 0); // c_rdevmajor
        field(out, 0); // c_rdevminor
        field(out, namesize); // c_namesize
        field(out, 0); // c_check (unused for newc)
        out.extend_from_slice(name.as_bytes());
        out.push(0);
        pad4(out); // pad name (header is 110 bytes, fixed)
        out.extend_from_slice(data);
        pad4(out); // pad data
        ino += 1;
    };

    for e in entries {
        match e {
            CpioEntry::File(path, perms, data) => emit(&mut out, path, S_IFREG | perms, 1, data),
            CpioEntry::Dir(path, perms) => emit(&mut out, path, S_IFDIR | perms, 2, &[]),
        }
    }
    // Trailer record (name "TRAILER!!!", no data).
    emit(&mut out, "TRAILER!!!", 0, 1, &[]);
    out
}

/// Assemble a self-contained KVM agent initramfs (newc cpio) in-process.
///
/// The PID-1 `/init` mounts `/proc` + `/dev`, `insmod`s the given guest kernel
/// modules in order, mounts the rootfs disk (`/dev/vda`, squashfs) at `/mnt`,
/// then execs `/supermachine-agent`. `.ko.zst` modules are decompressed. This
/// replaces the hand-built `agent.cpio` + external `cpio`/`busybox` build steps
/// — the KVM counterpart of the HVF bake's `init-oci.cpio.gz`. (A future
/// vsock-built-in x86 kernel drops the module list entirely.)
#[cfg_attr(
    not(all(target_os = "linux", target_arch = "x86_64")),
    allow(dead_code)
)]
pub(crate) fn build_kvm_agent_initramfs(
    agent_bin: &Path,
    busybox_bin: &Path,
    ordered_modules: &[PathBuf],
    out_cpio: &Path,
) -> Result<(), String> {
    use std::io::Read as _;
    let agent =
        std::fs::read(agent_bin).map_err(|e| format!("read agent {}: {e}", agent_bin.display()))?;
    let busybox = std::fs::read(busybox_bin)
        .map_err(|e| format!("read busybox {}: {e}", busybox_bin.display()))?;

    // (basename without .zst, decompressed bytes), in load order.
    let mut modules: Vec<(String, Vec<u8>)> = Vec::new();
    for m in ordered_modules {
        let raw = std::fs::read(m).map_err(|e| format!("read module {}: {e}", m.display()))?;
        let fname = m
            .file_name()
            .and_then(|s| s.to_str())
            .ok_or_else(|| format!("bad module name {}", m.display()))?;
        let (name, bytes) = match fname.strip_suffix(".zst") {
            Some(stripped) => {
                let mut dec = ruzstd::StreamingDecoder::new(std::io::Cursor::new(&raw))
                    .map_err(|e| format!("zstd init {}: {e}", m.display()))?;
                let mut out = Vec::new();
                dec.read_to_end(&mut out)
                    .map_err(|e| format!("zstd decode {}: {e}", m.display()))?;
                (stripped.to_string(), out)
            }
            None => (fname.to_string(), raw),
        };
        modules.push((name, bytes));
    }

    // The PID-1 init: bring up pseudo-fs, (optionally) load vsock modules, then
    // build a WRITABLE container rootfs as an overlay over the read-only OCI
    // squashfs (/dev/vda) — lowerdir=squashfs, upper+work=tmpfs — stage the
    // agent into it, and `pivot_root` in so the agent (and everything it execs)
    // runs with `/` = the OCI rootfs (real `docker exec` semantics, writable via
    // the tmpfs upper). This mirrors init-oci's phase-1 overlay+switch_root on
    // HVF. The kernel needs OVERLAY_FS + SQUASHFS (the supermachine x86 kernel
    // has them built-in; stock Ubuntu kernels do too).
    let mut init = String::from("#!/bin/busybox sh\n");
    init.push_str("BB=/bin/busybox\n");
    init.push_str("$BB mount -t proc proc /proc 2>/dev/null\n");
    init.push_str("$BB mount -t devtmpfs dev /dev 2>/dev/null\n");
    init.push_str("$BB mount -t sysfs sys /sys 2>/dev/null\n");
    // Set the wall clock from the host (cmdline supermachine.host_time=<secs>).
    // A KVM guest has no RTC and boots at ~1999, which makes TLS cert validation
    // fail (apk/pip/https in workloads + builder RUNs). Try the GNU `@epoch` form
    // then the busybox `-D %s` form.
    init.push_str(
        "for tok in $($BB cat /proc/cmdline); do case \"$tok\" in supermachine.host_time=*) \
         ht=\"${tok#supermachine.host_time=}\"; \
         $BB date -s \"@$ht\" >/dev/null 2>&1 || $BB date -u -D %s -s \"$ht\" >/dev/null 2>&1 || true;; \
         esac; done\n",
    );
    // Guest-environment parity with init-oci on HVF (each of these was
    // MISSING on KVM until integration_guest_env_contract caught the
    // divergence — the x86 path has no init-oci, so its work product
    // must be mirrored here by hand):
    //
    // - unprivileged low ports: node/nginx binding :80/:443 as non-root.
    //   Per-netns sysctl; survives switch_root.
    init.push_str("echo 0 > /proc/sys/net/ipv4/ip_unprivileged_port_start 2>/dev/null || true\n");
    // - AI_ADDRCONFIG: glibc only returns v4 records when a non-host-
    //   scope v4 address exists (apt, gnutls, getent ahostsv4, Java).
    //   init-oci builds an smnet0 veth; this kernel has no CONFIG_DUMMY/
    //   VETH, but glibc's check is ADDRESS-scope-based (check_pf.c skips
    //   scope >= RT_SCOPE_HOST), so a global-scope 198.18.0.1/30
    //   (RFC 2544 benchmark range, same as init-oci) on `lo` satisfies
    //   it with zero kernel deps. TSI owns actual routing either way.
    init.push_str("$BB ip link set lo up 2>/dev/null || true\n");
    init.push_str("$BB ip addr add 198.18.0.1/30 dev lo 2>/dev/null || true\n");
    // - POSIX shm: Chromium, Python multiprocessing, postgres. The
    //   tmpfs submount rides along with `mount --move /dev` below.
    init.push_str(
        "$BB mkdir -p /dev/shm && \
         $BB mount -t tmpfs -o size=64m,mode=1777 tmpfs /dev/shm 2>/dev/null || true\n",
    );
    // - /dev/fd + stdio symlinks: `bash <(cmd)` and countless scripts.
    //   Created in devtmpfs pre-move, so they exist in the container.
    init.push_str(
        "$BB ln -sf /proc/self/fd /dev/fd; \
         $BB ln -sf /proc/self/fd/0 /dev/stdin; \
         $BB ln -sf /proc/self/fd/1 /dev/stdout; \
         $BB ln -sf /proc/self/fd/2 /dev/stderr\n",
    );
    for (name, _) in &modules {
        init.push_str(&format!("$BB insmod /modules/{name}\n"));
    }
    init.push_str("$BB mkdir -p /lower /ov /newroot\n");
    init.push_str("lowers=''; idx=0\n");
    init.push_str("for dev in /dev/vd?; do\n");
    init.push_str("  [ -b \"$dev\" ] || continue\n");
    init.push_str("  m=/lower/$idx\n");
    init.push_str("  $BB mkdir -p \"$m\"\n");
    init.push_str("  if $BB mount -t squashfs -o ro \"$dev\" \"$m\" 2>/dev/null; then\n");
    init.push_str(
        "    if [ -n \"$lowers\" ]; then lowers=\"$m:$lowers\"; else lowers=\"$m\"; fi\n",
    );
    init.push_str("    idx=$((idx + 1))\n");
    init.push_str("  else\n");
    init.push_str("    $BB rmdir \"$m\" 2>/dev/null || true\n");
    init.push_str("  fi\n");
    init.push_str("done\n");
    init.push_str("if [ -z \"$lowers\" ]; then echo 'supermachine-kvm-init: no squashfs rootfs' >/dev/console; exec $BB sh; fi\n");
    init.push_str("$BB mount -t tmpfs tmpfs /ov\n");
    init.push_str("$BB mkdir -p /ov/upper /ov/work\n");
    init.push_str(
        "$BB mount -t overlay overlay \
         -o lowerdir=$lowers,upperdir=/ov/upper,workdir=/ov/work /newroot\n",
    );
    // DNS for transparent egress: TSI tunnels the resolver's UDP/TCP :53 to the
    // host, but the libc resolver needs nameservers to fire at all. Write the
    // SAME resolv.conf init-oci writes on HVF, and like init-oci REPLACE
    // whatever the image shipped (docker-exported rootfs carry docker's
    // embedded 127.0.0.11 — meaningless inside our VM).
    //
    // Exactly ONE nameserver: the TSI dgram shim overwrites `msg_name` with
    // the last sendto target, so with two nameservers musl's `__res_msend`
    // (which fires at both in parallel) rejects ~half the replies as
    // off-path — intermittent "DNS: transient error". This init shipped
    // `1.1.1.1` + `8.8.8.8` for a while and every KVM guest was exposed to
    // exactly that; caught by integration_dns::guest_dns_config_contract.
    // The options line mirrors init-oci: `no-aaaa` is glibc belt-and-braces
    // (musl ignores it — the muxer's AAAA→NODATA rewrite is the
    // musl-effective mechanism on v6-less hosts), `single-request`
    // serializes A/AAAA on resolvers that ignore no-aaaa.
    init.push_str(
        "$BB mkdir -p /newroot/etc; \
         printf 'nameserver 1.1.1.1\\noptions timeout:2 attempts:2 no-aaaa single-request\\n' \
         > /newroot/etc/resolv.conf\n",
    );
    // localhost must resolve even on images shipping an empty /etc/hosts
    // (cloudflared/sidecar pattern) — same guard as init-oci's hosts write.
    init.push_str(
        "$BB grep -q localhost /newroot/etc/hosts 2>/dev/null || \
         printf '127.0.0.1\\tlocalhost\\n::1\\tlocalhost\\n' >> /newroot/etc/hosts\n",
    );
    // Data volumes: the host attaches extra virtio-blk devices and passes
    // `sm.volume=DEV:MOUNT` on the cmdline. Mount each into the overlay so it's
    // present in the container after switch_root (submounts carry over). The
    // backing file is pre-formatted ext4 host-side, so this is a plain mount.
    init.push_str(
        "for tok in $($BB cat /proc/cmdline); do case \"$tok\" in sm.volume=*) \
         spec=\"${tok#sm.volume=}\"; dev=\"${spec%%:*}\"; mnt=\"${spec#*:}\"; \
         $BB mkdir -p \"/newroot$mnt\"; \
         $BB mount -t ext4 \"$dev\" \"/newroot$mnt\" || $BB mount \"$dev\" \"/newroot$mnt\";; \
         esac; done\n",
    );
    // virtio-fs mounts: the host exposes a directory over FUSE-over-virtio and
    // passes `sm.virtiofs=TAG:MOUNT` on the cmdline. Mount each by its tag into
    // the overlay so it's present in the container after switch_root.
    init.push_str(
        "for tok in $($BB cat /proc/cmdline); do case \"$tok\" in sm.virtiofs=*) \
         spec=\"${tok#sm.virtiofs=}\"; tag=\"${spec%%:*}\"; mnt=\"${spec#*:}\"; \
         $BB mkdir -p \"/newroot$mnt\"; \
         $BB mount -t virtiofs -o dax \"$tag\" \"/newroot$mnt\" || \
         $BB mount -t virtiofs \"$tag\" \"/newroot$mnt\";; \
         esac; done\n",
    );
    // Stage the agent inside the writable container root, carry the
    // pseudo-filesystems across, and switch_root in (the initramfs idiom —
    // pivot_root doesn't work from the initial ramfs). The agent becomes PID 1
    // inside the container; everything it execs sees `/` = the OCI rootfs.
    init.push_str("$BB mkdir -p /newroot/.supermachine /newroot/proc /newroot/dev /newroot/sys\n");
    init.push_str("$BB cp /supermachine-agent /newroot/.supermachine/agent\n");
    // Stage busybox into the container root too: the post-switch_root `launch`
    // shim (and the workload script's shebang) reference it by absolute path, so
    // it must exist regardless of what the OCI image ships (distroless, scratch).
    init.push_str("$BB cp /bin/busybox /newroot/.supermachine/busybox\n");
    init.push_str("$BB chmod 755 /newroot/.supermachine/busybox\n");
    // The `launch` shim becomes PID 1 inside the container after switch_root. It
    // forks the OCI workload (if the bake wrote `/.supermachine/run-workload`
    // into the squashfs — see `build_kvm_rootfs_squashfs`/`kvm_workload_script`),
    // records its pid where the agent looks for it
    // (`/run/supermachine-workload.pid`, matching init-oci on HVF), then `exec`s
    // the agent so the agent is PID 1 and reaps the workload. When no workload
    // script is present (agent-only image), it just execs the agent — the prior
    // behavior. Heredoc is single-quoted, so `$B`/`$!` are written verbatim and
    // expand when `launch` runs (post-switch_root), not while building init.
    init.push_str(
        "$BB cat > /newroot/.supermachine/launch <<'SMLAUNCH'\n\
#!/.supermachine/busybox sh\n\
B=/.supermachine/busybox\n\
$B mkdir -p /run\n\
if [ -f /.supermachine/run-workload ]; then\n\
  eval \"$($B grep -E '^export ' /.supermachine/run-workload)\" 2>/dev/null || true\n\
  $B sh /.supermachine/run-workload &\n\
  echo $! > /run/supermachine-workload.pid\n\
fi\n\
exec /.supermachine/agent\n\
SMLAUNCH\n",
    );
    init.push_str("$BB chmod 755 /newroot/.supermachine/launch\n");
    init.push_str("$BB mount --move /proc /newroot/proc\n");
    init.push_str("$BB mount --move /dev /newroot/dev\n");
    init.push_str("$BB mount --move /sys /newroot/sys\n");
    init.push_str("exec $BB switch_root /newroot /.supermachine/launch\n");
    let init_bytes = init.into_bytes();
    let module_paths: Vec<String> = modules
        .iter()
        .map(|(n, _)| format!("modules/{n}"))
        .collect();

    let mut entries = vec![
        CpioEntry::Dir("bin", 0o755),
        CpioEntry::Dir("modules", 0o755),
        CpioEntry::Dir("proc", 0o755),
        CpioEntry::Dir("dev", 0o755),
        CpioEntry::Dir("sys", 0o755),
        CpioEntry::File("init", 0o755, &init_bytes),
        CpioEntry::File("bin/busybox", 0o755, &busybox),
        CpioEntry::File("supermachine-agent", 0o755, &agent),
    ];
    for (i, (_, bytes)) in modules.iter().enumerate() {
        entries.push(CpioEntry::File(&module_paths[i], 0o644, bytes));
    }

    let cpio = build_newc_cpio(&entries);
    std::fs::write(out_cpio, &cpio)
        .map_err(|e| format!("write initramfs {}: {e}", out_cpio.display()))
}

#[cfg(test)]
mod sentry_rootfs_tests {
    use super::build_sentry_rootfs;
    use std::path::PathBuf;

    fn tmp(tag: &str) -> PathBuf {
        use std::sync::atomic::{AtomicU64, Ordering};
        static N: AtomicU64 = AtomicU64::new(0);
        let n = N.fetch_add(1, Ordering::Relaxed);
        std::env::temp_dir().join(format!("sm-sentryroot-{tag}-{}-{n}", std::process::id()))
    }

    fn make_tar(path: &std::path::Path, entries: &[(&str, &[u8])]) {
        let f = std::fs::File::create(path).unwrap();
        let mut b = tar::Builder::new(f);
        for (name, data) in entries {
            let mut h = tar::Header::new_gnu();
            h.set_path(name).unwrap();
            h.set_size(data.len() as u64);
            h.set_mode(0o644);
            h.set_cksum();
            b.append(&h, *data).unwrap();
        }
        b.finish().unwrap();
    }

    /// The sentry bake leaves a merged DIRECTORY (its artifact — no squashfs):
    /// layers merged bottom-up, whiteouts applied, workload script staged 0755.
    #[test]
    fn stages_merged_dir_with_whiteouts_and_workload() {
        let l1 = tmp("l1.tar");
        let l2 = tmp("l2.tar");
        make_tar(&l1, &[("a.txt", b"from-layer-1"), ("keep.txt", b"keep")]);
        make_tar(&l2, &[(".wh.a.txt", b""), ("b.txt", b"from-layer-2")]);

        let root = tmp("rootfs");
        let script = "#!/.supermachine/busybox sh\nexec 'nginx' '-g' 'daemon off;'\n";
        build_sentry_rootfs(&[l1.clone(), l2.clone()], &root, Some(script)).expect("build");

        assert!(root.is_dir(), "rootfs dir is the artifact and must remain");
        assert!(root.join("b.txt").is_file(), "upper layer file present");
        assert!(root.join("keep.txt").is_file(), "lower-only file kept");
        assert!(!root.join("a.txt").exists(), "whited-out file removed");
        assert!(!root.join(".wh.a.txt").exists(), "whiteout marker removed");
        let run = root.join(".supermachine/run-workload");
        assert_eq!(std::fs::read_to_string(&run).unwrap(), script);
        #[cfg(unix)]
        {
            use std::os::unix::fs::PermissionsExt as _;
            let mode = std::fs::metadata(&run).unwrap().permissions().mode();
            assert_eq!(mode & 0o777, 0o755, "run-workload is executable");
        }

        let _ = std::fs::remove_file(&l1);
        let _ = std::fs::remove_file(&l2);
        let _ = std::fs::remove_dir_all(&root);
    }
}

#[cfg(test)]
mod kvm_rootfs_tests {
    use super::build_kvm_rootfs_squashfs;
    use std::path::PathBuf;

    fn tmp(tag: &str) -> PathBuf {
        use std::sync::atomic::{AtomicU64, Ordering};
        static N: AtomicU64 = AtomicU64::new(0);
        let n = N.fetch_add(1, Ordering::Relaxed);
        std::env::temp_dir().join(format!("sm-kvmroot-{tag}-{}-{n}", std::process::id()))
    }

    /// Write a plain (uncompressed) tar containing the given (path, contents)
    /// entries — enough for `extract_layer_tar` to ingest as a layer.
    fn make_tar(path: &std::path::Path, entries: &[(&str, &[u8])]) {
        let f = std::fs::File::create(path).unwrap();
        let mut b = tar::Builder::new(f);
        for (name, data) in entries {
            let mut h = tar::Header::new_gnu();
            h.set_path(name).unwrap();
            h.set_size(data.len() as u64);
            h.set_mode(0o644);
            h.set_cksum();
            b.append(&h, *data).unwrap();
        }
        b.finish().unwrap();
    }

    /// Two layers merged bottom-up with a whiteout: layer2 deletes a.txt
    /// (via `.wh.a.txt`) and adds b.txt. The merged rootfs must contain b.txt,
    /// not a.txt, no `.wh.` markers, and the squashfs must be produced.
    #[test]
    fn merges_layers_applies_whiteouts_and_writes_squashfs() {
        let l1 = tmp("l1.tar");
        let l2 = tmp("l2.tar");
        make_tar(&l1, &[("a.txt", b"from-layer-1"), ("keep.txt", b"keep")]);
        make_tar(&l2, &[(".wh.a.txt", b""), ("b.txt", b"from-layer-2")]);

        let work = tmp("work");
        let out = tmp("rootfs.squashfs");
        build_kvm_rootfs_squashfs(&[l1.clone(), l2.clone()], &work, &out, None)
            .expect("build rootfs");

        // Merge + whiteout correctness on the staged tree.
        assert!(work.join("b.txt").is_file(), "upper layer file present");
        assert!(work.join("keep.txt").is_file(), "lower-only file kept");
        assert!(!work.join("a.txt").exists(), "whited-out file removed");
        assert!(!work.join(".wh.a.txt").exists(), "whiteout marker removed");

        // A real squashfs was written (magic "hsqs", non-trivial size).
        let bytes = std::fs::read(&out).expect("read squashfs");
        assert!(bytes.len() > 100, "squashfs non-empty");
        assert_eq!(&bytes[..4], b"hsqs", "squashfs magic");

        let _ = std::fs::remove_file(&l1);
        let _ = std::fs::remove_file(&l2);
        let _ = std::fs::remove_dir_all(&work);
        let _ = std::fs::remove_file(&out);
    }

    /// Empty layer list is rejected cleanly by the squashfs writer rather than
    /// producing a corrupt image (defensive: callers shouldn't pass none).
    #[test]
    fn writes_squashfs_for_single_layer() {
        let l1 = tmp("s1.tar");
        make_tar(&l1, &[("etc/os-release", b"NAME=test\n")]);
        let work = tmp("swork");
        let out = tmp("s.squashfs");
        build_kvm_rootfs_squashfs(&[l1.clone()], &work, &out, None).expect("build");
        assert!(work.join("etc/os-release").is_file());
        assert_eq!(&std::fs::read(&out).unwrap()[..4], b"hsqs");
        let _ = std::fs::remove_file(&l1);
        let _ = std::fs::remove_dir_all(&work);
        let _ = std::fs::remove_file(&out);
    }

    /// When a workload script is supplied, it's staged into the rootfs at
    /// `/.supermachine/run-workload` (mode 0755) before squashing, so the
    /// initramfs `launch` shim can fork it.
    #[test]
    fn workload_script_written_into_rootfs() {
        let l1 = tmp("w1.tar");
        make_tar(&l1, &[("etc/os-release", b"NAME=test\n")]);
        let work = tmp("wwork");
        let out = tmp("w.squashfs");
        let script = "#!/.supermachine/busybox sh\nexec 'nginx' '-g' 'daemon off;'\n";
        build_kvm_rootfs_squashfs(&[l1.clone()], &work, &out, Some(script)).expect("build");
        let run = work.join(".supermachine/run-workload");
        assert!(run.is_file(), "run-workload staged");
        assert_eq!(std::fs::read_to_string(&run).unwrap(), script);
        #[cfg(unix)]
        {
            use std::os::unix::fs::PermissionsExt as _;
            let mode = std::fs::metadata(&run).unwrap().permissions().mode();
            assert_eq!(mode & 0o777, 0o755, "run-workload is executable");
        }
        let _ = std::fs::remove_file(&l1);
        let _ = std::fs::remove_dir_all(&work);
        let _ = std::fs::remove_file(&out);
    }

    /// Shell single-quoting is injection-proof: literals are wrapped in `'…'`
    /// and embedded quotes become `'\''`, so no metacharacter escapes the quote.
    #[test]
    fn sh_single_quote_escapes_metachars() {
        use super::sh_single_quote;
        assert_eq!(sh_single_quote("nginx"), "'nginx'");
        assert_eq!(sh_single_quote("daemon off;"), "'daemon off;'");
        // An embedded single quote closes, escapes, reopens.
        assert_eq!(sh_single_quote("a'b"), "'a'\\''b'");
        // Shell-active chars are inert inside single quotes.
        assert_eq!(sh_single_quote("$(rm -rf /)"), "'$(rm -rf /)'");
        assert_eq!(sh_single_quote("`id`"), "'`id`'");
    }

    use super::{build_newc_cpio, CpioEntry};

    /// Parse a newc cpio back into (name, mode, data) records (until TRAILER).
    fn parse_newc(buf: &[u8]) -> Vec<(String, u32, Vec<u8>)> {
        let hex = |b: &[u8]| u32::from_str_radix(std::str::from_utf8(b).unwrap(), 16).unwrap();
        let mut out = Vec::new();
        let mut p = 0usize;
        loop {
            assert_eq!(&buf[p..p + 6], b"070701", "newc magic at {p}");
            let mode = hex(&buf[p + 14..p + 22]);
            let filesize = hex(&buf[p + 54..p + 62]) as usize;
            let namesize = hex(&buf[p + 94..p + 102]) as usize;
            let name_start = p + 110;
            let name =
                String::from_utf8(buf[name_start..name_start + namesize - 1].to_vec()).unwrap();
            // header(110)+name padded to 4
            let after_name = (name_start + namesize + 3) & !3;
            let data = buf[after_name..after_name + filesize].to_vec();
            let after_data = (after_name + filesize + 3) & !3;
            p = after_data;
            if name == "TRAILER!!!" {
                break;
            }
            out.push((name, mode, data));
        }
        out
    }

    /// The in-process newc cpio builder produces a parseable archive with the
    /// right entries, modes, contents, and a terminating TRAILER.
    #[test]
    fn newc_cpio_roundtrips() {
        let cpio = build_newc_cpio(&[
            CpioEntry::Dir("mnt", 0o755),
            CpioEntry::File("init", 0o755, b"#!/bin/sh\nexec /agent\n"),
            CpioEntry::File("modules/vsock.ko", 0o644, b"\x7fELF-fake-module"),
        ]);
        // Length is 4-aligned and starts with the newc magic.
        assert_eq!(cpio.len() % 4, 0);
        assert_eq!(&cpio[..6], b"070701");
        let recs = parse_newc(&cpio);
        assert_eq!(recs.len(), 3, "3 entries before trailer");
        assert_eq!(recs[0].0, "mnt");
        assert_eq!(recs[0].1 & 0o170000, 0o040000, "dir bit");
        assert_eq!(recs[1].0, "init");
        assert_eq!(recs[1].1 & 0o170000, 0o100000, "regular file bit");
        assert_eq!(recs[1].1 & 0o777, 0o755, "exec perms");
        assert_eq!(recs[1].2, b"#!/bin/sh\nexec /agent\n");
        assert_eq!(recs[2].0, "modules/vsock.ko");
        assert_eq!(recs[2].2, b"\x7fELF-fake-module");
    }
}

#[cfg(test)]
mod balloon_target_tests {
    use super::compute_balloon_target_pages;
    #[test]
    fn small_guest_caps_at_safety_floor() {
        // 128 MiB - 96 MiB = 32 MiB reclaim = 8192 pages
        assert_eq!(compute_balloon_target_pages(128), 8192);
    }
    #[test]
    fn medium_guest_uses_70_percent() {
        // 256 MiB * 0.7 = 179.2 MiB = 45875 pages
        // safety floor: 256 - 96 = 160 MiB = 40960 pages
        // min = 40960
        assert_eq!(compute_balloon_target_pages(256), 40960);
    }
    #[test]
    fn large_guest_uses_70_percent() {
        // 1024 MiB * 0.7 = 716.8 MiB = 183500 pages
        // safety floor: 1024 - 96 = 928 MiB = 237568 pages
        // min = 183500
        assert_eq!(compute_balloon_target_pages(1024), 183500);
    }
    #[test]
    fn tiny_guest_no_ballooning() {
        // 64 MiB < safety floor — no reclaim at all.
        assert_eq!(compute_balloon_target_pages(64), 0);
    }
}

#[cfg(test)]
mod kernel_panic_detect_tests {
    use super::{detect_kernel_panic, encode_kernel_panic_err, PANIC_STACK_LINES};
    use std::io::Write;
    use std::path::PathBuf;
    use std::sync::atomic::{AtomicU64, Ordering};

    static TEST_COUNTER: AtomicU64 = AtomicU64::new(0);

    /// Cheap stand-in for `tempfile::NamedTempFile` so the dev-deps
    /// stay frozen. Writes to a unique path under the system temp dir
    /// and removes it on drop.
    struct TempLog {
        path: PathBuf,
    }
    impl TempLog {
        fn new(contents: &str) -> Self {
            let id = TEST_COUNTER.fetch_add(1, Ordering::Relaxed);
            let path = std::env::temp_dir().join(format!(
                "supermachine-panic-test-{}-{id}.log",
                std::process::id()
            ));
            let mut f = std::fs::File::create(&path).expect("create tmp log");
            f.write_all(contents.as_bytes()).expect("write tmp log");
            Self { path }
        }
        fn path(&self) -> &std::path::Path {
            &self.path
        }
    }
    impl Drop for TempLog {
        fn drop(&mut self) {
            let _ = std::fs::remove_file(&self.path);
        }
    }

    #[test]
    fn detects_kernel_panic_banner() {
        let log = TempLog::new(
            "[    0.001234] Booting Linux\n\
             [    0.123456] something normal\n\
             [    1.234567] Kernel panic - not syncing: foo\n\
             [    1.234568] CPU: 0 PID: 1\n\
             [    1.234569] Call trace:\n\
             [    1.234570]  panic+0x12c/0x34c\n",
        );
        let (first, stack) = detect_kernel_panic(log.path()).expect("panic detected");
        assert!(first.contains("Kernel panic"), "first_line: {first}");
        assert!(stack.iter().any(|l| l.contains("Call trace")));
    }

    #[test]
    fn detects_oops() {
        let log = TempLog::new(
            "boot fine\n\
             Internal error: Oops: 0000000096000004 [#1] PREEMPT SMP\n\
             Modules linked in:\n\
             pstate: 60400005\n",
        );
        let (first, stack) = detect_kernel_panic(log.path()).expect("oops detected");
        assert!(first.contains("Internal error: Oops"));
        assert_eq!(stack.len(), 2);
    }

    #[test]
    fn detects_unable_to_handle_null() {
        let log = TempLog::new(
            "[ok] running\n\
             [ok] still going\n\
             Unable to handle kernel NULL pointer dereference at virtual address 0\n",
        );
        let (first, _) = detect_kernel_panic(log.path()).expect("null deref detected");
        assert!(first.contains("NULL pointer dereference"));
    }

    #[test]
    fn detects_kill_idle_task() {
        let log = TempLog::new("Attempted to kill the idle task!\nsystem hosed\n");
        let (first, _) = detect_kernel_panic(log.path()).expect("idle-task detected");
        assert!(first.contains("Attempted to kill the idle task"));
    }

    #[test]
    fn detects_paging_request() {
        let log = TempLog::new(
            "ok\nUnable to handle kernel paging request at virtual address ffff000000000000\n",
        );
        let (first, _) = detect_kernel_panic(log.path()).expect("paging detected");
        assert!(first.contains("paging request"));
    }

    #[test]
    fn no_match_on_clean_log() {
        let log = TempLog::new(
            "[    0.000000] Booting Linux on physical CPU 0x0000000000\n\
             [    0.123456] init-oci: stage=workload-pre-exec\n\
             [    0.987654] supermachine.worker: BAKE_READY\n",
        );
        assert!(detect_kernel_panic(log.path()).is_none());
    }

    #[test]
    fn stack_capture_is_bounded() {
        let mut text = String::from("Kernel panic - test\n");
        for i in 0..(PANIC_STACK_LINES + 50) {
            text.push_str(&format!("frame{i}\n"));
        }
        let log = TempLog::new(&text);
        let (_, stack) = detect_kernel_panic(log.path()).unwrap();
        assert_eq!(stack.len(), PANIC_STACK_LINES);
    }

    #[test]
    fn encode_decode_round_trips() {
        let first = "Kernel panic - test".to_owned();
        let stack = vec!["frame_a".to_owned(), "frame_b".to_owned()];
        let encoded = encode_kernel_panic_err(&first, &stack);
        assert!(encoded.starts_with("KERNEL_PANIC|"));
        let rest = encoded.strip_prefix("KERNEL_PANIC|").unwrap();
        let mut parts = rest.splitn(2, '|');
        assert_eq!(parts.next().unwrap(), "Kernel panic - test");
        let stack_str = parts.next().unwrap();
        let decoded: Vec<&str> = stack_str.split('\x1F').collect();
        assert_eq!(decoded, vec!["frame_a", "frame_b"]);
    }

    #[test]
    fn snapshot_after_ms_default_no_flag() {
        // Without --supermachine-listener-required, the bake's
        // listener fallback drops from 7000 to 200 ms — Fix 2.
        let extra: Vec<String> = Vec::new();
        // SAFETY: tests share env; clear the override so the
        // default-selection branch is what we exercise. Set/unset
        // around assertion only.
        let prev = std::env::var("SUPERMACHINE_SNAPSHOT_AFTER_MS").ok();
        unsafe {
            std::env::remove_var("SUPERMACHINE_SNAPSHOT_AFTER_MS");
        }
        let plan = super::BakePlan {
            image: "x",
            name: None,
            runtime: "supermachine",
            guest_port: 80,
            memory_mib: 256,
            vcpus: 1,
            pull_policy: "missing",
            snapshots_dir: std::path::Path::new("/tmp"),
            cmd_override: None,
            extra_args: &extra,
            platform: "linux/arm64",
        };
        let v = super::native_snapshot_after_ms(&plan);
        if let Some(p) = prev {
            unsafe {
                std::env::set_var("SUPERMACHINE_SNAPSHOT_AFTER_MS", p);
            }
        }
        assert_eq!(v, 200, "no listener-required ⇒ short fallback");
    }

    #[test]
    fn snapshot_after_ms_listener_required_keeps_7s() {
        let extra: Vec<String> = vec!["--supermachine-listener-required".to_owned()];
        let prev = std::env::var("SUPERMACHINE_SNAPSHOT_AFTER_MS").ok();
        unsafe {
            std::env::remove_var("SUPERMACHINE_SNAPSHOT_AFTER_MS");
        }
        let plan = super::BakePlan {
            image: "x",
            name: None,
            runtime: "supermachine",
            guest_port: 80,
            memory_mib: 256,
            vcpus: 1,
            pull_policy: "missing",
            snapshots_dir: std::path::Path::new("/tmp"),
            cmd_override: None,
            extra_args: &extra,
            platform: "linux/arm64",
        };
        let v = super::native_snapshot_after_ms(&plan);
        if let Some(p) = prev {
            unsafe {
                std::env::set_var("SUPERMACHINE_SNAPSHOT_AFTER_MS", p);
            }
        }
        assert_eq!(v, 7000, "listener-required ⇒ keep 7s safety net");
    }
}

#[cfg(test)]
mod mount_arg_parsing_tests {
    //! Wire-encoding tests for the unified `--mount
    //! HOST:TAG:GUEST_PATH[:POLICY]` format. Every mount carries a
    //! `guest_path` (required field) — the parser rejects shorter
    //! forms outright. POLICY (4th field) is optional and defaults
    //! to `opaque`.
    use super::parse_mount_arg;

    #[test]
    fn three_field_mount_no_policy() {
        let v = parse_mount_arg("/host/x:workspace:/workspace").unwrap();
        assert_eq!(v["host_path"], "/host/x");
        assert_eq!(v["guest_tag"], "workspace");
        assert_eq!(v["guest_path"], "/workspace");
        assert!(v.get("symlinks").is_none());
    }

    #[test]
    fn four_field_mount_with_policy() {
        let v = parse_mount_arg("/h:t:/g:deny").unwrap();
        assert_eq!(v["host_path"], "/h");
        assert_eq!(v["guest_tag"], "t");
        assert_eq!(v["guest_path"], "/g");
        assert_eq!(v["symlinks"], "deny");
    }

    #[test]
    fn each_policy_round_trips() {
        for policy in ["opaque", "deny", "follow"] {
            let raw = format!("/h:t:/g:{policy}");
            let v = parse_mount_arg(&raw).unwrap();
            assert_eq!(v["symlinks"], policy);
        }
    }

    #[test]
    fn two_field_legacy_form_rejected() {
        // Pre-0.7.29 `HOST:TAG` (no guest_path) is no longer valid.
        assert!(parse_mount_arg("/h:tag").is_none());
        assert!(
            parse_mount_arg("/h:tag:").is_none(),
            "empty guest_path is also invalid"
        );
    }

    #[test]
    fn empty_tag_rejected() {
        // HOST::GUEST_PATH — empty tag is a no-op virtio-fs share.
        assert!(parse_mount_arg("/h::/g").is_none());
    }

    #[test]
    fn empty_guest_path_rejected() {
        assert!(parse_mount_arg("/h:tag:").is_none());
    }

    #[test]
    fn one_field_rejected() {
        assert!(parse_mount_arg("/just-host").is_none());
    }

    #[test]
    fn five_or_more_fields_treated_as_path_with_colons() {
        // splitn(4, ':') means a 5th token would actually land in the
        // policy field. We only accept opaque/deny/follow at the
        // worker side; bake.rs's parse_mount_arg lets it through
        // here (the worker layer is the validator). Spot-check
        // shape only.
        let v = parse_mount_arg("/h:t:/g:weirdpolicy:trailing");
        assert!(
            v.is_some(),
            "splitn(4) keeps the trailing token in the 4th field"
        );
    }

    #[test]
    fn policy_field_can_be_empty_treated_as_omitted() {
        // `/h:t:/g:` → 4 fields with the 4th empty. Conservative:
        // treat as policy = "" which the worker arg parser will
        // reject. We accept the parse here because the validation
        // boundary is at the worker.
        let v = parse_mount_arg("/h:t:/g:").unwrap();
        assert_eq!(v["symlinks"], "");
    }

    #[test]
    fn guest_path_with_subdirs_preserved() {
        let v = parse_mount_arg("/h:t:/a/b/c/d").unwrap();
        assert_eq!(v["guest_path"], "/a/b/c/d");
    }
}

#[cfg(test)]
mod manifest_pin_verification_tests {
    //! Supply-chain root of trust: a digest-pinned image reference must
    //! be rejected if the registry returns a manifest that doesn't hash
    //! to the pinned digest. Tag refs and non-sha256 algorithms are
    //! intentionally not verified here.
    use super::{
        parse_content_digest_header, sha256_bytes, verify_advertised_content_digest,
        verify_pinned_manifest_digest,
    };

    #[test]
    fn tag_reference_is_not_verified() {
        // No digest to check against — any body is accepted.
        assert!(verify_pinned_manifest_digest("latest", b"arbitrary manifest bytes").is_ok());
        assert!(verify_pinned_manifest_digest("v1.2.3", b"\x00\xff\x10").is_ok());
    }

    #[test]
    fn matching_pinned_digest_is_accepted() {
        let body = br#"{"schemaVersion":2,"layers":[]}"#;
        let hex = sha256_bytes(body).unwrap();
        let reference = format!("sha256:{hex}");
        assert!(
            verify_pinned_manifest_digest(&reference, body).is_ok(),
            "honest registry serving the pinned manifest must pass",
        );
    }

    #[test]
    fn mismatched_pinned_digest_is_rejected() {
        // A registry serving DIFFERENT bytes than the pin demands — the
        // exact MITM / tampering scenario digest pinning exists to stop.
        let pinned_body = br#"{"schemaVersion":2,"config":{"digest":"sha256:aaaa"}}"#;
        let hex = sha256_bytes(pinned_body).unwrap();
        let reference = format!("sha256:{hex}");

        let tampered_body = br#"{"schemaVersion":2,"config":{"digest":"sha256:bbbb"}}"#;
        let err = verify_pinned_manifest_digest(&reference, tampered_body)
            .expect_err("tampered manifest must be rejected");
        assert!(err.contains("digest mismatch"), "unexpected error: {err}");
        assert!(err.contains("refusing to bake"), "unexpected error: {err}");
    }

    #[test]
    fn flipping_one_byte_is_detected() {
        let mut body = b"the original manifest document".to_vec();
        let hex = sha256_bytes(&body).unwrap();
        let reference = format!("sha256:{hex}");
        assert!(verify_pinned_manifest_digest(&reference, &body).is_ok());
        // Single-bit change anywhere must fail the pin.
        body[0] ^= 1;
        assert!(
            verify_pinned_manifest_digest(&reference, &body).is_err(),
            "a one-byte change slipped past the digest pin",
        );
    }

    #[test]
    fn non_sha256_algorithm_is_left_unverified() {
        // We don't (yet) verify sha512 pins; ensure we don't panic or
        // falsely reject — we pass through, matching prior behavior.
        assert!(verify_pinned_manifest_digest("sha512:deadbeef", b"anything").is_ok());
    }

    // --- Docker-Content-Digest cross-check (defense in depth) ---------

    #[test]
    fn content_digest_header_is_parsed_case_insensitively() {
        let headers = "HTTP/1.1 200 OK\r\n\
                       Content-Type: application/vnd.oci.image.manifest.v1+json\r\n\
                       docker-content-digest: sha256:abc123\r\n\
                       Content-Length: 42\r\n";
        assert_eq!(
            parse_content_digest_header(headers).as_deref(),
            Some("sha256:abc123"),
        );
    }

    #[test]
    fn absent_content_digest_header_is_a_noop() {
        let headers = "HTTP/1.1 200 OK\r\nContent-Length: 3\r\n";
        assert!(parse_content_digest_header(headers).is_none());
        assert!(verify_advertised_content_digest(headers, b"anything").is_ok());
    }

    #[test]
    fn advertised_digest_matching_body_is_accepted() {
        let body = br#"{"schemaVersion":2}"#;
        let hex = sha256_bytes(body).unwrap();
        let headers = format!("HTTP/1.1 200 OK\r\nDocker-Content-Digest: sha256:{hex}\r\n");
        assert!(verify_advertised_content_digest(&headers, body).is_ok());
    }

    #[test]
    fn advertised_digest_not_matching_body_is_rejected() {
        // Registry/proxy returns body X but advertises a digest for some
        // other content — mislabeling that pinning alone wouldn't catch
        // on a tag pull.
        let real = br#"{"schemaVersion":2}"#;
        let other = br#"{"schemaVersion":2,"evil":true}"#;
        let other_hex = sha256_bytes(other).unwrap();
        let headers = format!("HTTP/1.1 200 OK\r\nDocker-Content-Digest: sha256:{other_hex}\r\n");
        let err = verify_advertised_content_digest(&headers, real)
            .expect_err("mislabeled content must be rejected");
        assert!(
            err.contains("mislabeled content"),
            "unexpected error: {err}"
        );
    }

    #[test]
    fn advertised_non_sha256_digest_is_left_unverified() {
        let headers = "HTTP/1.1 200 OK\r\nDocker-Content-Digest: sha512:deadbeef\r\n";
        assert!(verify_advertised_content_digest(headers, b"anything").is_ok());
    }
}

#[cfg(test)]
mod digest_path_guard_tests {
    //! Path-traversal guards for registry/`docker save`-controlled
    //! digests and manifest paths, so a hostile manifest can't escape
    //! `blobs/sha256/` or read arbitrary host files.
    use super::{confined_join, sha256_path_component};
    use std::path::Path;

    #[test]
    fn sha256_component_accepts_valid_digest() {
        let hex = "a".repeat(64);
        assert_eq!(
            sha256_path_component(&format!("sha256:{hex}")).unwrap(),
            hex
        );
        // Bare (already-stripped) hex is accepted too.
        assert_eq!(sha256_path_component(&hex).unwrap(), hex);
    }

    #[test]
    fn sha256_component_rejects_traversal() {
        for bad in [
            "sha256:../../etc/passwd",
            "sha256:..",
            "sha256:a/b",
            "sha256:../x",
            "../../escape",
            "sha256:",
            "",
        ] {
            assert!(
                sha256_path_component(bad).is_err(),
                "{bad:?} must be rejected as unsafe"
            );
        }
    }

    #[test]
    fn confined_join_allows_in_tree_paths() {
        let root = Path::new("/base");
        assert_eq!(
            confined_join(root, "blobs/sha256/abc").unwrap(),
            Path::new("/base/blobs/sha256/abc")
        );
        assert_eq!(
            confined_join(root, "config.json").unwrap(),
            Path::new("/base/config.json")
        );
    }

    #[test]
    fn confined_join_rejects_escapes() {
        let root = Path::new("/base");
        for bad in [
            "../../etc/passwd",
            "/etc/passwd",
            "a/../../b",
            "blobs/../../escape",
        ] {
            assert!(
                confined_join(root, bad).is_err(),
                "{bad:?} must be rejected as a traversal"
            );
        }
    }
}

#[cfg(test)]
mod tar_extract_tests {
    //! Security: a malicious OCI layer must not tar-slip out of its
    //! extraction directory onto the host. We rely on the system tar's
    //! default confinement; this locks it against regression by crafting
    //! a hostile tar (parent-dir traversal, a symlink-escape pair, and an
    //! absolute path) and asserting NOTHING lands outside `dest`.
    use super::extract_layer_tar;
    use std::path::PathBuf;

    /// Emit one raw USTAR 512-byte header + padded data block.
    fn ustar(name: &str, typeflag: u8, linkname: &str, data: &[u8]) -> Vec<u8> {
        let mut h = [0u8; 512];
        let nb = name.as_bytes();
        h[0..nb.len().min(100)].copy_from_slice(&nb[..nb.len().min(100)]);
        h[100..108].copy_from_slice(b"0000644\0");
        h[108..116].copy_from_slice(b"0000000\0");
        h[116..124].copy_from_slice(b"0000000\0");
        h[124..136].copy_from_slice(format!("{:011o}\0", data.len()).as_bytes());
        h[136..148].copy_from_slice(b"00000000000\0");
        h[156] = typeflag;
        let lb = linkname.as_bytes();
        h[157..157 + lb.len().min(100)].copy_from_slice(&lb[..lb.len().min(100)]);
        h[257..263].copy_from_slice(b"ustar\0");
        h[263..265].copy_from_slice(b"00");
        // Checksum: field is spaces during the sum, then "%06o\0 ".
        for b in &mut h[148..156] {
            *b = b' ';
        }
        let sum: u32 = h.iter().map(|&b| b as u32).sum();
        h[148..156].copy_from_slice(format!("{sum:06o}\0 ").as_bytes());
        let mut out = h.to_vec();
        out.extend_from_slice(data);
        let pad = (512 - data.len() % 512) % 512;
        out.extend(std::iter::repeat(0u8).take(pad));
        out
    }

    fn tmp(tag: &str) -> PathBuf {
        let n = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_nanos();
        std::env::temp_dir().join(format!("sm-tarslip-{tag}-{}-{n}", std::process::id()))
    }

    #[test]
    fn tar_extract_confinement() {
        const FILE: u8 = b'0';
        const SYMLINK: u8 = b'2';
        let base = tmp("base");
        let dest = base.join("layer_extract");
        std::fs::create_dir_all(&dest).unwrap();

        // A hostile layer archive.
        let mut blob = Vec::new();
        blob.extend(ustar("../escape_dotdot", FILE, "", b"PWNED")); // parent traversal
        blob.extend(ustar("good", FILE, "", b"ok")); // control: lands inside
        blob.extend(ustar("evil_link", SYMLINK, "..", b"")); // symlink to parent
        blob.extend(ustar("evil_link/through", FILE, "", b"PWNED2")); // extract through it
        blob.extend(ustar("/abs_escape", FILE, "", b"PWNED3")); // absolute path
        blob.extend(std::iter::repeat(0u8).take(1024)); // two zero blocks = EOF
        let blob_path = base.join("evil.tar");
        std::fs::write(&blob_path, &blob).unwrap();

        // Tolerant of the non-zero exit the rejected entries produce.
        extract_layer_tar(&blob_path, &dest).expect("extract returns Ok");

        // The control entry made it INTO dest.
        assert_eq!(
            std::fs::read_to_string(dest.join("good")).unwrap_or_default(),
            "ok",
            "legitimate entry must extract inside dest"
        );

        // NOTHING escaped to dest's parent. The leading-'/' entry is
        // stripped to land inside dest, never at the real fs root.
        assert!(
            !base.join("escape_dotdot").exists(),
            "`../` traversal escaped the extraction dir"
        );
        assert!(
            !base.join("through").exists() && !base.join("PWNED2").exists(),
            "symlink-escape wrote outside the extraction dir"
        );
        // The only child of `base` besides the tar + dest is nothing else.
        let stray: Vec<_> = std::fs::read_dir(&base)
            .unwrap()
            .flatten()
            .map(|e| e.file_name().to_string_lossy().into_owned())
            .filter(|n| n != "layer_extract" && n != "evil.tar")
            .collect();
        assert!(
            stray.is_empty(),
            "unexpected escaped entries in base: {stray:?}"
        );

        let _ = std::fs::remove_dir_all(&base);
    }

    #[test]
    fn decompression_bomb_rejected() {
        // A tiny gzip layer that expands far past a low cap must be rejected
        // (disk-fill DoS protection).
        let dir = tmp("bomb");
        std::fs::create_dir_all(&dir).unwrap();
        // A valid tar with a 1 MiB file → gzips to a few KB.
        let enc = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::best());
        let mut ar = tar::Builder::new(enc);
        let big = vec![0u8; 1 << 20];
        let mut h = tar::Header::new_gnu();
        h.set_size(big.len() as u64);
        h.set_mode(0o644);
        h.set_cksum();
        ar.append_data(&mut h, "big", &big[..]).unwrap();
        let gz = ar.into_inner().unwrap().finish().unwrap();
        let blob = dir.join("bomb.tar.gz");
        std::fs::write(&blob, &gz).unwrap();
        let dest = dir.join("out");
        std::fs::create_dir_all(&dest).unwrap();

        std::env::set_var("SUPERMACHINE_MAX_LAYER_BYTES", "65536"); // < 1 MiB
        let r = extract_layer_tar(&blob, &dest);
        std::env::remove_var("SUPERMACHINE_MAX_LAYER_BYTES");
        assert!(
            r.is_err() && format!("{r:?}").contains("cap"),
            "decompression bomb should be rejected, got {r:?}"
        );
        let _ = std::fs::remove_dir_all(&dir);
    }
}

#[cfg(test)]
mod oci_save_dir_tests {
    //! Regression tests for [`layer_shas_from_save_dir`] — specifically
    //! the case where Apple's `container image save` (and Docker's
    //! `docker save --platform`) wraps the actual platform manifest
    //! inside an *outer* `oci.image.index.v1+json` whose only
    //! `manifests[]` entry is another image-index. The platform
    //! discriminator (`platform.architecture`) lives one level down.
    //!
    //! Before the fix, this path only inspected the outer index's
    //! `manifests[]` and rejected the amd64 case with
    //! `no amd64 manifest in image <ref>`. arm64 happened to work for
    //! integrators because a later fallback path picked up the only
    //! manifest regardless of structure — masking the bug until the
    //! Rosetta-in-VM amd64 workflow exercised it.
    use serde_json::json;

    fn write_blob(layout: &std::path::Path, bytes: &[u8]) -> String {
        use std::io::Write;
        let sha = super::sha256_bytes(bytes).expect("sha256");
        let dir = layout.join("blobs/sha256");
        std::fs::create_dir_all(&dir).unwrap();
        let mut f = std::fs::File::create(dir.join(&sha)).unwrap();
        f.write_all(bytes).unwrap();
        sha
    }

    fn write_json(layout: &std::path::Path, v: &serde_json::Value) -> String {
        let bytes = serde_json::to_vec(v).unwrap();
        write_blob(layout, &bytes)
    }

    fn scratch_layout(name: &str) -> std::path::PathBuf {
        let p = std::env::temp_dir().join(format!(
            "supermachine-oci-save-test-{}-{name}",
            std::process::id()
        ));
        let _ = std::fs::remove_dir_all(&p);
        std::fs::create_dir_all(&p).unwrap();
        std::fs::create_dir_all(p.join("blobs/sha256")).unwrap();
        p
    }

    fn write_index(layout: &std::path::Path, idx: &serde_json::Value) {
        let bytes = serde_json::to_vec_pretty(idx).unwrap();
        std::fs::write(layout.join("index.json"), bytes).unwrap();
    }

    /// `container image save` wraps a single-arch amd64 image in an
    /// outer image-index whose only entry is another image-index;
    /// the actual `platform.architecture=amd64` is on the inner
    /// image-index's manifests[]. This is the integrator-reported
    /// failure mode.
    #[test]
    fn nested_index_resolves_amd64() {
        let layout = scratch_layout("nested-amd64");
        // Innermost: the actual platform manifest (layers list).
        let manifest = json!({
            "schemaVersion": 2,
            "mediaType": "application/vnd.oci.image.manifest.v1+json",
            "config": { "mediaType": "application/vnd.oci.image.config.v1+json",
                        "digest": "sha256:dead", "size": 0 },
            "layers": [
                { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
                  "digest": "sha256:aaa", "size": 1 },
                { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
                  "digest": "sha256:bbb", "size": 2 }
            ]
        });
        let manifest_sha = write_json(&layout, &manifest);

        // Inner index: holds the platform discriminator.
        let inner_index = json!({
            "schemaVersion": 2,
            "mediaType": "application/vnd.oci.image.index.v1+json",
            "manifests": [
                { "mediaType": "application/vnd.oci.image.manifest.v1+json",
                  "digest": format!("sha256:{manifest_sha}"),
                  "size": 0,
                  "platform": { "architecture": "amd64", "os": "linux" } }
            ]
        });
        let inner_sha = write_json(&layout, &inner_index);

        // Outer index: NO platform field; just wraps the inner index.
        // This is exactly what `container image save` writes.
        let outer = json!({
            "schemaVersion": 2,
            "manifests": [
                { "mediaType": "application/vnd.oci.image.index.v1+json",
                  "digest": format!("sha256:{inner_sha}"),
                  "size": 0 }
            ]
        });
        write_index(&layout, &outer);

        let shas =
            super::layer_shas_from_save_dir("test/amd64", &layout, "amd64").expect("resolve");
        assert_eq!(shas, vec!["aaa".to_owned(), "bbb".to_owned()]);
    }

    /// Single-arch tar produced by `docker save IMAGE` (no
    /// `--platform`) has the manifest directly at the top level, no
    /// nesting. Still must work.
    #[test]
    fn flat_index_resolves_arch() {
        let layout = scratch_layout("flat-arm64");
        let manifest = json!({
            "schemaVersion": 2,
            "mediaType": "application/vnd.oci.image.manifest.v1+json",
            "config": { "mediaType": "application/vnd.oci.image.config.v1+json",
                        "digest": "sha256:dead", "size": 0 },
            "layers": [
                { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
                  "digest": "sha256:ccc", "size": 1 }
            ]
        });
        let m_sha = write_json(&layout, &manifest);
        let outer = json!({
            "schemaVersion": 2,
            "manifests": [
                { "mediaType": "application/vnd.oci.image.manifest.v1+json",
                  "digest": format!("sha256:{m_sha}"),
                  "size": 0,
                  "platform": { "architecture": "arm64", "os": "linux" } }
            ]
        });
        write_index(&layout, &outer);
        let shas = super::layer_shas_from_save_dir("test/arm64", &layout, "arm64").expect("ok");
        assert_eq!(shas, vec!["ccc".to_owned()]);
    }

    /// Multi-arch index — both platforms listed at the top level
    /// (typical registry pull). We must pick the requested arch and
    /// ignore the other.
    #[test]
    fn multi_arch_picks_requested() {
        let layout = scratch_layout("multi-arch");
        let m_arm = write_json(
            &layout,
            &json!({
                "schemaVersion": 2,
                "mediaType": "application/vnd.oci.image.manifest.v1+json",
                "config": { "digest": "sha256:c1", "size": 0,
                            "mediaType": "application/vnd.oci.image.config.v1+json" },
                "layers": [ { "digest": "sha256:arm1", "size": 1,
                              "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip" } ]
            }),
        );
        let m_amd = write_json(
            &layout,
            &json!({
                "schemaVersion": 2,
                "mediaType": "application/vnd.oci.image.manifest.v1+json",
                "config": { "digest": "sha256:c2", "size": 0,
                            "mediaType": "application/vnd.oci.image.config.v1+json" },
                "layers": [ { "digest": "sha256:amd1", "size": 1,
                              "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip" } ]
            }),
        );
        let outer = json!({
            "schemaVersion": 2,
            "manifests": [
                { "mediaType": "application/vnd.oci.image.manifest.v1+json",
                  "digest": format!("sha256:{m_arm}"), "size": 0,
                  "platform": { "architecture": "arm64", "os": "linux" } },
                { "mediaType": "application/vnd.oci.image.manifest.v1+json",
                  "digest": format!("sha256:{m_amd}"), "size": 0,
                  "platform": { "architecture": "amd64", "os": "linux" } }
            ]
        });
        write_index(&layout, &outer);
        assert_eq!(
            super::layer_shas_from_save_dir("multi", &layout, "amd64").unwrap(),
            vec!["amd1".to_owned()]
        );
        assert_eq!(
            super::layer_shas_from_save_dir("multi", &layout, "arm64").unwrap(),
            vec!["arm1".to_owned()]
        );
    }

    /// Requesting an arch that genuinely isn't in the tar still fails
    /// with the same error shape the integrator already wraps.
    #[test]
    fn missing_arch_errors() {
        let layout = scratch_layout("missing-arch");
        let m = write_json(
            &layout,
            &json!({
                "schemaVersion": 2,
                "mediaType": "application/vnd.oci.image.manifest.v1+json",
                "config": { "digest": "sha256:c", "size": 0,
                            "mediaType": "application/vnd.oci.image.config.v1+json" },
                "layers": [ { "digest": "sha256:l", "size": 1,
                              "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip" } ]
            }),
        );
        let outer = json!({
            "schemaVersion": 2,
            "manifests": [
                { "mediaType": "application/vnd.oci.image.manifest.v1+json",
                  "digest": format!("sha256:{m}"), "size": 0,
                  "platform": { "architecture": "arm64", "os": "linux" } }
            ]
        });
        write_index(&layout, &outer);
        let e = super::layer_shas_from_save_dir("only-arm", &layout, "amd64").unwrap_err();
        assert!(
            e.contains("no amd64 manifest")
                || e.contains("legacy manifest.json fallback also failed"),
            "got: {e}"
        );
    }

    /// Docker Desktop's `docker save` of multi-arch images: OCI
    /// outer image-index lists the per-arch manifest digest but
    /// the corresponding blob isn't included in the tar. The
    /// flattened legacy `manifest.json` IS, and our fallback
    /// should pick it up. Verified with `docker save python:3.12`
    /// in the integrator's environment.
    #[test]
    fn docker_save_missing_inner_manifest_falls_back_to_legacy() {
        let layout = scratch_layout("docker-save-fallback");
        // Outer index points at an inner image-index whose
        // per-arch manifest digest's BLOB is absent — exactly the
        // shape Docker Desktop produces.
        let inner_index = json!({
            "schemaVersion": 2,
            "mediaType": "application/vnd.oci.image.index.v1+json",
            "manifests": [
                { "mediaType": "application/vnd.oci.image.manifest.v1+json",
                  "digest": "sha256:dead0000missingblob",
                  "size": 2319,
                  "platform": { "architecture": "amd64", "os": "linux" } }
            ]
        });
        let inner_sha = write_json(&layout, &inner_index);
        let outer = json!({
            "schemaVersion": 2,
            "manifests": [
                { "mediaType": "application/vnd.oci.image.index.v1+json",
                  "digest": format!("sha256:{inner_sha}"),
                  "size": 0 }
            ]
        });
        write_index(&layout, &outer);

        // Drop a legacy manifest.json + a fake config blob that
        // says architecture=amd64. The fallback should find the
        // layers via this path.
        let config_bytes = serde_json::to_vec(&json!({
            "architecture": "amd64",
            "config": {}
        }))
        .unwrap();
        let config_sha = write_blob(&layout, &config_bytes);
        let legacy = json!([
            {
                "Config": format!("blobs/sha256/{config_sha}"),
                "RepoTags": ["multi-arch:latest"],
                "Layers": [
                    "blobs/sha256/layeraaa",
                    "blobs/sha256/layerbbb"
                ]
            }
        ]);
        std::fs::write(
            layout.join("manifest.json"),
            serde_json::to_vec(&legacy).unwrap(),
        )
        .unwrap();

        let shas = super::layer_shas_from_save_dir("multi-arch:latest", &layout, "amd64")
            .expect("fallback");
        assert_eq!(shas, vec!["layeraaa".to_owned(), "layerbbb".to_owned()]);
    }

    /// Legacy fallback must REFUSE if the saved image is the wrong
    /// arch — silently booting amd64 binaries on an arm64 request
    /// would be much worse than a clear error.
    #[test]
    fn legacy_fallback_rejects_wrong_arch() {
        let layout = scratch_layout("legacy-wrong-arch");
        // No OCI index path at all to force the fallback.
        write_index(&layout, &json!({ "schemaVersion": 2, "manifests": [] }));
        let config_bytes = serde_json::to_vec(&json!({
            "architecture": "arm64",
            "config": {}
        }))
        .unwrap();
        let config_sha = write_blob(&layout, &config_bytes);
        let legacy = json!([
            {
                "Config": format!("blobs/sha256/{config_sha}"),
                "RepoTags": ["x:latest"],
                "Layers": [ "blobs/sha256/lll" ]
            }
        ]);
        std::fs::write(
            layout.join("manifest.json"),
            serde_json::to_vec(&legacy).unwrap(),
        )
        .unwrap();
        let e = super::layer_shas_from_save_dir("x:latest", &layout, "amd64").unwrap_err();
        assert!(
            e.contains("describes a linux/arm64 image") && e.contains("--platform linux/amd64"),
            "expected actionable arch-mismatch hint, got: {e}"
        );
    }
}

/// Wall-clock fallback for the `--snapshot-on-listener` trigger.
/// If the workload's in-guest listener doesn't bind within this many
/// milliseconds, the worker captures whatever state the guest is in.
///
/// Default selection:
/// - `listenerRequired=true` (caller passed `--supermachine-listener-required`):
///   7000 ms. The user explicitly asked us to wait for the listener;
///   give slow-starting services (postgres, JVM apps) room to bind.
/// - Otherwise (no warmup, no listener required, or `cmd` is
///   `sleep`/`tail -f`/etc): 200 ms. There's no listener to wait for;
///   the 7s fallback is dead time. The pre-exec / parked-PID-1
///   marker fires well before this anyway.
///
/// Explicit override always wins (`--supermachine-snapshot-after-ms`
/// in extra args, or `SUPERMACHINE_SNAPSHOT_AFTER_MS` env var).
fn native_snapshot_after_ms(plan: &BakePlan<'_>) -> u64 {
    if let Some(v) = arg_value(plan.extra_args, "--supermachine-snapshot-after-ms")
        .and_then(|s| s.parse::<u64>().ok())
    {
        return v;
    }
    if let Some(v) = std::env::var("SUPERMACHINE_SNAPSHOT_AFTER_MS")
        .ok()
        .and_then(|s| s.parse::<u64>().ok())
    {
        return v;
    }
    // No override → choose by listener-required flag.
    if has_flag(plan.extra_args, "--supermachine-listener-required") {
        7000
    } else {
        200
    }
}

/// True when `name` (with no value) appears in `extra_args`. Used
/// for boolean opt-in flags like `--supermachine-listener-required`.
fn has_flag(extra_args: &[String], name: &str) -> bool {
    extra_args.iter().any(|s| s == name)
}

fn native_supermachine_base_inputs(
    plan: &BakePlan<'_>,
    resolution: &ImageResolution,
    root: &Path,
) -> Result<serde_json::Value, String> {
    let sm22_bin = supermachine_worker_bin(root);
    let kernel = supermachine_kernel(root);
    let agent = ensure_supermachine_agent(root)?;
    let snapshot_hash_mode =
        std::env::var("SUPERMACHINE_SNAPSHOT_HASH_MODE").unwrap_or_else(|_| "fast".to_owned());
    Ok(serde_json::json!({
        "version": 1,
        "runtime": "supermachine",
        "image": plan.image,
        "image_id": resolution.image_id,
        "architecture": resolution.architecture,
        "guest_port": plan.guest_port,
        "memory_mib": plan.memory_mib,
        // vcpus must be persisted in native_bake_inputs so the
        // pre-resolution cheap-input check
        // (try_fast_cache_hit, try_sibling_clone_fast_path) can
        // compare without redoing resolution. Cross-vcpu clone
        // would break the snapshot's CPU topology.
        // `architecture` is already in the json above (matches the
        // cheap-input key of the same name).
        "vcpus": plan.vcpus,
        "cmd": resolution.effective_cmd,
        // cmd_override (raw user input, pre-resolution) is
        // persisted alongside the resolved `cmd` so that
        // pre-resolution cheap-input checks (try_fast_cache_hit,
        // try_sibling_clone_fast_path) can compare cmd-by-cmd
        // without needing to redo image resolution.
        "cmd_override": plan.cmd_override,
        "working_dir": resolution.working_dir,
        "user": resolution.user,
        "env": env_json_value(plan, resolution),
        "extra_args": plan.extra_args,
        "egress_policy": arg_value(plan.extra_args, "--egress-policy").unwrap_or(""),
        "listener_settle_ms": native_listener_settle_ms(plan),
        "snapshot_after_ms": native_snapshot_after_ms(plan),
        "runtime_bin": file_size_mtime(&sm22_bin)?,
        "kernel": file_size_mtime(&kernel)?,
        "init_oci": native_init_oci_fingerprint(root)?,
        "busybox": native_busybox_fingerprint()?,
        "agent": file_size_mtime(&agent)?,
        "snapshot_hash_mode": snapshot_hash_mode,
        // 0.7.44+ networking-cmdline keys. Bake-time-only state:
        // host_ipv6 = lib's cached probe result, baked into the
        // guest's cmdline as `supermachine.host_ipv6={0|1}`.
        // guest_ipv6_disabled = user opted into `ipv6.disable=1`
        // via SUPERMACHINE_GUEST_IPV6=0. Both are immutable
        // post-bake — sibling-clone MUST not match across
        // different values (different cmdline = different boot).
        "host_ipv6": crate::utils::net::cached_host_ipv6_route(),
        "guest_ipv6_disabled":
            std::env::var("SUPERMACHINE_GUEST_IPV6").as_deref() == Ok("0"),
        // 0.7.44+ kcache opt-in. Reads the same env var the bake
        // driver checks at `bake.rs:6308`; the resulting snapshot's
        // boot shape (extra cmdline arg + paused-in-kcache-read
        // state) differs from a no-kcache bake.
        "kcache_enabled": std::env::var("SUPERMACHINE_KCACHE")
            .as_deref()
            .map(|v| v == "1" || v == "true")
            .unwrap_or(false),
    }))
}

/// Subset of [`native_supermachine_base_inputs`] that doesn't
/// require an [`ImageResolution`] — i.e. the keys whose values
/// don't depend on hitting the registry / running `inspect` on
/// the image. Used for the early cache-hit check that skips
/// `select_image_source` + `resolve_image` entirely when the
/// snapshot's stored bake key already matches on these.
///
/// **Cache semantics:** if the user re-pushed a floating tag
/// (e.g. `rust:1-slim` got rebuilt upstream), this fast path
/// won't notice — the snapshot's `image_id` could be stale and
/// we'd serve the cached snapshot anyway. That's the same
/// trade-off `docker run` makes when `--pull missing` and the
/// image is locally cached. Users who need freshness pass
/// `--pull always`, which short-circuits the fast path back to
/// the full resolve-and-rebuild flow.
fn native_supermachine_cheap_input_keys() -> &'static [&'static str] {
    &[
        "version",
        "runtime",
        "image",
        "guest_port",
        "memory_mib",
        // vCPU count is baked into the snapshot: the snapshot
        // captures per-vCPU register state + the kernel's CPU
        // topology view. Restoring a vcpus=1 snapshot under a
        // vcpus=2 pool request would either fail or silently
        // produce a 1-CPU guest. Pre-0.7.42 fast paths didn't
        // gate on it (latent bug, surfaced by `multi_vcpu.test.ts`
        // when sibling-clone could match across vcpu counts).
        "vcpus",
        // Architecture (arm64 vs amd64) determines the entire
        // snapshot: different guest kernel, different init-oci,
        // different Rosetta wiring. Cross-arch clone would
        // produce a non-bootable VM. Stored in native_bake_inputs
        // since pre-0.7.42 — backwards compat is automatic.
        "architecture",
        "extra_args",
        // The user's `cmd_override` (raw JSON argv from
        // `.with_cmd(...)` or BuildOptions.cmd) is REQUIRED in the
        // cheap input set so that two snapshots differing only in
        // cmd don't collide. Pre-0.7.42, cmd_override flowed only
        // into `native_bake_inputs.cmd` (post-resolution
        // effective_cmd) and the delta_key — neither visible at
        // the pre-resolution cheap-cache-check stage. That made
        // `try_fast_cache_hit` AND the new sibling-clone fast path
        // latently incorrect when a re-bake under the same name
        // (or a sibling under any name) changed cmd. The fix is
        // to surface cmd_override in cheap inputs directly.
        "cmd_override",
        "egress_policy",
        "listener_settle_ms",
        "snapshot_after_ms",
        "runtime_bin",
        "kernel",
        "init_oci",
        "busybox",
        // The in-guest agent is baked into the delta layer; its
        // fingerprint MUST be part of the cheap cache key. Without
        // this, shipping a new agent (e.g. adding stage_file or
        // chain support) doesn't invalidate existing snapshots,
        // and embedders silently keep using the stale agent —
        // which makes new ExecRequest fields look like no-ops.
        "agent",
        "snapshot_hash_mode",
        // 0.7.44+ networking-cmdline keys. The worker appends
        // `supermachine.host_ipv6={0|1}` and (when env asks for it)
        // `ipv6.disable=1` to the kernel cmdline at bake time. Both
        // bake INTO the snapshot — they can't be re-toggled at
        // restore. Without these in the cheap-cache key, two bakes
        // with identical other inputs but different IPv6 stances
        // collide and the second one silently reuses the first's
        // snapshot — surfaced by ipv6_works.test.ts V7 +
        // version_drift_and_ipv6.test.ts I2 when SUPERMACHINE_GUEST_IPV6=0
        // was ignored on the second bake.
        "host_ipv6",
        "guest_ipv6_disabled",
        // 0.7.44+ kcache opt-in (via SUPERMACHINE_KCACHE=1 env).
        // When set, the bake driver appends `supermachine.kcache=1`
        // to the kernel cmdline, init-oci inserts the
        // `[SUPERMACHINE-KCACHE] ready` pause point, and the host
        // orchestrator routes MARKER_KCACHE_READY → snapshot →
        // resume-byte push. The snapshot captures init-oci paused
        // inside the kcache read, fundamentally different boot
        // shape from a no-kcache bake. Without this in the cache
        // key, a no-kcache snapshot silently clones in place of a
        // kcache bake (surfaced by pl011_rx_spike.test.ts).
        "kcache_enabled",
    ]
}

fn native_supermachine_cheap_inputs(
    plan: &BakePlan<'_>,
    root: &Path,
) -> Result<serde_json::Value, String> {
    let sm22_bin = supermachine_worker_bin(root);
    let kernel = supermachine_kernel(root);
    let agent = ensure_supermachine_agent(root)?;
    let snapshot_hash_mode =
        std::env::var("SUPERMACHINE_SNAPSHOT_HASH_MODE").unwrap_or_else(|_| "fast".to_owned());
    Ok(serde_json::json!({
        "version": 1,
        "runtime": "supermachine",
        "image": plan.image,
        "guest_port": plan.guest_port,
        "memory_mib": plan.memory_mib,
        // See `native_supermachine_cheap_input_keys` for why vcpus
        // + architecture must be cheap-input keys (cross-vcpu /
        // cross-arch sibling-clone would produce a broken VM).
        // `architecture` derives from `plan.platform`'s tail
        // (`linux/amd64` → `amd64`, `linux/arm64` → `arm64`) to
        // match the post-resolution `architecture` field already
        // persisted in `native_bake_inputs` since pre-0.7.42 — so
        // existing snapshots already have the key for matching.
        "vcpus": plan.vcpus,
        "architecture": plan.arch(),
        "extra_args": plan.extra_args,
        // Surface cmd_override at the cheap-input level (see
        // `native_supermachine_cheap_input_keys` for rationale).
        // Stored verbatim — usually a JSON-encoded argv string,
        // or null when the user didn't override (image default
        // CMD is used).
        "cmd_override": plan.cmd_override,
        "egress_policy": arg_value(plan.extra_args, "--egress-policy").unwrap_or(""),
        "listener_settle_ms": native_listener_settle_ms(plan),
        "snapshot_after_ms": native_snapshot_after_ms(plan),
        "runtime_bin": file_size_mtime(&sm22_bin)?,
        "kernel": file_size_mtime(&kernel)?,
        "init_oci": native_init_oci_fingerprint(root)?,
        "busybox": native_busybox_fingerprint()?,
        "agent": file_size_mtime(&agent)?,
        "snapshot_hash_mode": snapshot_hash_mode,
        // 0.7.44+ networking-cmdline keys. Bake-time-only state:
        // host_ipv6 = lib's cached probe result, baked into the
        // guest's cmdline as `supermachine.host_ipv6={0|1}`.
        // guest_ipv6_disabled = user opted into `ipv6.disable=1`
        // via SUPERMACHINE_GUEST_IPV6=0. Both are immutable
        // post-bake — sibling-clone MUST not match across
        // different values (different cmdline = different boot).
        "host_ipv6": crate::utils::net::cached_host_ipv6_route(),
        "guest_ipv6_disabled":
            std::env::var("SUPERMACHINE_GUEST_IPV6").as_deref() == Ok("0"),
        // 0.7.44+ kcache opt-in. Reads the same env var the bake
        // driver checks at `bake.rs:6308`; the resulting snapshot's
        // boot shape (extra cmdline arg + paused-in-kcache-read
        // state) differs from a no-kcache bake.
        "kcache_enabled": std::env::var("SUPERMACHINE_KCACHE")
            .as_deref()
            .map(|v| v == "1" || v == "true")
            .unwrap_or(false),
    }))
}

/// Pre-resolution cache-hit check. Reads `metadata.json`,
/// compares the stored `native_bake_inputs` against the cheap
/// subset of inputs we can compute without calling
/// `resolve_image` (which is the slow path that runs `docker
/// inspect` / parses an OCI manifest). Returns `Some` if the
/// snapshot is reusable, `None` if we need the full resolve.
///
/// Skipped when `pull_policy = "always"` (user explicitly opted
/// into per-call upstream verification).
fn try_fast_cache_hit(
    plan: &BakePlan<'_>,
    root: &Path,
    run_t0: Instant,
) -> Result<Option<NativeBakeResult>, String> {
    if plan.pull_policy == "always" {
        return Ok(None);
    }
    if snapshot_reuse_disabled() || !native_supermachine_early_reuse_supported(plan) {
        return Ok(None);
    }
    let meta_path = plan.metadata_path();
    let text = match std::fs::read_to_string(&meta_path) {
        Ok(text) => text,
        Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
        Err(e) => return Err(format!("read metadata {}: {e}", meta_path.display())),
    };
    let meta: serde_json::Value = serde_json::from_str(&text)
        .map_err(|e| format!("parse metadata {}: {e}", meta_path.display()))?;
    if meta.get("supermachine_version").and_then(|v| v.as_str()) != Some("supermachine") {
        return Ok(None);
    }
    let Some(meta_inputs) = meta.get("native_bake_inputs").and_then(|v| v.as_object()) else {
        return Ok(None);
    };
    let cheap = native_supermachine_cheap_inputs(plan, root)?;
    let cheap_obj = cheap
        .as_object()
        .ok_or_else(|| "cheap bake inputs was not an object".to_owned())?;
    for &key in native_supermachine_cheap_input_keys() {
        if meta_inputs.get(key) != cheap_obj.get(key) {
            return Ok(None);
        }
    }
    if !metadata_snapshot_files_exist(&meta) {
        return Ok(None);
    }
    let timings = serde_json::json!({
        "total_ms": 0,
        "snapshot_reused": true,
        "fast_snapshot_reused": true,
        "reuse_check_total_ms": elapsed_ms(run_t0),
    });
    Ok(Some(NativeBakeResult {
        total_ms: 0,
        timings,
        reused: true,
    }))
}

/// Sibling-clone fast path (0.7.42+).
///
/// Sister to `try_fast_cache_hit`, but instead of checking
/// `<snap_dir>/metadata.json` (same name), it scans EVERY sibling
/// directory under `snapshots_dir` for one whose
/// `native_bake_inputs` already matches the cheap subset of the
/// requested bake. On match, `clonefile`s the sibling's snapshot
/// files into a fresh dir for `plan.snapshot_name()` and rewrites
/// `metadata.json` with the new name + paths.
///
/// Why this works:
///   * `native_supermachine_cheap_input_keys()` already enumerates
///     every input that determines the snapshot's byte content,
///     minus the registry-resolved layer set. Two snapshots with
///     identical cheap inputs (image ref, memory, cmd, env, kernel,
///     init, agent, etc.) are guaranteed to be byte-equal modulo
///     bake-time entropy that the cache key tolerates.
///   * APFS clonefile is O(metadata); the new restore.snap shares
///     all blocks with the sibling until first write.
///   * Subsequent restores from the new snapshot benefit from a
///     warm OS page cache (sibling's pages already mmap'd if its
///     pool is alive).
///
/// Opt-out: `SUPERMACHINE_SIBLING_CLONE=0`. `PullPolicy::Always`
/// also skips this (matches `try_fast_cache_hit` behavior — user
/// asked for a fresh upstream verify).
///
/// Empirical wins on alpine:latest (0.7.41 baseline 1995 ms):
///   * sibling exists → ~30-80 ms (clonefile + metadata rewrite)
///   * no sibling → returns Ok(None), full bake proceeds
fn try_sibling_clone_fast_path(
    plan: &BakePlan<'_>,
    root: &Path,
    run_t0: Instant,
) -> Result<Option<NativeBakeResult>, String> {
    if plan.pull_policy == "always" {
        return Ok(None);
    }
    if std::env::var("SUPERMACHINE_SIBLING_CLONE").as_deref() == Ok("0") {
        return Ok(None);
    }
    if snapshot_reuse_disabled() || !native_supermachine_early_reuse_supported(plan) {
        return Ok(None);
    }

    let target_dir = plan.snapshots_dir.join(plan.snapshot_name());
    let cheap = native_supermachine_cheap_inputs(plan, root)?;
    let cheap_obj = cheap
        .as_object()
        .ok_or_else(|| "cheap bake inputs was not an object".to_owned())?;

    let entries = match std::fs::read_dir(plan.snapshots_dir) {
        Ok(e) => e,
        Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
        Err(e) => return Err(format!("read snapshots dir: {e}")),
    };

    // Walk every sibling, score by `baked_at`, pick the newest
    // matching one. Newest wins because it's most likely to have
    // the warmest OS page cache (recently restored from / written
    // to). Baked-at-based ranking — not file mtime — gives
    // deterministic ordering even after long clonefile chains
    // (see comment at the rank computation below).
    let target_canon = std::fs::canonicalize(&target_dir).ok();
    let mut best: Option<(String, std::path::PathBuf)> = None;
    for entry in entries.flatten() {
        let path = entry.path();
        if !path.is_dir() {
            continue;
        }
        // Skip self.
        if target_canon
            .as_ref()
            .and_then(|c| std::fs::canonicalize(&path).ok().map(|p| p == *c))
            .unwrap_or(false)
        {
            continue;
        }
        // Skip warm variants (`<name>__warm__<tag>`); they're a
        // post-bake transform layered on top of a base snapshot
        // and not a viable clone source for a fresh bake.
        if path
            .file_name()
            .and_then(|n| n.to_str())
            .is_some_and(|n| n.contains("__warm__"))
        {
            continue;
        }
        let meta_path = path.join("metadata.json");
        let Ok(text) = std::fs::read_to_string(&meta_path) else {
            continue;
        };
        let Ok(meta): Result<serde_json::Value, _> = serde_json::from_str(&text) else {
            continue;
        };
        // Same as try_fast_cache_hit: only consider snapshots that
        // declare themselves "supermachine" (skip foreign metadata).
        if meta.get("supermachine_version").and_then(|v| v.as_str()) != Some("supermachine") {
            continue;
        }
        let Some(meta_inputs) = meta.get("native_bake_inputs").and_then(|v| v.as_object()) else {
            continue;
        };
        // ALL cheap keys must match — same image ref, same memory,
        // same cmd/env (via extra_args), same kernel/init/agent
        // fingerprint, same snapshot_hash_mode.
        //
        // Backwards-compat fallback: pre-0.7.42 metadata didn't
        // store `vcpus` or `cmd_override` inside
        // `native_bake_inputs`. `vcpus` lived at top-level meta;
        // `cmd_override` wasn't recorded at all (it flowed only
        // through `delta_key`).
        //
        // For `vcpus`: fall back to top-level meta when
        // native_bake_inputs lacks it. Without this fallback,
        // every pre-0.7.42 snapshot becomes permanently un-cloneable
        // on 0.7.42+, forcing a mass re-bake on upgrade.
        //
        // For `cmd_override`: rely on the strict comparison.
        // Pre-0.7.42 snapshots without the field will match only a
        // plan with `cmd_override = None` (both null), which is the
        // common default-CMD case — acceptable for backwards compat.
        // The (rare) user with an existing `with_cmd(...)` snapshot
        // pays a re-bake on upgrade; correctness wins over speed.
        //
        // `architecture` is in `native_bake_inputs` since pre-0.7.42
        // (the `architecture` field has always been written), so no
        // fallback needed.
        const TOPLEVEL_FALLBACK_KEYS: &[&str] = &["vcpus"];
        let mut matched = true;
        for &key in native_supermachine_cheap_input_keys() {
            let from_inputs = meta_inputs.get(key);
            let from_meta = from_inputs.or_else(|| {
                if TOPLEVEL_FALLBACK_KEYS.contains(&key) {
                    meta.get(key)
                } else {
                    None
                }
            });
            if from_meta != cheap_obj.get(key) {
                matched = false;
                break;
            }
        }
        if !matched {
            continue;
        }
        if !metadata_snapshot_files_exist(&meta) {
            continue;
        }
        // restore.snap must exist + be non-empty to be cloneable.
        let restore_path = path.join("restore.snap");
        let Ok(stat) = std::fs::metadata(&restore_path) else {
            continue;
        };
        if stat.len() == 0 {
            continue;
        }
        // Rank by `baked_at` (RFC3339-Z string), NOT file mtime.
        // Rationale: `clonefile()` preserves the source's mtime, so
        // a chain of A → cloned_to_B → cloned_to_C all share the
        // same restore.snap mtime. mtime-based ranking becomes
        // nondeterministic (depends on readdir order) and produces
        // surprising `sibling_cloned_from` chains. `baked_at` is
        // refreshed by both cold bake AND sibling-clone (see the
        // metadata rewrite in `clone_sibling_snapshot`), so the
        // most-recently-touched snapshot always wins. Falls back
        // to file mtime when baked_at is unparseable so old
        // metadata still participates.
        let rank = meta
            .get("baked_at")
            .and_then(|v| v.as_str())
            .map(ToOwned::to_owned)
            .or_else(|| {
                stat.modified().ok().and_then(|t| {
                    t.duration_since(std::time::UNIX_EPOCH)
                        .ok()
                        .map(|d| format!("@{}", d.as_secs()))
                })
            })
            .unwrap_or_default();
        if best.as_ref().is_none_or(|(r, _): &(String, _)| &rank > r) {
            best = Some((rank, path));
        }
    }

    let Some((_, sibling_dir)) = best else {
        return Ok(None);
    };

    clone_sibling_snapshot(&sibling_dir, &target_dir, run_t0)
}

/// Materialize a fresh snapshot directory at `target_dir` by
/// `clonefile`-ing the sibling's snapshot files and writing a
/// rewritten `metadata.json` with the new name + paths. The
/// resulting tree is functionally indistinguishable from a freshly
/// baked snapshot under the new name.
///
/// Files cloned (when present in sibling):
///   * `restore.snap`   — guest RAM dump (the big one)
///   * `delta.squashfs` — per-bake layer delta
///   * `init.cpio.gz`   — initramfs
///   * `env.json`       — env vars
///
/// Files rewritten:
///   * `metadata.json`  — paths repointed from sibling_dir → target_dir,
///     `name` field updated, `baked_at` refreshed, `sibling_cloned_from`
///     added for diagnostics.
///
/// Files SKIPPED (left absent in target):
///   * `bake.log` — sibling's log isn't ours; the fast path doesn't bake.
fn clone_sibling_snapshot(
    sibling_dir: &Path,
    target_dir: &Path,
    run_t0: Instant,
) -> Result<Option<NativeBakeResult>, String> {
    let t0 = Instant::now();
    std::fs::create_dir_all(target_dir)
        .map_err(|e| format!("mkdir {}: {e}", target_dir.display()))?;

    // Clonefile each materializable file via a per-file tmp + atomic
    // rename. Two concurrent bakes targeting the SAME name (rare —
    // user race-conditions a deliberate double-build, or vitest
    // fileParallelism crosses test fixtures) both observe the same
    // sibling and would race on direct `clonefile(src, dst)` with
    // dst === <target>/restore.snap. clonefile errors EEXIST on the
    // second one. The fix is the same atomic-rename pattern that
    // `dedup.rs::dedup_against` uses: clone to a unique tmp path,
    // then `std::fs::rename(tmp → dst)` which is atomic-replace on
    // APFS. Last writer wins; neither errors.
    //
    // 0.7.44+ also handles the "sibling vanished mid-clone" race:
    // between when `try_sibling_clone_fast_path` picked this sibling
    // and now, another test's `afterAll` cleanup may have `rm -rf`'d
    // it. ENOENT at any sibling-side read returns `Ok(None)` so the
    // outer bake driver falls through to a full cold bake; the
    // partial-clone files we may have written are harmless (without
    // metadata.json the dir isn't a valid snapshot, so a future
    // sibling-clone scanner skips it at `read_to_string(meta_path)`).
    const FILES: &[&str] = &["restore.snap", "delta.squashfs", "init.cpio.gz", "env.json"];
    // Per-call tmp suffix: PID + monotonic counter. Each bake gets a
    // unique suffix so concurrent bakes don't collide on tmp paths
    // either.
    let tmp_seq = TEMP_COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
    let tmp_suffix = format!(".sibclone.tmp.{}.{}", std::process::id(), tmp_seq);
    for fname in FILES {
        let src = sibling_dir.join(fname);
        let dst = target_dir.join(fname);
        // `exists()` is best-effort: races with a concurrent `rm -rf`.
        // We re-check via the `ENOENT` arm of `clonefile_one` below.
        if !src.exists() {
            // delta.squashfs / env.json are optional depending on
            // bake shape; skip missing files quietly.
            continue;
        }
        let tmp = target_dir.join(format!("{fname}{tmp_suffix}"));
        // Defensive: remove any partial leftover from an interrupted
        // prior attempt with our exact tmp name (extremely unlikely
        // — PID+counter — but safe).
        let _ = std::fs::remove_file(&tmp);
        if let Err(e) = clonefile_one(&src, &tmp) {
            // ENOENT on the source side means the sibling was
            // unlinked between our scan and now. Roll back our partial
            // work and let the caller fall through to a full bake.
            if e.kind() == std::io::ErrorKind::NotFound {
                let _ = std::fs::remove_file(&tmp);
                cleanup_partial_sibling_clone(target_dir, &tmp_suffix);
                return Ok(None);
            }
            return Err(format!(
                "clonefile {} → {}: {e}",
                src.display(),
                tmp.display()
            ));
        }
        // Atomic rename — last writer wins under concurrent bakes,
        // both succeed.
        std::fs::rename(&tmp, &dst).map_err(|e| {
            // Best-effort cleanup of the tmp; the OS would eventually
            // reap it but leaving it would surface in `du`.
            let _ = std::fs::remove_file(&tmp);
            format!("rename {} → {}: {e}", tmp.display(), dst.display())
        })?;
    }

    // Per-volume pristine bytes. 0.7.49+ snapshots ship a
    // `volumes/<i>.pristine` clone of each virtio-blk backing file
    // so cycle-restore pools can re-clone clean bytes per acquire.
    // Mirror them into the cloned snapshot or the rewritten metadata
    // would point at a sibling that may get cleaned up next.
    //
    // Best-effort: missing `volumes/` (pre-0.7.49 sibling) is fine —
    // the restore-side fallback path warns and skips per-acquire COW.
    let sib_pristine_dir = sibling_dir.join("volumes");
    if sib_pristine_dir.is_dir() {
        let tgt_pristine_dir = target_dir.join("volumes");
        std::fs::create_dir_all(&tgt_pristine_dir)
            .map_err(|e| format!("mkdir {}: {e}", tgt_pristine_dir.display()))?;
        let entries = match std::fs::read_dir(&sib_pristine_dir) {
            Ok(e) => e,
            Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
                cleanup_partial_sibling_clone(target_dir, &tmp_suffix);
                return Ok(None);
            }
            Err(e) => return Err(format!("read pristine dir: {e}")),
        };
        for entry in entries {
            let entry = match entry {
                Ok(e) => e,
                Err(_) => continue,
            };
            let fname = entry.file_name();
            let Some(fname_str) = fname.to_str() else {
                continue;
            };
            if !fname_str.ends_with(".pristine") {
                continue;
            }
            let src = entry.path();
            let dst = tgt_pristine_dir.join(&fname);
            let tmp = tgt_pristine_dir.join(format!("{fname_str}{tmp_suffix}"));
            let _ = std::fs::remove_file(&tmp);
            if let Err(e) = clonefile_one(&src, &tmp) {
                if e.kind() == std::io::ErrorKind::NotFound {
                    let _ = std::fs::remove_file(&tmp);
                    continue; // skip; mid-clone race on this pristine
                }
                return Err(format!(
                    "clonefile pristine {} → {}: {e}",
                    src.display(),
                    tmp.display()
                ));
            }
            std::fs::rename(&tmp, &dst).map_err(|e| {
                let _ = std::fs::remove_file(&tmp);
                format!("rename {} → {}: {e}", tmp.display(), dst.display())
            })?;
        }
    }

    // Read sibling's metadata.json, rewrite for the target name.
    // ENOENT here = same race as clonefile above: fall through to a
    // full bake instead of erroring out.
    let sib_meta_text = match std::fs::read_to_string(sibling_dir.join("metadata.json")) {
        Ok(t) => t,
        Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
            cleanup_partial_sibling_clone(target_dir, &tmp_suffix);
            return Ok(None);
        }
        Err(e) => return Err(format!("read sibling metadata: {e}")),
    };
    let mut meta: serde_json::Value =
        serde_json::from_str(&sib_meta_text).map_err(|e| format!("parse sibling metadata: {e}"))?;

    let sib_str = sibling_dir.to_string_lossy().into_owned();
    let tgt_str = target_dir.to_string_lossy().into_owned();
    rewrite_meta_paths_walk(&mut meta, &sib_str, &tgt_str);

    if let Some(obj) = meta.as_object_mut() {
        let target_name = target_dir
            .file_name()
            .and_then(|n| n.to_str())
            .map(ToOwned::to_owned)
            .unwrap_or_default();
        obj.insert("name".to_owned(), serde_json::json!(target_name));
        let now_secs = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map(|d| d.as_secs())
            .unwrap_or(0);
        // Match the existing baked_at format: "YYYY-MM-DDTHH:MM:SSZ".
        // Cheap formatter; bake.rs already takes a serde_json
        // dependency but not chrono, so we hand-roll the
        // RFC3339-with-Z form.
        obj.insert(
            "baked_at".to_owned(),
            serde_json::json!(rfc3339_z(now_secs)),
        );
        obj.insert(
            "sibling_cloned_from".to_owned(),
            serde_json::json!(sibling_dir.display().to_string()),
        );
    }

    let new_meta_text =
        serde_json::to_string_pretty(&meta).map_err(|e| format!("encode meta: {e}"))?;
    // Atomic write of metadata.json — same tmp+rename pattern as the
    // restore.snap / delta.squashfs clones above. Prevents partial-
    // metadata states under concurrent bakes targeting the same name.
    let meta_dst = target_dir.join("metadata.json");
    let meta_tmp = target_dir.join(format!("metadata.json{tmp_suffix}"));
    std::fs::write(&meta_tmp, new_meta_text + "\n")
        .map_err(|e| format!("write meta tmp {}: {e}", meta_tmp.display()))?;
    std::fs::rename(&meta_tmp, &meta_dst).map_err(|e| {
        let _ = std::fs::remove_file(&meta_tmp);
        format!(
            "rename {} → {}: {e}",
            meta_tmp.display(),
            meta_dst.display()
        )
    })?;

    let total_ms = elapsed_ms(run_t0);
    if trace_enabled() {
        eprintln!(
            "[sibling-clone] OK in {} ms: {} → {} (total bake {} ms)",
            t0.elapsed().as_millis(),
            sibling_dir.display(),
            target_dir.display(),
            total_ms,
        );
    }
    Ok(Some(NativeBakeResult {
        total_ms,
        timings: serde_json::json!({
            "total_ms": total_ms,
            "snapshot_reused": true,
            "sibling_cloned": true,
            "sibling": sibling_dir.display().to_string(),
            "clone_ms": t0.elapsed().as_millis() as u64,
        }),
        reused: true,
    }))
}

/// Best-effort cleanup of any restore.snap/delta.squashfs/etc. that
/// we cloned into the target before a mid-clone race forced us to
/// abandon the fast path. Also wipes any leftover `.sibclone.tmp.*`
/// files that may not have made it to the atomic rename. The full
/// bake that runs next writes everything fresh, so we just need to
/// avoid leaving inconsistent files that another concurrent
/// sibling-clone scanner could pick up.
///
/// We DON'T delete the directory itself — the caller may have other
/// files in there (e.g. an in-progress metadata.json from a prior
/// attempt) we shouldn't touch.
fn cleanup_partial_sibling_clone(target_dir: &Path, tmp_suffix: &str) {
    const FILES: &[&str] = &["restore.snap", "delta.squashfs", "init.cpio.gz", "env.json"];
    for fname in FILES {
        let _ = std::fs::remove_file(target_dir.join(fname));
        let _ = std::fs::remove_file(target_dir.join(format!("{fname}{tmp_suffix}")));
    }
    // Don't touch metadata.json — if it wasn't us who wrote it, leave
    // it alone. We only got here because the sibling was unlinked
    // mid-clone, so our metadata write never happened anyway.
}

/// Copy-on-write file clone (O(metadata), shares blocks until written).
/// macOS: APFS `clonefile`. Linux: the `FICLONE` ioctl (reflink, on btrfs/XFS).
/// Both return a "not supported" error cross-volume or on a non-reflink FS
/// (e.g. ext4) — the caller decides to fall back to a real copy or surface it
/// (current callers surface it), so the semantics match across platforms.
pub(crate) fn clonefile_one(src: &Path, dst: &Path) -> std::io::Result<()> {
    #[cfg(target_os = "macos")]
    {
        use std::os::unix::ffi::OsStrExt;
        let src_c = std::ffi::CString::new(src.as_os_str().as_bytes())
            .map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidInput, "path NUL"))?;
        let dst_c = std::ffi::CString::new(dst.as_os_str().as_bytes())
            .map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidInput, "path NUL"))?;
        // SAFETY: paths are valid C strings (no interior NUL).
        let ret = unsafe { libc::clonefile(src_c.as_ptr(), dst_c.as_ptr(), 0) };
        if ret != 0 {
            return Err(std::io::Error::last_os_error());
        }
        return Ok(());
    }
    #[cfg(not(target_os = "macos"))]
    {
        use std::os::unix::io::AsRawFd;
        // FICLONE = _IOW(0x94, 9, int). Clones the whole file CoW. Fails with
        // EOPNOTSUPP/EXDEV on a non-reflink FS or across devices → caller copies.
        const FICLONE: libc::c_ulong = 0x4004_9409;
        let src_f = std::fs::File::open(src)?;
        let dst_f = std::fs::OpenOptions::new()
            .write(true)
            .create(true)
            .truncate(true)
            .open(dst)?;
        // `as _`: ioctl's request parameter is c_ulong on glibc but c_int on
        // musl — the portable spelling for the same bit pattern.
        let ret = unsafe { libc::ioctl(dst_f.as_raw_fd(), FICLONE as _, src_f.as_raw_fd()) };
        if ret != 0 {
            let err = std::io::Error::last_os_error();
            // Don't leave a truncated dst behind for the caller's copy fallback.
            let _ = std::fs::remove_file(dst);
            return Err(err);
        }
        return Ok(());
    }
    #[allow(unreachable_code)]
    Ok(())
}

/// Materialize `dst` as a content-identical copy of `src`. Tries
/// APFS clonefile first (metadata-only, near-instant), falls back
/// to `std::fs::copy` (full byte copy) on any clonefile failure
/// (cross-device, non-APFS, target-exists, etc.).
///
/// The fallback unlinks `dst` first because `std::fs::copy` opens
/// the destination with `O_TRUNC` not `O_CREAT|O_EXCL`, which is
/// fine, but on the clonefile retry path the target may already
/// exist from a previous attempt — unlink defensively.
///
/// Used by:
/// 1. **Bake**: capturing per-snapshot pristine copies of each
///    virtio-blk volume's host backing file. Pristine lives at
///    `<snapshot_dir>/volumes/<i>.pristine` alongside the snapshot.
/// 2. **Pool acquire**: re-cloning each pristine to a per-worker
///    temp file so the worker's writes don't corrupt the pristine
///    (and so the next worker spawn starts from clean bytes).
///
/// On APFS the clonefile path is ~µs (the OS just bumps refcount
/// on the underlying extents); on non-APFS we pay one real copy.
pub(crate) fn cow_or_copy(src: &Path, dst: &Path) -> std::io::Result<()> {
    // Best-effort: drop any stale target so clonefile (which fails
    // with EEXIST if dst already exists) gets a clean slate.
    let _ = std::fs::remove_file(dst);
    match clonefile_one(src, dst) {
        Ok(()) => Ok(()),
        Err(_) => {
            // Slow path: real copy. Sufficient correctness, just
            // pays for IO bandwidth on non-APFS filesystems.
            std::fs::copy(src, dst).map(|_| ())
        }
    }
}

/// Capture a "pristine" copy of each virtio-blk volume's backing
/// file alongside the snapshot artifacts. Returns the per-volume
/// pristine path (absolute, under `<out_dir>/volumes/<i>.pristine`)
/// in the same order as the input volumes.
///
/// The pristine captures the volume's bytes at snapshot-save time
/// — i.e. whatever the bake's warmup wrote into the volume,
/// committed by the bake-time FLUSH. Because the snapshot's RAM
/// also captures ext4's in-RAM bitmap / journal-pointer state at
/// the same moment, the pristine bytes match what ext4 expects
/// to find on disk after a restore. Re-baking from the pristine
/// on each acquire keeps RAM and disk in lock-step.
///
/// Per-snapshot, captured once at bake. Subsequent acquires
/// clonefile the pristine to a per-worker temp file (cheap on
/// APFS); writes during the worker's life land on the temp, not
/// the pristine. So the pristine is **effectively immutable** —
/// no reader/writer lock needed.
///
/// Errors during capture are surfaced as the bake's error: a
/// snapshot that can't restore its volumes is worse than no
/// snapshot at all.
pub(crate) fn capture_volume_pristines(
    out_dir: &Path,
    volumes: &[VolumeMapping],
) -> Result<Vec<PathBuf>, String> {
    if volumes.is_empty() {
        return Ok(Vec::new());
    }
    let pristine_dir = out_dir.join("volumes");
    std::fs::create_dir_all(&pristine_dir)
        .map_err(|e| format!("create pristine dir {}: {e}", pristine_dir.display()))?;
    let mut out = Vec::with_capacity(volumes.len());
    for (i, v) in volumes.iter().enumerate() {
        let dst = pristine_dir.join(format!("{i}.pristine"));
        cow_or_copy(&v.host_file, &dst).map_err(|e| {
            format!(
                "capture volume pristine {} → {}: {e}",
                v.host_file.display(),
                dst.display()
            )
        })?;
        out.push(dst);
    }
    Ok(out)
}

/// Walk a JSON value, replacing every string starting with
/// `src_prefix` with the same string but with `src_prefix` → `dst_prefix`.
///
/// Used to rewrite the self-referential absolute paths in a
/// sibling's `metadata.json` (e.g. `delta_squashfs`, `init_cpio`)
/// when we clone its tree under a different name. Matches at
/// arbitrary depth (object keys are not rewritten — only values).
fn rewrite_meta_paths_walk(v: &mut serde_json::Value, src_prefix: &str, dst_prefix: &str) {
    match v {
        serde_json::Value::String(s) => {
            if s.starts_with(src_prefix) {
                let tail = &s[src_prefix.len()..];
                *s = format!("{dst_prefix}{tail}");
            }
        }
        serde_json::Value::Array(arr) => {
            for item in arr.iter_mut() {
                rewrite_meta_paths_walk(item, src_prefix, dst_prefix);
            }
        }
        serde_json::Value::Object(obj) => {
            for (_, val) in obj.iter_mut() {
                rewrite_meta_paths_walk(val, src_prefix, dst_prefix);
            }
        }
        _ => {}
    }
}

/// Format a Unix epoch second as `YYYY-MM-DDTHH:MM:SSZ` (the
/// shape `baked_at` uses elsewhere). Hand-rolled to avoid a chrono
/// dependency just for this one timestamp.
fn rfc3339_z(secs: u64) -> String {
    // Days since 1970-01-01 (Thursday).
    let total_days = (secs / 86_400) as i64;
    let mut sec_of_day = (secs % 86_400) as u32;
    let hour = sec_of_day / 3600;
    sec_of_day %= 3600;
    let min = sec_of_day / 60;
    let sec = sec_of_day % 60;

    // Convert days-since-epoch to civil date. Standard algorithm
    // from Howard Hinnant's date library.
    let z = total_days + 719_468;
    let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
    let doe = (z - era * 146_097) as u64; // [0, 146096]
    let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146_096) / 365; // [0, 399]
    let y = yoe as i64 + era * 400;
    let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); // [0, 365]
    let mp = (5 * doy + 2) / 153; // [0, 11]
    let d = doy - (153 * mp + 2) / 5 + 1; // [1, 31]
    let m = if mp < 10 { mp + 3 } else { mp - 9 }; // [1, 12]
    let y = if m <= 2 { y + 1 } else { y };
    format!("{y:04}-{m:02}-{d:02}T{hour:02}:{min:02}:{sec:02}Z")
}

fn native_supermachine_bake_key(
    plan: &BakePlan<'_>,
    resolution: &ImageResolution,
    layer_plan: &LayerPlan,
    delta: &DeltaMaterialization,
    root: &Path,
) -> Result<(String, serde_json::Value), String> {
    let delta_key = delta
        .key
        .as_deref()
        .ok_or_else(|| "native supermachine bake needs delta key".to_owned())?;
    let mut inputs = native_supermachine_base_inputs(plan, resolution, root)?;
    let obj = inputs
        .as_object_mut()
        .ok_or_else(|| "native bake inputs was not an object".to_owned())?;
    obj.insert(
        "layers".to_owned(),
        serde_json::json!(layer_plan.layer_shas),
    );
    obj.insert("delta_key".to_owned(), serde_json::json!(delta_key));
    let encoded =
        serde_json::to_string(&inputs).map_err(|e| format!("encode native bake key: {e}"))?;
    let key = sha256_text(&format!("{encoded}\n"))?;
    Ok((key, inputs))
}

fn metadata_string(value: &serde_json::Value, key: &str) -> Option<String> {
    value.get(key)?.as_str().map(ToOwned::to_owned)
}

fn metadata_path_value(meta: &serde_json::Value, key: &str) -> Option<PathBuf> {
    metadata_string(meta, key).map(PathBuf::from)
}

fn metadata_path_exists(meta: &serde_json::Value, key: &str) -> bool {
    metadata_path_value(meta, key)
        .as_deref()
        .map(Path::exists)
        .unwrap_or(false)
}

fn snapshot_reuse_disabled() -> bool {
    // SUPERMACHINE_SNAPSHOT_REUSE was a pre-0.7.38 kill-switch
    // for the input-hash-based cache reuse. Deleted; reuse is
    // always on. The PullPolicy::Always API option gives the
    // same effect when callers explicitly want a fresh bake.
    false
}

/// Look up the cmd (argv) from a previous bake's metadata.json so
/// re-running without `--cmd` keeps the user's last-known intent.
/// Returns None if the metadata doesn't exist, doesn't have `cmd`,
/// or its image differs from the requested one.
fn previous_metadata_cmd(plan: &BakePlan<'_>) -> Option<Vec<String>> {
    let meta_path = plan.metadata_path();
    let text = std::fs::read_to_string(&meta_path).ok()?;
    let meta: serde_json::Value = serde_json::from_str(&text).ok()?;
    if meta.get("image").and_then(|v| v.as_str()) != Some(plan.image) {
        return None;
    }
    let arr = meta.get("cmd")?.as_array()?;
    let mut out = Vec::with_capacity(arr.len());
    for v in arr {
        out.push(v.as_str()?.to_owned());
    }
    if out.is_empty() {
        None
    } else {
        Some(out)
    }
}

fn metadata_snapshot_files_exist(meta: &serde_json::Value) -> bool {
    if !metadata_path_exists(meta, "snapshot_base")
        || !metadata_path_exists(meta, "delta_squashfs")
        || !metadata_path_exists(meta, "init_cpio")
    {
        return false;
    }
    meta.get("layers")
        .and_then(|v| v.as_array())
        .map(|layers| {
            !layers.is_empty()
                && layers.iter().all(|layer| {
                    layer
                        .as_str()
                        .map(Path::new)
                        .map(Path::exists)
                        .unwrap_or(false)
                })
        })
        .unwrap_or(false)
}

fn native_supermachine_early_reuse_supported(plan: &BakePlan<'_>) -> bool {
    !arg_present(plan.extra_args, "--extra-file")
        && !arg_present(plan.extra_args, "--inbound-tls-autogen")
        && arg_value(plan.extra_args, "--inbound-tls-cert").is_none()
        && arg_value(plan.extra_args, "--inbound-tls-key").is_none()
}

fn native_supermachine_early_reuse_snapshot(
    plan: &BakePlan<'_>,
    resolution: &ImageResolution,
    root: &Path,
    run_t0: Instant,
) -> Result<Option<NativeBakeResult>, String> {
    if snapshot_reuse_disabled() || !native_supermachine_early_reuse_supported(plan) {
        return Ok(None);
    }
    let meta_path = plan.metadata_path();
    let text = match std::fs::read_to_string(&meta_path) {
        Ok(text) => text,
        Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
        Err(e) => return Err(format!("read metadata {}: {e}", meta_path.display())),
    };
    let meta: serde_json::Value = serde_json::from_str(&text)
        .map_err(|e| format!("parse metadata {}: {e}", meta_path.display()))?;
    if meta.get("supermachine_version").and_then(|v| v.as_str()) != Some("supermachine") {
        return Ok(None);
    }
    let Some(meta_inputs) = meta.get("native_bake_inputs").and_then(|v| v.as_object()) else {
        return Ok(None);
    };
    let current_inputs = native_supermachine_base_inputs(plan, resolution, root)?;
    let current_obj = current_inputs
        .as_object()
        .ok_or_else(|| "native base inputs was not an object".to_owned())?;
    for (key, value) in current_obj {
        if meta_inputs.get(key) != Some(value) {
            return Ok(None);
        }
    }
    if !metadata_snapshot_files_exist(&meta) {
        return Ok(None);
    }
    let timings = serde_json::json!({
        "total_ms": 0,
        "snapshot_reused": true,
        "early_snapshot_reused": true,
        "reuse_check_total_ms": elapsed_ms(run_t0),
    });
    Ok(Some(NativeBakeResult {
        total_ms: 0,
        timings,
        reused: true,
    }))
}

fn native_supermachine_reuse_snapshot(
    plan: &BakePlan<'_>,
    expected_bake_key: &str,
    run_t0: Instant,
) -> Result<Option<NativeBakeResult>, String> {
    if snapshot_reuse_disabled() {
        return Ok(None);
    }
    let meta_path = plan.metadata_path();
    let text = match std::fs::read_to_string(&meta_path) {
        Ok(text) => text,
        Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
        Err(e) => return Err(format!("read metadata {}: {e}", meta_path.display())),
    };
    let meta: serde_json::Value = serde_json::from_str(&text)
        .map_err(|e| format!("parse metadata {}: {e}", meta_path.display()))?;
    if meta.get("supermachine_version").and_then(|v| v.as_str()) != Some("supermachine") {
        return Ok(None);
    }
    if meta.get("native_bake_key").and_then(|v| v.as_str()) != Some(expected_bake_key) {
        return Ok(None);
    }
    if !metadata_snapshot_files_exist(&meta) {
        return Ok(None);
    }
    let total_ms = 0;
    let timings = serde_json::json!({
        "total_ms": total_ms,
        "snapshot_reused": true,
        "reuse_check_total_ms": elapsed_ms(run_t0),
    });
    Ok(Some(NativeBakeResult {
        total_ms,
        timings,
        reused: true,
    }))
}

fn run_native_supermachine_bake(
    plan: &BakePlan<'_>,
    resolution: &ImageResolution,
    layer_plan: &LayerPlan,
    delta: &DeltaMaterialization,
    root: &Path,
    native_bake_key: &str,
    native_bake_inputs: &serde_json::Value,
) -> Result<NativeBakeResult, String> {
    let total_t0 = Instant::now();
    let out_dir = plan.snapshots_dir.join(plan.snapshot_name());
    std::fs::create_dir_all(&out_dir)
        .map_err(|e| format!("create snapshot dir {}: {e}", out_dir.display()))?;

    let delta_cache = delta
        .cache_path
        .as_ref()
        .filter(|p| p.is_file())
        .ok_or_else(|| "native supermachine bake needs materialized delta cache".to_owned())?;
    let squash_t0 = Instant::now();
    let delta_squashfs = out_dir.join("delta.squashfs");
    std::fs::copy(delta_cache, &delta_squashfs).map_err(|e| {
        format!(
            "copy delta cache {} -> {}: {e}",
            delta_cache.display(),
            delta_squashfs.display()
        )
    })?;
    let squashfs_ms = elapsed_ms(squash_t0);

    let env_json = write_env_json(plan, resolution, &out_dir)?;
    let (init_cpio, initramfs_ms) = native_initramfs(root, &out_dir)?;

    let sm22_bin = supermachine_worker_bin(root);
    let kernel = supermachine_kernel(root);
    let snap_base = out_dir.join("restore.snap");
    let log = out_dir.join("bake.log");
    let _ = std::fs::remove_file(&snap_base);
    let log_file = std::fs::File::create(&log)
        .map_err(|e| format!("create bake log {}: {e}", log.display()))?;
    let log_err = log_file
        .try_clone()
        .map_err(|e| format!("clone bake log fd: {e}"))?;
    let listener_settle_ms = native_listener_settle_ms(plan);
    let snapshot_after_ms = native_snapshot_after_ms(plan);
    let host_time = std::time::SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map_err(|e| format!("host time before epoch: {e}"))?
        .as_secs();

    let bake_t0 = Instant::now();
    // Per-snapshot TSI control-channel auth token. Generated once
    // here, baked into the kernel's BSS via the cmdline arg below,
    // persisted to metadata.json so every restore re-injects the
    // same bytes (kernel BSS is captured in the snapshot's memory
    // image; mismatched tokens at restore would break the muxer
    // check). See generate_tsi_token_hex doc.
    let tsi_token_hex = generate_tsi_token_hex()?;
    let mut cmd = Command::new(&sm22_bin);
    cmd.arg("--kernel")
        .arg(&kernel)
        .arg("--initramfs")
        .arg(&init_cpio)
        .arg("--tsi-token")
        .arg(&tsi_token_hex);
    let layer_paths: Vec<PathBuf> = layer_plan
        .layer_shas
        .iter()
        .map(|sha| layer_plan.cache_dir.join(format!("{sha}.squashfs")))
        .collect();
    for layer in &layer_paths {
        cmd.arg("--virtio-blk").arg(layer);
    }
    cmd.arg("--virtio-blk").arg(&delta_squashfs);
    // Writable volumes — pass each as `--volume HOST_FILE:GUEST_PATH`
    // so the worker's CLI parser turns them into VmResources::volumes.
    // Volume order matches /.supermachine-volumes (written into the
    // delta squashfs at delta-stage time) so init-oci can map
    // /dev/vd<letter> → guest mount point.
    let volumes = parse_volume_args(plan.extra_args)?;
    for vol in &volumes {
        ensure_volume_host_file(vol, vol.size_bytes)?;
        cmd.arg("--volume").arg(format!(
            "{}:{}:{}",
            vol.host_file.display(),
            vol.guest_path,
            vol.size_bytes,
        ));
    }
    // virtio-fs mounts — `--mount HOST:TAG:GUEST_PATH[:POLICY]`.
    // Resolved relative to CWD; we pass the canonical absolute host
    // path so the worker isn't sensitive to its own CWD. POLICY
    // (optional) is one of {deny, opaque, follow}; omitted ⇒
    // default `opaque`.
    for raw in arg_values(plan.extra_args, "--mount") {
        let parts: Vec<&str> = raw.splitn(4, ':').collect();
        let (host, tag, guest_path, policy) = match parts.len() {
            3 => (parts[0], parts[1], parts[2], None),
            4 => (parts[0], parts[1], parts[2], Some(parts[3])),
            _ => {
                return Err(format!(
                    "--mount expects HOST:TAG:GUEST_PATH[:POLICY], got {raw:?}"
                ));
            }
        };
        if host.is_empty() || tag.is_empty() || guest_path.is_empty() {
            return Err(format!(
                "--mount HOST:TAG:GUEST_PATH has empty field: {raw:?}"
            ));
        }
        let abs = std::fs::canonicalize(host).map_err(|e| format!("--mount {host}: {e}"))?;
        let encoded = match policy {
            None => format!("{}:{}:{}", abs.display(), tag, guest_path),
            Some(p) => format!("{}:{}:{}:{}", abs.display(), tag, guest_path, p),
        };
        cmd.arg("--mount").arg(encoded);
    }
    // 0.7.44+ pre-compute host IPv6 (lib-cached) for the worker.
    cmd.env(
        "SUPERMACHINE_HOST_IPV6",
        // host_ipv6_route (not cached_…): explicit env override wins,
        // matching the worker's and api.rs's precedence.
        if crate::utils::net::host_ipv6_route() {
            "1"
        } else {
            "0"
        },
    );
    cmd.arg("--memory")
        .arg(plan.memory_mib.to_string())
        .arg("--vcpus")
        .arg(plan.vcpus.to_string())
        .arg("--cmdline")
        .arg({
            // `quiet loglevel=4` suppresses kernel printks below
            // KERN_WARNING during boot. Each printk to the pl011
            // serial driver is a synchronous MMIO write — at
            // ~1 µs per char + ~1000 boot lines averaging 60
            // chars = ~60 ms of pure serial I/O burned during
            // kernel init. Userspace writes via /dev/console
            // (init-oci heartbeats + "parking PID 1" markers we
            // depend on for bake triggers) still go through —
            // they bypass the kernel-level printk filter.
            //
            // Override per-bake via `--cmdline ...` in extra_args
            // if you actually want the kernel boot trace (e.g.
            // diagnosing a guest crash). Append user-supplied
            // `--cmdline-extra TOKEN` entries (power-user / test
            // escape hatch for forcing panics etc.) after our
            // defaults; supermachine.tsi_token then appended by
            // the worker.
            let mut s = format!(
                "earlycon=pl011,mmio32,0x09000000 console=ttyAMA0 quiet loglevel=4 \
                 tsi_hijack supermachine.host_time={host_time}"
            );
            for extra in arg_values(plan.extra_args, "--cmdline-extra") {
                s.push(' ');
                s.push_str(extra);
            }
            s
        });
    // Snapshot trigger:
    //   - Without volumes: --snapshot-on-listener — fastest path,
    //     captures the workload mid-run with its listener bound.
    //     If no listener appears within --snapshot-after-ms, the
    //     worker falls back to capturing whatever state the guest
    //     is in (typically init-oci parked after a non-service
    //     workload exited — rust:1-slim, python:slim, etc.).
    //   - With volumes: --snapshot-at 1 — captures init-oci's
    //     pre-mount state at the heartbeat marker. Each restore
    //     re-runs mount_volumes() against the *current* host file
    //     contents, dodging the page-cache vs. on-disk drift that
    //     would otherwise corrupt the FS across runs. The
    //     trade-off is a slower restore (must complete workload
    //     init post-restore) — acceptable for stateful workloads.
    if volumes.is_empty() {
        cmd.arg("--snapshot-on-listener")
            .arg("--snapshot-after-ms")
            .arg(snapshot_after_ms.to_string())
            .arg("--quiesce-ms")
            .arg(listener_settle_ms.to_string());
    } else {
        cmd.arg("--snapshot-at").arg("1");
    }
    cmd.arg("--snapshot-out")
        .arg(&snap_base)
        .arg("--env-file")
        .arg(&env_json)
        .stdout(Stdio::from(log_file))
        .stderr(Stdio::from(log_err));
    let mut child = cmd
        .spawn()
        .map_err(|e| format!("spawn supermachine bake: {e}"))?;

    let mut listener_ready_ms = None;
    let mut snapshot_trigger_ms = None;
    let mut snapshot_line_ms = None;
    let deadline = Instant::now() + std::time::Duration::from_secs(240);
    while Instant::now() < deadline {
        if listener_ready_ms.is_none() && log_has(&log, "listener readiness") {
            listener_ready_ms = Some(elapsed_ms(bake_t0));
        }
        if snapshot_trigger_ms.is_none() && log_has(&log, "snapshot trigger (") {
            snapshot_trigger_ms = Some(elapsed_ms(bake_t0));
        }
        if snapshot_line_ms.is_none() && log_has(&log, "snapshot (") {
            snapshot_line_ms = Some(elapsed_ms(bake_t0));
        }
        if snap_base.is_file() {
            let _ = child.wait();
            break;
        }
        if child
            .try_wait()
            .map_err(|e| format!("poll supermachine bake: {e}"))?
            .is_some()
        {
            break;
        }
        if let Some((first_line, stack)) = detect_kernel_panic(&log) {
            let _ = child.kill();
            let _ = child.wait();
            return Err(encode_kernel_panic_err(&first_line, &stack));
        }
        if log_has_failure(&log) {
            let _ = child.kill();
            let _ = child.wait();
            return Err(format!("supermachine bake failed; see {}", log.display()));
        }
        std::thread::sleep(std::time::Duration::from_millis(50));
    }
    let vmm_bake_ms = elapsed_ms(bake_t0);
    if !snap_base.is_file() {
        // If we're here, the 240s deadline fired. Last-chance panic
        // check on the log file — covers the (rare) case where the
        // panic banner only landed in the final 50ms poll gap.
        if let Some((first_line, stack)) = detect_kernel_panic(&log) {
            let _ = child.kill();
            let _ = child.wait();
            return Err(encode_kernel_panic_err(&first_line, &stack));
        }
        let _ = child.kill();
        let _ = child.wait();
        return Err(format!(
            "supermachine snapshot timeout; see {}",
            log.display()
        ));
    }
    if listener_ready_ms.is_none() && log_has(&log, "listener readiness") {
        listener_ready_ms = Some(elapsed_ms(bake_t0));
    }
    if snapshot_trigger_ms.is_none() && log_has(&log, "snapshot trigger (") {
        snapshot_trigger_ms = Some(elapsed_ms(bake_t0));
    }
    if snapshot_line_ms.is_none() && log_has(&log, "snapshot (") {
        snapshot_line_ms = Some(elapsed_ms(bake_t0));
    }

    let snapshot_line = last_line_containing(&log, "snapshot (").unwrap_or_default();
    let snapshot_reason = parse_snapshot_reason(&snapshot_line).unwrap_or_default();
    let (snapshot_capture_us, snapshot_save_us) = parse_capture_save_us(&snapshot_line);
    let (snapshot_ram_data_mib, snapshot_ram_zero_mib) = parse_ram_mib(&snapshot_line);
    let snapshot_logical_bytes = std::fs::metadata(&snap_base).ok().map(|m| m.len());
    let snapshot_physical_bytes = file_physical_bytes(&snap_base);
    let listener_to_trigger_ms = listener_ready_ms
        .zip(snapshot_trigger_ms)
        .map(|(a, b)| b.saturating_sub(a));
    let listener_to_snapshot_ms = listener_ready_ms
        .zip(snapshot_line_ms)
        .map(|(a, b)| b.saturating_sub(a));

    let metadata_t0 = Instant::now();
    let runtime_sha = sha256_file(&sm22_bin)?;
    let runtime_sha16 = &runtime_sha[..runtime_sha.len().min(16)];
    let hash_mode =
        std::env::var("SUPERMACHINE_SNAPSHOT_HASH_MODE").unwrap_or_else(|_| "fast".to_owned());
    let snapshot_id = compute_snapshot_id(plan, &snap_base, runtime_sha16, &hash_mode)?;
    let baked_at = now_utc_iso()?;
    let egress_policy = arg_value(plan.extra_args, "--egress-policy").unwrap_or("");
    let balloon_target_pages = compute_balloon_target_pages(plan.memory_mib);
    let metadata_prepare_ms = elapsed_ms(metadata_t0);
    let total_ms = elapsed_ms(total_t0);
    let timings = serde_json::json!({
        "total_ms": total_ms,
        "pull_inspect_ms": resolution.inspect_ms,
        "rootfs_prepare_ms": layer_plan.plan_ms,
        "rootfs_customize_ms": delta.prepare_ms,
        "init_oci_build_ms": 0,
        "squashfs_ms": squashfs_ms,
        "initramfs_ms": initramfs_ms,
        "vmm_bake_ms": vmm_bake_ms,
        "metadata_prepare_ms": metadata_prepare_ms,
        "guest_boot_to_listener_ms": listener_ready_ms,
        "listener_settle_config_ms": listener_settle_ms,
        "listener_to_snapshot_trigger_ms": listener_to_trigger_ms,
        "listener_to_snapshot_ms": listener_to_snapshot_ms,
        "snapshot_capture_us": snapshot_capture_us,
        "snapshot_save_us": snapshot_save_us,
        "snapshot_reason": snapshot_reason,
    });
    // Volumes: writable virtio-blk attachments. Host file path and
    // guest mount point are both runtime — restore needs both to
    // re-attach. We store the resolved host_file (so the router
    // doesn't need to recompute the name → path mapping) and the
    // bake-fingerprinted guest_path.
    // `size_bytes` is captured so `Image::from_snapshot` can replay
    // the `--volume HOST:GUEST:SIZE` flag at pool-spawn time. Without
    // it, the warm-restored worker would lack the virtio-blk device,
    // but the guest's restored mount table still references it →
    // ext4 reads hang on /dev/vd<N> with no backing device.
    // (Field-report bug fixed 0.7.30.)
    //
    // Pristines: 0.7.49+ also captures a "pristine" clonefile of
    // each volume backing file alongside the snapshot. Pool acquires
    // with restoreOnRelease=true re-clone the pristine to a per-
    // worker temp file so the worker's writes never leak between
    // acquires — keeps the virtio-blk bytes in lock-step with the
    // snapshot's captured ext4 RAM state. Without this, the second
    // acquire's first lstat against a path the first acquire
    // modified can return -EBADMSG (ext4's cached bitmap / journal
    // pointer disagrees with on-disk bytes the first acquire
    // rewrote).
    let parsed_volumes = parse_volume_args(plan.extra_args)?;
    let volume_pristines = capture_volume_pristines(&out_dir, &parsed_volumes)?;
    let volumes_meta: Vec<serde_json::Value> = parsed_volumes
        .iter()
        .zip(volume_pristines.iter())
        .map(|(v, pristine)| {
            serde_json::json!({
                "host_file": v.host_file.to_string_lossy(),
                "guest_path": v.guest_path,
                "size_bytes": v.size_bytes,
                "pristine": pristine.to_string_lossy(),
            })
        })
        .collect();

    // virtio-fs mount specs. Persisted into metadata so `from_snapshot`
    // can reconstruct the same VirtioFs devices on restore (the FDT
    // baked at boot expects the same number of virtio-mmio devices
    // at the same MMIO addresses).
    //
    // Wire encoding: `--mount HOST:TAG:GUEST_PATH[:POLICY]`. Every
    // mount carries a `guest_path`; init-oci auto-mounts each share
    // at that path before the workload starts.
    let mounts_meta: Vec<serde_json::Value> = arg_values(plan.extra_args, "--mount")
        .into_iter()
        .filter_map(parse_mount_arg)
        .collect();

    // Restart policy. Default `no` (docker default) — watchdog
    // doesn't auto-respawn unless the user opted in. Accepted:
    // `no`, `on-failure`, `always`. CLI normalizes
    // `unless-stopped` → `always` since our daemon goes away on
    // `--stop`.
    let restart_policy = arg_value(plan.extra_args, "--restart").unwrap_or("no");

    // Health check command + interval. The router's per-snapshot
    // health thread runs the command via the in-guest exec agent
    // every `health_interval` seconds and tracks pass/fail.
    // Empty cmd → no health check (default).
    let health_cmd = arg_value(plan.extra_args, "--health-cmd").unwrap_or("");
    let health_interval_secs: u32 = arg_value(plan.extra_args, "--health-interval")
        .and_then(|s| s.parse().ok())
        .unwrap_or(if health_cmd.is_empty() { 0 } else { 30 });

    let metadata = serde_json::json!({
        "name": plan.snapshot_name(),
        "image": plan.image,
        "port": plan.guest_port,
        "memory_mib": plan.memory_mib,
        "cmd": resolution.effective_cmd,
        "snapshot_sha16": snapshot_id,
        "snapshot_id16": snapshot_id,
        "snapshot_hash_mode": hash_mode,
        "runtime_sha16": runtime_sha16,
        "layers": layer_paths,
        "volumes": volumes_meta,
        "mounts": mounts_meta,
        "restart_policy": restart_policy,
        "health_cmd": health_cmd,
        "health_interval_secs": health_interval_secs,
        "delta_squashfs": delta_squashfs,
        "rootfs_squashfs": serde_json::Value::Null,
        "snapshot_base": snap_base,
        "snapshot_logical_bytes": snapshot_logical_bytes,
        "snapshot_physical_bytes": snapshot_physical_bytes,
        "snapshot_ram_data_mib": snapshot_ram_data_mib,
        "snapshot_ram_zero_mib": snapshot_ram_zero_mib,
        "init_cpio": init_cpio,
        "kernel": kernel,
        // Immutable version stamp. Set at fresh bake; never rewritten
        // by reuse paths. Used by `warn_if_snapshot_version_mismatch`
        // to detect when a snapshot was baked under different binaries
        // than the current process. The `kernel` field above carries
        // the current path and is unreliable here — it gets overwritten
        // on cache-miss re-bakes; this field stays fixed.
        "baked_by_version": env!("CARGO_PKG_VERSION"),
        "egress_policy": egress_policy,
        "vcpus": plan.vcpus,
        "ttl_seconds": serde_json::Value::Null,
        "egress_bps": serde_json::Value::Null,
        "cpu_nice": serde_json::Value::Null,
        "cpu_affinity": serde_json::Value::Null,
        "cpu_qos": serde_json::Value::Null,
        "balloon_target_pages": balloon_target_pages,
        "auth": {"type": "none"},
        "native_bake_key": native_bake_key,
        "native_bake_inputs": native_bake_inputs,
        "timings": timings,
        "baked_at": baked_at,
        "supermachine_version": "supermachine",
        // Per-snapshot TSI control-channel auth token. Captured
        // here (rather than rotated at restore) because the
        // kernel's `static u8 tsi_auth_token[32]` is part of the
        // snapshot's memory image. See
        // `kernel-build/patches/af-tsi/0014-*.patch`.
        "tsi_token": tsi_token_hex,
    });
    let meta_path = out_dir.join("metadata.json");
    std::fs::write(
        &meta_path,
        serde_json::to_vec_pretty(&metadata).map_err(|e| format!("encode metadata: {e}"))?,
    )
    .map_err(|e| format!("write metadata {}: {e}", meta_path.display()))?;

    Ok(NativeBakeResult {
        total_ms,
        timings,
        reused: false,
    })
}

/// Pipelined-bake variant of [`run_native_supermachine_bake`].
///
/// Differences from the sequential bake:
///   * Worker is launched WITHOUT `--snapshot-out` and WITH
///     `--pool-worker <ctl>`. The runner detects bake-then-pool
///     (`pool_mode && restore_from.is_none() && snapshot_out.is_none()`)
///     and writes `BAKE_READY` to the supervisor socket on the
///     first readiness trigger (listener-ready / workload-parked /
///     wall-clock fallback) instead of capturing.
///   * After `BAKE_READY`, this function sends `SNAPSHOT_ASYNC
///     <base>` — runner pauses, captures into a compact in-memory
///     buffer (~50 ms for ~100 MiB of non-zero pages), kicks off a
///     background save thread, returns `DONE_SNAPSHOT_ASYNC`
///     immediately. The guest is unpaused for the warmup workload
///     while the disk write happens in parallel.
///   * Then invokes the user warmup callback (vsock-exec is live).
///   * Then sends `SNAPSHOT <warm>` (sync, streaming) for the
///     warm snapshot.
///   * Finally `QUIT` — the runner drains any in-flight async
///     saves before exiting, so by the time `wait()` returns
///     both `<base>` and `<warm>` are on disk.
///
/// Metadata: writes both `<base>/metadata.json` and
/// `<warm>/metadata.json` so either can be loaded by
/// `Image::from_snapshot`.
#[allow(clippy::too_many_arguments)]
/// Read one supervisor line, transparently skipping any
/// `SAVE_DONE <path>` / `SAVE_FAIL <path> ...` notifications that
/// the worker's bg async-save thread emits on completion. Those
/// arrive on the same supervisor channel as the request/response
/// protocol but aren't responses to the bake driver's own reads —
/// they're orthogonal "I finished a save you previously asked me
/// to start" announcements. Returns the first non-SAVE line.
///
/// This exists because the bake's SNAPSHOT_ASYNC kicks off a bg
/// save that completes WHILE the driver is doing later work
/// (warmup callback, warm SNAPSHOT). The bg save's SAVE_DONE
/// would otherwise interleave with the driver's read of the next
/// DONE_SNAPSHOT and break the line-oriented protocol parse.
/// 0.7.43+ kernel-boot cache mode for a bake. Selected once at
/// `run_native_supermachine_bake_pipelined` start and threaded
/// through the cmdline/env construction and the READY→BAKE_READY
/// orchestration.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum KcacheMode {
    /// Cache not enabled or not applicable (with-warmup path,
    /// kill-switch set). Bake runs as if 0.7.42.
    Disabled,
    /// First novel bake for this (kernel + init-oci + memory +
    /// vcpus) tuple on this host. Bake driver takes the kcache
    /// snapshot when MARKER_KCACHE_READY fires, then pushes the
    /// resume token, then waits for BAKE_READY as normal.
    Write,
    /// Cache file exists for this tuple. Bake driver passes
    /// `--restore-from <kcache.snap>` to the worker spawn; the
    /// worker's `supermachine-kcache-resume` thread pushes the
    /// resume token ~100 ms after spawn (allowing restore + GIC
    /// setup to complete). Bake driver just waits for BAKE_READY.
    Hit,
}

fn read_supervisor_line_skip_save_notifications(
    reader: &mut std::io::BufReader<std::os::unix::net::UnixStream>,
    line: &mut String,
) -> std::io::Result<()> {
    use std::io::BufRead;
    loop {
        line.clear();
        let n = reader.read_line(line)?;
        if n == 0 {
            return Err(std::io::Error::new(
                std::io::ErrorKind::UnexpectedEof,
                "supervisor closed mid-protocol",
            ));
        }
        let trimmed = line.trim();
        if trimmed.starts_with("SAVE_DONE ") || trimmed.starts_with("SAVE_FAIL ") {
            // Bg save thread's notification — orthogonal to the
            // request/response protocol. Skip and read the next
            // line. We could surface this to a higher-level save-
            // tracker for true async save coordination later;
            // for now, the bake's later SNAPSHOT-warm + QUIT path
            // already drains and synchronizes, so just skipping
            // is correct.
            continue;
        }
        // 0.7.43+: MARKER_KCACHE_READY is now ROUTED, not skipped.
        // The bake-WRITE orchestrator in `run_native_supermachine_
        // bake_pipelined` reads this line directly and reacts with
        // SNAPSHOT_ASYNC + PUSH_RX. Other callers (in the existing
        // bake flow) don't enter the kcache window so won't see
        // the line at all (the marker fires before BAKE_READY).
        return Ok(());
    }
}

/// Total wall-clock ceiling for a single pipelined warm-bake (boot + base
/// snapshot + the user warmup callback + warm snapshot). A stuck warmup — e.g.
/// a guest that idles in WFI mid-`npm install` because the workload is wedged —
/// would otherwise hang the host driver FOREVER: the supervisor reader blocks
/// in `read_line`, and the warmup callback blocks in its vsock `exec`. The
/// [`BakeDeadlineGuard`] SIGKILLs the worker when this elapses, which EOFs every
/// host-side wait so the driver returns a typed error instead of wedging.
///
/// Default 20 min — generous enough for a heavy `npm install` warmup, low
/// enough to fail a genuinely-stuck bake in bounded time. Override with
/// `SUPERMACHINE_BAKE_DEADLINE_SECS`; `0` disables the guard (for debugging a
/// legitimately very long warmup).
fn bake_deadline_secs() -> u64 {
    std::env::var("SUPERMACHINE_BAKE_DEADLINE_SECS")
        .ok()
        .and_then(|s| s.trim().parse::<u64>().ok())
        .unwrap_or(1200)
}

/// RAII watchdog that SIGKILLs the bake worker if the bake exceeds
/// [`bake_deadline_secs`]. Armed right after the worker spawns and dropped at
/// every driver return path (success or error), so the timer is always
/// cancelled cleanly on a healthy bake. Unlike the panic-watcher (joined at
/// `BAKE_READY`), this spans the WHOLE bake including the warmup phase — which
/// is exactly where a wedged guest hangs. On expiry it sets `timed_out` so the
/// driver's error mapper can surface a clear "exceeded deadline" message.
struct BakeDeadlineGuard {
    stop: std::sync::Arc<std::sync::atomic::AtomicBool>,
    handle: Option<std::thread::JoinHandle<()>>,
}

impl BakeDeadlineGuard {
    fn arm(
        child_pid: i32,
        secs: u64,
        timed_out: std::sync::Arc<std::sync::atomic::AtomicBool>,
    ) -> Self {
        // `secs == 0` disables the guard (no thread, never fires).
        if secs == 0 {
            return Self {
                stop: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
                handle: None,
            };
        }
        Self::arm_with(child_pid, Duration::from_secs(secs), timed_out)
    }

    /// `arm` with an explicit `Duration` (so tests can use a sub-second
    /// deadline). Always spawns the watchdog thread.
    fn arm_with(
        child_pid: i32,
        dur: Duration,
        timed_out: std::sync::Arc<std::sync::atomic::AtomicBool>,
    ) -> Self {
        use std::sync::atomic::Ordering;
        let stop = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
        let handle = {
            let stop_c = stop.clone();
            std::thread::Builder::new()
                .name("supermachine-bake-deadline".into())
                .stack_size(64 * 1024)
                .spawn(move || {
                    let deadline = Instant::now() + dur;
                    while !stop_c.load(Ordering::Acquire) {
                        if Instant::now() >= deadline {
                            timed_out.store(true, Ordering::Release);
                            eprintln!(
                                "[supermachine] bake exceeded {}s deadline \
                                 (stuck warmup?); SIGKILL worker pid {child_pid}",
                                dur.as_secs()
                            );
                            // Closes the worker's socket fds → the supervisor
                            // reader EOFs and the warmup exec errors, unblocking
                            // the driver. Kill-after-reap is a harmless ESRCH.
                            unsafe {
                                libc::kill(child_pid, libc::SIGKILL);
                            }
                            return;
                        }
                        std::thread::sleep(Duration::from_millis(200));
                    }
                })
                .ok()
        };
        Self { stop, handle }
    }
}

impl Drop for BakeDeadlineGuard {
    fn drop(&mut self) {
        self.stop.store(true, std::sync::atomic::Ordering::Release);
        if let Some(h) = self.handle.take() {
            let _ = h.join();
        }
    }
}

fn run_native_supermachine_bake_pipelined(
    plan: &BakePlan<'_>,
    resolution: &ImageResolution,
    layer_plan: &LayerPlan,
    delta: &DeltaMaterialization,
    root: &Path,
    native_bake_key: &str,
    native_bake_inputs: &serde_json::Value,
    pipelined: PipelinedWarmup<'_>,
    // Out-param for the warm-handoff path. When `pipelined.keep_alive`
    // is true and the bake completes successfully, the driver fills
    // this with the live worker handle; caller stashes it on the
    // returned Image. When false (or on error), stays None.
    keep_alive_out: &mut Option<BakedWorker>,
) -> Result<NativeBakeResult, String> {
    use std::io::{BufReader, Write};
    use std::os::unix::net::UnixListener;

    let total_t0 = Instant::now();
    let out_dir = plan.snapshots_dir.join(plan.snapshot_name());
    std::fs::create_dir_all(&out_dir)
        .map_err(|e| format!("create snapshot dir {}: {e}", out_dir.display()))?;

    // 0.7.43+ kernel-boot cache mode selection. Computed once at
    // function start so the cmdline / env / spawn args + the
    // READY→BAKE_READY orchestration agree.
    //
    // DEFAULT OFF as of post-shipping measurement: empirical
    // benchmarks (docs/design/kcache-perf-finding-2026-05-21.md +
    // sibling_clone_coverage.test.ts + kcache_cross_image_bench.
    // test.ts) found that:
    //   * Sibling-clone fast path (0.7.42) already covers every
    //     non-first-on-host bake at 11-15 ms. That's the case
    //     that matters in practice.
    //   * kcache's pre-pivot cache point only skips ~75 ms of
    //     boot work, which is offset by ~75 ms of restore +
    //     retry-loop overhead — net delta is in the noise.
    //   * Cross-image novel-bake measurement (the one case
    //     sibling-clone CAN'T help) also showed no measurable
    //     savings from kcache.
    //
    // The infrastructure stays — PL011 RX, marker protocol,
    // PUSH_RX, KcacheMode are all useful building blocks for
    // future image-specific cache points if they ever become
    // worthwhile. Users who want to experiment can set
    // SUPERMACHINE_KCACHE=1.
    let kcache_enabled = std::env::var("SUPERMACHINE_KCACHE")
        .as_deref()
        .map(|v| v == "1" || v == "true")
        .unwrap_or(false)
        && pipelined.skip_warm_snapshot;
    let kcache_path = if kcache_enabled {
        kernel_boot_cache_snap_path(plan, root).ok()
    } else {
        None
    };
    let kcache_mode: KcacheMode = match (kcache_enabled, kcache_path.as_ref()) {
        (true, Some(p)) if p.is_file() => KcacheMode::Hit,
        (true, Some(_)) => KcacheMode::Write,
        _ => KcacheMode::Disabled,
    };
    if matches!(kcache_mode, KcacheMode::Write | KcacheMode::Hit) {
        // Lazy mkdir; cheap, idempotent. `create_dir_all` is the
        // atomic-rename target's parent dir, not the .snap file.
        if let Some(p) = kcache_path.as_ref() {
            if let Some(parent) = p.parent() {
                let _ = std::fs::create_dir_all(parent);
            }
        }
    }
    if trace_enabled() {
        eprintln!(
            "[kcache] mode={:?} path={:?}",
            kcache_mode,
            kcache_path.as_ref().map(|p| p.display().to_string())
        );
    }
    if !pipelined.skip_warm_snapshot {
        std::fs::create_dir_all(&pipelined.warm_dir).map_err(|e| {
            format!(
                "create warm snapshot dir {}: {e}",
                pipelined.warm_dir.display()
            )
        })?;
    }

    let delta_cache = delta
        .cache_path
        .as_ref()
        .filter(|p| p.is_file())
        .ok_or_else(|| "pipelined bake needs materialized delta cache".to_owned())?;
    let squash_t0 = Instant::now();
    let delta_squashfs = out_dir.join("delta.squashfs");
    std::fs::copy(delta_cache, &delta_squashfs).map_err(|e| {
        format!(
            "copy delta cache {} -> {}: {e}",
            delta_cache.display(),
            delta_squashfs.display()
        )
    })?;
    let squashfs_ms = elapsed_ms(squash_t0);

    let env_json = write_env_json(plan, resolution, &out_dir)?;
    let (init_cpio, initramfs_ms) = native_initramfs(root, &out_dir)?;

    let sm22_bin = supermachine_worker_bin(root);
    let kernel = supermachine_kernel(root);
    let snap_base = out_dir.join("restore.snap");
    let snap_warm = pipelined.warm_dir.join("restore.snap");
    let log = out_dir.join("bake.log");
    let _ = std::fs::remove_file(&snap_base);
    let _ = std::fs::remove_file(&snap_warm);
    let log_file = std::fs::File::create(&log)
        .map_err(|e| format!("create bake log {}: {e}", log.display()))?;
    let log_err = log_file
        .try_clone()
        .map_err(|e| format!("clone bake log fd: {e}"))?;
    let listener_settle_ms = native_listener_settle_ms(plan);
    let snapshot_after_ms = native_snapshot_after_ms(plan);
    let host_time = std::time::SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map_err(|e| format!("host time before epoch: {e}"))?
        .as_secs();

    // Per-bake unique sockets. Unix socket paths cap at 104 bytes
    // on macOS (SUN_LEN); `std::env::temp_dir()` resolves to
    // `/var/folders/4q/<10-char-hash>/T/` (~50 chars) on a typical
    // mac, leaving only ~50 chars for our dir + socket names.
    // `vsock-exec.sock` is 15 chars; with a `supermachine-bake-
    // <pid>-<unixtime>-<counter>` dir name we hit exactly 104
    // chars (= SUN_LEN's strict-less-than bound is violated) and
    // bake fails with "path must be shorter than SUN_LEN".
    //
    // Workaround: use `/tmp` (5 chars) instead of $TMPDIR. The
    // full `supermachine-bake-` prefix stays (project policy is
    // never to abbreviate the brand); even with that we end up
    // at ~50 chars, well under the cap.
    let suffix = format!(
        "{:x}-{}",
        std::process::id(),
        TEMP_COUNTER.fetch_add(1, Ordering::Relaxed)
    );
    let socks_dir = std::path::PathBuf::from(format!("/tmp/supermachine-bake-{suffix}"));
    std::fs::create_dir_all(&socks_dir)
        .map_err(|e| format!("create bake socks dir {}: {e}", socks_dir.display()))?;
    let vsock_mux_path = socks_dir.join("vsock-mux.sock");
    let vsock_exec_path = socks_dir.join("vsock-exec.sock");
    let ctl_path = socks_dir.join("ctl.sock");
    let _ = std::fs::remove_file(&vsock_mux_path);
    let _ = std::fs::remove_file(&vsock_exec_path);
    let _ = std::fs::remove_file(&ctl_path);

    // Bind the ctl listener BEFORE spawning so the worker's
    // connect() always finds it.
    let ctl_listener = UnixListener::bind(&ctl_path)
        .map_err(|e| format!("bind bake ctl socket {}: {e}", ctl_path.display()))?;

    let bake_t0 = Instant::now();
    // Per-snapshot TSI auth token — see the doc on
    // generate_tsi_token_hex + the sequential-bake counterpart.
    let tsi_token_hex = generate_tsi_token_hex()?;
    let mut cmd = Command::new(&sm22_bin);
    cmd.arg("--kernel")
        .arg(&kernel)
        .arg("--initramfs")
        .arg(&init_cpio)
        .arg("--tsi-token")
        .arg(&tsi_token_hex);
    let layer_paths: Vec<PathBuf> = layer_plan
        .layer_shas
        .iter()
        .map(|sha| layer_plan.cache_dir.join(format!("{sha}.squashfs")))
        .collect();
    for layer in &layer_paths {
        cmd.arg("--virtio-blk").arg(layer);
    }
    cmd.arg("--virtio-blk").arg(&delta_squashfs);
    let volumes = parse_volume_args(plan.extra_args)?;
    for vol in &volumes {
        ensure_volume_host_file(vol, vol.size_bytes)?;
        cmd.arg("--volume").arg(format!(
            "{}:{}:{}",
            vol.host_file.display(),
            vol.guest_path,
            vol.size_bytes,
        ));
    }
    // virtio-fs mounts — `--mount HOST:TAG:GUEST_PATH[:POLICY]`.
    // Resolved relative to CWD; we pass the canonical absolute host
    // path so the worker isn't sensitive to its own CWD. POLICY
    // (optional) is one of {deny, opaque, follow}; omitted ⇒
    // default `opaque`.
    for raw in arg_values(plan.extra_args, "--mount") {
        let parts: Vec<&str> = raw.splitn(4, ':').collect();
        let (host, tag, guest_path, policy) = match parts.len() {
            3 => (parts[0], parts[1], parts[2], None),
            4 => (parts[0], parts[1], parts[2], Some(parts[3])),
            _ => {
                return Err(format!(
                    "--mount expects HOST:TAG:GUEST_PATH[:POLICY], got {raw:?}"
                ));
            }
        };
        if host.is_empty() || tag.is_empty() || guest_path.is_empty() {
            return Err(format!(
                "--mount HOST:TAG:GUEST_PATH has empty field: {raw:?}"
            ));
        }
        let abs = std::fs::canonicalize(host).map_err(|e| format!("--mount {host}: {e}"))?;
        let encoded = match policy {
            None => format!("{}:{}:{}", abs.display(), tag, guest_path),
            Some(p) => format!("{}:{}:{}:{}", abs.display(), tag, guest_path, p),
        };
        cmd.arg("--mount").arg(encoded);
    }
    // 0.7.44+ pre-compute host IPv6 (lib-cached) for the worker.
    cmd.env(
        "SUPERMACHINE_HOST_IPV6",
        // host_ipv6_route (not cached_…): explicit env override wins,
        // matching the worker's and api.rs's precedence.
        if crate::utils::net::host_ipv6_route() {
            "1"
        } else {
            "0"
        },
    );
    cmd.arg("--memory")
        .arg(plan.memory_mib.to_string())
        .arg("--vcpus")
        .arg(plan.vcpus.to_string())
        .arg("--cmdline")
        .arg({
            // See `run_native_supermachine_bake` for the
            // `quiet loglevel=4` rationale (~60 ms saved
            // per-bake on serial-printk floor). Append
            // user-supplied --cmdline-extra tokens after our
            // defaults so e.g. `init=/nope` reaches the kernel
            // for deliberate-panic tests.
            let mut s = format!(
                "earlycon=pl011,mmio32,0x09000000 console=ttyAMA0 quiet loglevel=4 \
                 tsi_hijack supermachine.host_time={host_time}"
            );
            // 0.7.43+ kcache opt-in via kernel cmdline. init-oci's
            // scan of /proc/cmdline reacts to this and inserts the
            // [SUPERMACHINE-KCACHE] ready pause point.
            if !matches!(kcache_mode, KcacheMode::Disabled) {
                s.push_str(" supermachine.kcache=1");
            }
            // 0.7.44+ pre-exec sync opt-in. Active only for skip-warm
            // bakes that use the pre-exec trigger — the bake driver
            // will push the unblock byte after the on-pre-exec
            // snapshot save (runner.rs's snapshot handler), and the
            // pool spawn_one path will set SUPERMACHINE_PRE_EXEC_SYNC_RESUME=1
            // so restored workers push immediately. Both are
            // required: without the bake push, the bake hangs; without
            // the restore push, the first acquire hangs.
            //
            // Env override: `SUPERMACHINE_PRE_EXEC_SYNC=0` forces
            // the legacy nanosleep path back on. The bake driver
            // honors that override here.
            let pre_exec_sync_enabled = pipelined.use_pre_exec_trigger
                && std::env::var("SUPERMACHINE_PRE_EXEC_SYNC")
                    .map(|v| v != "0" && v != "false")
                    .unwrap_or(true);
            if pre_exec_sync_enabled {
                s.push_str(" supermachine.pre_exec_sync=1");
            }
            for extra in arg_values(plan.extra_args, "--cmdline-extra") {
                s.push(' ');
                s.push_str(extra);
            }
            s
        });
    // 0.7.43+ kcache env wiring:
    //   * disable serial.rs auto-push fallback so the bake driver
    //     controls the resume-byte timing (CRITICAL for cache-WRITE:
    //     we need to take the snapshot BEFORE the push).
    //   * cache-HIT also opts the worker into the post-restore
    //     delayed-push thread (which fires after restore + GIC
    //     setup are complete).
    if !matches!(kcache_mode, KcacheMode::Disabled) {
        cmd.env("SUPERMACHINE_KCACHE_AUTO_PUSH", "0");
    }
    if matches!(kcache_mode, KcacheMode::Hit) {
        cmd.env("SUPERMACHINE_KCACHE_RESUME", "1");
        if let Some(p) = kcache_path.as_ref() {
            // `--kcache-restore-from` (not `--restore-from`) — see
            // worker.rs for why the dedicated flag is necessary
            // (keeps the worker's bake-then-pool protocol mode
            // active so this code's BAKE_READY wait still works).
            cmd.arg("--kcache-restore-from").arg(p);
        }
    }
    // Readiness triggers — the runner turns these into BAKE_READY
    // (no out_path, bake_then_pool detected) instead of an auto-
    // snapshot.
    //
    // For the always-pipelined-skip-warm path we add
    // `--snapshot-on-pre-exec`: init-oci's "workload-pre-exec"
    // marker fires bake-ready BEFORE the workload starts, saving
    // 50-150 ms vs waiting for the listener. The on_listener
    // trigger stays as backup for service-image bakes (warmup
    // path) and as a safety net.
    if volumes.is_empty() {
        // 0.7.0+: when pre-exec is the primary trigger, the wall-
        // clock timer is just a safety net for a broken init that
        // never fires the marker (genuinely-stuck guest). Set it
        // generously high — pre-exec fires in ~1-2s even for
        // heavy multi-vCPU bakes; 30s only fires if something is
        // truly wrong. Pre-0.7.1 used the 200ms `snapshot_after_ms`
        // value here too, which RACED with pre-exec on heavy bakes
        // (multi-vCPU PSCI CPU_ON bring-up takes >200ms) and the
        // timer would win mid-boot, capturing a corrupt snapshot
        // with vCPUs canceled.
        //
        // When pre-exec is NOT enabled (listener-required path),
        // the timer IS the primary fallback for slow-binding
        // services and stays at the user-tunable value
        // (snapshot_after_ms, 7000ms default).
        let after_ms = if pipelined.use_pre_exec_trigger {
            30_000
        } else {
            snapshot_after_ms
        };
        cmd.arg("--snapshot-on-listener")
            .arg("--snapshot-after-ms")
            .arg(after_ms.to_string())
            .arg("--quiesce-ms")
            .arg(listener_settle_ms.to_string());
        // 0.7.0: pre-exec is now eligible for warmup-bearing bakes
        // too (was previously gated on `skip_warm_snapshot`). The
        // pre-exec marker fires at a deterministic point in init-oci
        // (right before `execve` of the workload), and the warmup
        // callback's vm.exec channel is up independent of the
        // workload's state — so BASE-on-pre-exec + warmup callback
        // + WARM-on-promise-completion gives a snapshot trigger
        // chain with NO timer fallbacks anywhere.
        if pipelined.use_pre_exec_trigger {
            cmd.arg("--snapshot-on-pre-exec");
        }
    } else {
        cmd.arg("--snapshot-at").arg("1");
    }
    // Critical: NO --snapshot-out. WITH --pool-worker. This is
    // what trips the runner's `bake_then_pool` detection.
    cmd.arg("--env-file")
        .arg(&env_json)
        .arg("--vsock-mux")
        .arg(&vsock_mux_path)
        .arg("--vsock-exec")
        .arg(&vsock_exec_path)
        .arg("--pool-worker")
        .arg(&ctl_path)
        .stdout(Stdio::from(log_file))
        .stderr(Stdio::from(log_err));
    let spawn_t0 = Instant::now();
    let mut child = cmd
        .spawn()
        .map_err(|e| format!("spawn pipelined-bake worker: {e}"))?;
    if crate::trace::enabled("phases") {
        eprintln!(
            "[phases] worker spawn ({:?} since bake start)",
            total_t0.elapsed(),
        );
    }

    // Whole-bake deadline guard. Spans boot + base snapshot + the warmup
    // callback + warm snapshot (unlike the panic-watcher below, which is joined
    // at BAKE_READY and so can't catch a wedged warmup). On expiry it SIGKILLs
    // the worker — EOFing the supervisor reader and erroring the warmup exec —
    // and sets `bake_timed_out` so `take_panic_err` surfaces a clear message.
    // Dropped on every return path, cancelling the timer on a healthy bake.
    let bake_timed_out = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
    let _bake_deadline_guard = BakeDeadlineGuard::arm(
        child.id() as i32,
        bake_deadline_secs(),
        bake_timed_out.clone(),
    );

    // Kernel-panic watcher.
    //
    // The pipelined supervisor protocol reads on a UNIX-socket
    // request/response — `read_supervisor_line_skip_save_notifications`
    // blocks indefinitely. If the guest kernel panics during init
    // (e.g. the TSI auth-token parser fault we hit on some 0.6.0
    // bakes), the worker never writes BAKE_READY, never sends any
    // protocol line, and the bake hangs for the caller's full
    // timeout — typically 10+ minutes.
    //
    // The watcher thread tails `bake.log` (the worker's serial
    // console + tracing output) for known panic banners. On match
    // it captures the next ~40 lines of stack, sets the shared
    // `panic_info` slot, and SIGKILLs the worker — which closes
    // its UNIX socket end, which unblocks the supervisor reader
    // with EOF, which lets us return the typed error instead of
    // hanging. See `KERNEL_PANIC_PATTERNS` for the matched strings.
    let panic_info: std::sync::Arc<std::sync::Mutex<Option<(String, Vec<String>)>>> =
        std::sync::Arc::new(std::sync::Mutex::new(None));
    let watcher_stop = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
    // 0.7.43+: interruptible-sleep channel so `watcher_handle.join()`
    // at BAKE_READY doesn't have to wait for the current 100 ms
    // sleep to finish. Phase tracing found `join()` took ~60 ms
    // median on every bake — pure overhead, paid even when the
    // watcher is healthy. Replacing the bare `sleep` with
    // `recv_timeout` cuts that to ~0 ms.
    let (watcher_stop_tx, watcher_stop_rx) = std::sync::mpsc::channel::<()>();
    let watcher_handle = {
        let panic_info = panic_info.clone();
        let watcher_stop = watcher_stop.clone();
        let log_for_watcher = log.clone();
        let child_pid = child.id() as i32;
        std::thread::Builder::new()
            .name("supermachine-bake-panic-watcher".into())
            .stack_size(64 * 1024)
            .spawn(move || {
                loop {
                    if watcher_stop.load(std::sync::atomic::Ordering::Acquire) {
                        return;
                    }
                    if let Some((first, stack)) = detect_kernel_panic(&log_for_watcher) {
                        *panic_info.lock().unwrap() = Some((first, stack));
                        // SIGKILL: close worker's socket fds → reader EOF.
                        // Safety: just an FFI call with two scalars; the
                        // pid is owned by `child` (still alive in the
                        // parent scope), so this races with `child.wait()`
                        // at worst — kill-after-wait is a no-op ESRCH.
                        unsafe {
                            libc::kill(child_pid, libc::SIGKILL);
                        }
                        return;
                    }
                    // recv_timeout returns Ok on send, RecvTimeoutError::Timeout
                    // on timeout. Either way we re-check the loop conditions.
                    // Send happens from the bake driver at any cleanup point;
                    // wakes us immediately so join() returns fast.
                    match watcher_stop_rx.recv_timeout(Duration::from_millis(100)) {
                        Ok(()) => return,
                        Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {}
                        Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => return,
                    }
                }
            })
            .expect("spawn supermachine-bake-panic-watcher")
    };

    // Helper: snapshot the watcher state on any error path. If the
    // watcher caught a panic, hand back the encoded sentinel string
    // so `map_bake_error` produces `Error::KernelPanic`. Otherwise
    // the caller's own message wins.
    let take_panic_err = |fallback: String| -> String {
        // A deadline kill wins over the generic "supervisor closed" / "warmup
        // failed" fallback — those are just the symptom of the SIGKILL.
        if bake_timed_out.load(std::sync::atomic::Ordering::Acquire) {
            return format!(
                "pipelined bake: exceeded the {}s deadline and the worker was killed \
                 (a stuck warmup — the guest idled in WFI with no forward progress). \
                 Raise or disable via SUPERMACHINE_BAKE_DEADLINE_SECS. See {}",
                bake_deadline_secs(),
                log.display()
            );
        }
        if let Some((first_line, stack)) = panic_info.lock().unwrap().take() {
            encode_kernel_panic_err(&first_line, &stack)
        } else {
            fallback
        }
    };

    // Accept the worker's ctl connection. Worker connect()s as
    // soon as it reaches the supervisor handshake — well before
    // any HVF setup.
    ctl_listener
        .set_nonblocking(true)
        .map_err(|e| format!("set ctl listener nonblocking: {e}"))?;
    let deadline = Instant::now() + Duration::from_secs(10);
    let mut backoff = Duration::from_millis(1);
    let stream = loop {
        match ctl_listener.accept() {
            Ok((s, _)) => break s,
            Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
                if Instant::now() > deadline {
                    let _ = child.kill();
                    let _ = child.wait();
                    let _ = std::fs::remove_dir_all(&socks_dir);
                    return Err(format!(
                        "pipelined bake: worker ctl connect did not arrive within 10s; \
                         see {}",
                        log.display()
                    ));
                }
                std::thread::sleep(backoff);
                backoff = (backoff * 2).min(Duration::from_millis(10));
            }
            Err(e) => {
                let _ = child.kill();
                let _ = child.wait();
                let _ = std::fs::remove_dir_all(&socks_dir);
                return Err(format!("pipelined bake: ctl accept: {e}"));
            }
        }
    };
    stream
        .set_nonblocking(false)
        .map_err(|e| format!("set ctl stream blocking: {e}"))?;
    let writer = stream
        .try_clone()
        .map_err(|e| format!("clone ctl stream: {e}"))?;
    let mut reader = BufReader::new(stream);
    let mut writer = writer;

    // Read READY.
    let mut line = String::new();
    if let Err(e) = read_supervisor_line_skip_save_notifications(&mut reader, &mut line) {
        watcher_stop.store(true, std::sync::atomic::Ordering::Release);
        let _ = watcher_handle.join();
        let _ = child.kill();
        let _ = child.wait();
        let _ = std::fs::remove_dir_all(&socks_dir);
        return Err(take_panic_err(format!("pipelined bake: read READY: {e}")));
    }
    if crate::trace::enabled("phases") {
        eprintln!(
            "[phases] READY received ({:?} since bake start, {:?} since spawn)",
            total_t0.elapsed(),
            spawn_t0.elapsed(),
        );
    }
    if line.trim() != "READY" {
        watcher_stop.store(true, std::sync::atomic::Ordering::Release);
        let _ = watcher_handle.join();
        let _ = child.kill();
        let _ = child.wait();
        let _ = std::fs::remove_dir_all(&socks_dir);
        return Err(take_panic_err(format!(
            "pipelined bake: expected READY, got {:?}; see {}",
            line.trim(),
            log.display()
        )));
    }

    // Wait for BAKE_READY. The runner emits this on the first
    // readiness trigger (listener-ready / workload-parked /
    // wall-clock fallback). Bounded; if init crashes we want a
    // clean error rather than hanging.
    //
    // 0.7.43+ kcache-WRITE intercept: before BAKE_READY can fire,
    // init-oci hits the kcache pause point and the worker's
    // marker watcher emits MARKER_KCACHE_READY. We respond by
    // taking a cache snapshot (SNAPSHOT_ASYNC) then pushing the
    // 'R\n' resume token via PUSH_RX 52 + PUSH_RX 0a. init-oci
    // then continues and eventually emits BAKE_READY normally.
    //
    // For kcache-HIT, the worker's `supermachine-kcache-resume`
    // thread does the push autonomously ~100 ms after spawn, so
    // we just see BAKE_READY without a MARKER (init-oci was
    // already paused in the cached snapshot, no new marker print).
    //
    // For kcache-DISABLED, neither branch fires; the existing
    // helper skips MARKER_KCACHE_READY silently.
    let bake_ready_t0 = Instant::now();
    let mut listener_ready_ms: Option<u128> = None;
    let mut kcache_write_done = false;
    if crate::trace::enabled("phases") {
        eprintln!(
            "[phases] entering BAKE_READY loop ({:?} since bake start)",
            total_t0.elapsed(),
        );
    }
    loop {
        if let Err(e) = read_supervisor_line_skip_save_notifications(&mut reader, &mut line) {
            watcher_stop.store(true, std::sync::atomic::Ordering::Release);
            let _ = watcher_handle.join();
            let _ = child.kill();
            let _ = child.wait();
            let _ = std::fs::remove_dir_all(&socks_dir);
            return Err(take_panic_err(format!(
                "pipelined bake: read BAKE_READY: {e}"
            )));
        }
        let trimmed = line.trim().to_owned();
        // BAKE_READY → break out of the loop, fall through to
        // existing BAKE_READY validation below.
        if trimmed == "BAKE_READY" {
            if crate::trace::enabled("phases") {
                eprintln!(
                    "[phases] BAKE_READY received ({:?} since bake start, \
                     {:?} since READY)",
                    total_t0.elapsed(),
                    bake_ready_t0.elapsed(),
                );
            }
            break;
        }
        // MARKER_KCACHE_READY → cache-WRITE orchestration (idempotent
        // — multiple fires of the marker are guarded by
        // `kcache_write_done`). The existing helper skips this
        // line so we should never see it BUT we keep the explicit
        // branch as defense-in-depth in case the helper's filter
        // is removed in a future refactor.
        if trimmed == "MARKER_KCACHE_READY" {
            if matches!(kcache_mode, KcacheMode::Write) && !kcache_write_done {
                if let Some(p) = kcache_path.as_ref() {
                    let cache_t0 = Instant::now();
                    let snap_path_str = p.to_string_lossy();
                    if let Err(e) = writeln!(writer, "SNAPSHOT_ASYNC {snap_path_str}")
                        .and_then(|()| writer.flush())
                    {
                        watcher_stop.store(true, std::sync::atomic::Ordering::Release);
                        let _ = watcher_handle.join();
                        let _ = child.kill();
                        let _ = child.wait();
                        let _ = std::fs::remove_dir_all(&socks_dir);
                        return Err(format!("kcache-write: send SNAPSHOT_ASYNC: {e}"));
                    }
                    // Wait for DONE_SNAPSHOT_ASYNC. The helper
                    // skips SAVE_DONE/SAVE_FAIL/MARKER lines, so
                    // the first non-orthogonal line we get is the
                    // response.
                    if let Err(e) =
                        read_supervisor_line_skip_save_notifications(&mut reader, &mut line)
                    {
                        watcher_stop.store(true, std::sync::atomic::Ordering::Release);
                        let _ = watcher_handle.join();
                        let _ = child.kill();
                        let _ = child.wait();
                        let _ = std::fs::remove_dir_all(&socks_dir);
                        return Err(format!("kcache-write: read DONE_SNAPSHOT_ASYNC: {e}"));
                    }
                    if !line.trim().starts_with("DONE_SNAPSHOT_ASYNC") {
                        watcher_stop.store(true, std::sync::atomic::Ordering::Release);
                        let _ = watcher_handle.join();
                        let _ = child.kill();
                        let _ = child.wait();
                        let _ = std::fs::remove_dir_all(&socks_dir);
                        return Err(format!(
                            "kcache-write: bad SNAPSHOT_ASYNC response {:?}",
                            line.trim()
                        ));
                    }
                    // Push the resume token: 'R' = 0x52, '\n' = 0x0a.
                    // Canonical-mode TTY needs the newline to
                    // deliver to init-oci's read().
                    if let Err(e) = writeln!(writer, "PUSH_RX 52")
                        .and_then(|()| writeln!(writer, "PUSH_RX 0a"))
                        .and_then(|()| writer.flush())
                    {
                        watcher_stop.store(true, std::sync::atomic::Ordering::Release);
                        let _ = watcher_handle.join();
                        let _ = child.kill();
                        let _ = child.wait();
                        let _ = std::fs::remove_dir_all(&socks_dir);
                        return Err(format!("kcache-write: send PUSH_RX: {e}"));
                    }
                    if trace_enabled() {
                        eprintln!(
                            "[kcache-write] snap+push completed in {} ms",
                            elapsed_ms(cache_t0)
                        );
                    }
                    kcache_write_done = true;
                }
            }
            // Marker handled — read next line (eventually BAKE_READY).
            continue;
        }
        // Anything else → unexpected, fall through to the existing
        // BAKE_READY validation below.
        break;
    }
    if line.trim() != "BAKE_READY" {
        watcher_stop.store(true, std::sync::atomic::Ordering::Release);
        let _ = watcher_handle.join();
        let _ = child.kill();
        let _ = child.wait();
        let _ = std::fs::remove_dir_all(&socks_dir);
        return Err(take_panic_err(format!(
            "pipelined bake: expected BAKE_READY, got {:?}; see {}",
            line.trim(),
            log.display()
        )));
    }
    // BAKE_READY received — kernel booted, init ran, workload
    // signalled readiness. Kernel panic is no longer the failure
    // mode we're worried about; subsequent errors (warmup callback,
    // snapshot capture) have their own diagnostics. Stop the
    // watcher now so it doesn't race with snapshot save activity
    // that touches bake.log.
    let watcher_already_stopped = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(true));
    watcher_stop.store(true, std::sync::atomic::Ordering::Release);
    // Wake the watcher's interruptible sleep so join() returns
    // immediately instead of waiting up to 100 ms for the next
    // poll tick. Send is best-effort; if the watcher already
    // exited (panic detected → returned), the send fails silently.
    let _ = watcher_stop_tx.send(());
    let watcher_join_t0 = Instant::now();
    let watcher_handle = {
        // Replace `watcher_handle` with a no-op once joined so the
        // later `watcher_stop + watcher_handle.join()` at the
        // success path stays compilable / idempotent.
        let _ = watcher_handle.join();
        std::thread::spawn(|| {})
    };
    if crate::trace::enabled("phases") {
        eprintln!(
            "[phases] watcher_handle.join took {:?}",
            watcher_join_t0.elapsed(),
        );
    }
    let _ = watcher_already_stopped;
    let bake_ready_ms = bake_ready_t0.elapsed().as_millis();
    let log_has_t0 = Instant::now();
    if log_has(&log, "listener readiness") {
        listener_ready_ms = Some(elapsed_ms(bake_t0));
    }
    if crate::trace::enabled("phases") {
        eprintln!(
            "[phases] log_has(\"listener readiness\") took {:?}",
            log_has_t0.elapsed(),
        );
    }
    // Agent-listening readiness is handled GUEST-side in init-oci
    // (`wait_for_exec_agent_listening`) — it blocks before the
    // pre-exec marker fires, so by the time BAKE_READY arrives
    // here, the agent's accept() is established. A host-side
    // probe was tried but interfered with AF_VSOCK socket state
    // captured in the snapshot (restored workers got "agent closed
    // connection before sending EXIT" mid-exec). Letting the
    // guest do the wait, with no host-side traffic between agent-
    // ready and snapshot-fire, keeps the captured vsock state
    // clean.

    // Park secondaries around the BASE snapshot. Required because
    // even though embedders don't restore FROM the base, the
    // capture's `hv_vcpus_exit` + resume cycle disturbs secondary
    // vCPU state enough to wedge the post-capture workload (the
    // RCU stall on cpu=1 we see in bake.log after every base
    // resume that didn't park).
    //
    // Earlier history: an unparked-base attempt let the workload
    // hit RCU-stall → listener-ready never fired → bake timed out
    // at 30 s with "no listener after 30000 ms" → fallback
    // wall-clock snapshot of a corrupted post-resume state.
    // A first parked-base attempt fixed that but then UNPARK
    // RPC'd from host to agent timed out at 5 s, because CFS
    // load-balanced the agent's new per-connection thread onto
    // a parked vCPU (1-3) where it never ran. The fix landed in
    // supermachine-agent: `pin_to_cpu0_or_die` keeps the agent
    // and its accept-spawned threads on vCPU 0 (the only vCPU
    // smpark never touches), so the UNPARK RPC's thread always
    // gets CPU time. Forked workload children reset to
    // all-CPUs affinity in the agent's fork path so their
    // throughput isn't capped.
    // Flush guest dirty pages to host-persistent backing files
    // (virtio-blk volumes, writeback-cached virtio-fs) before the
    // base capture — and before parking, since parked secondaries
    // can't run writeback kworkers. A capture taken while volume
    // writes sit dirty in the guest page cache leaves the HOST
    // backing file torn; the volume outlives this VM, so a rebake
    // or fresh VM attaching it reads the torn file directly. ~1 ms
    // on an idle just-booted guest; bounded by writeback when the
    // workload's startup actually wrote data (exactly when it
    // matters). See `guest_sync_via_agent`.
    if !crate::api::guest_sync_via_agent(&vsock_exec_path) {
        eprintln!("[bake] guest sync before base capture failed; proceeding (capture may leave volume backing files torn)");
    }
    // 0.7.44+ skip the pre-base smpark_park RPC on single-vCPU
    // bakes — there are no secondaries to park, the agent returns
    // ok=false, and we paid a vsock round-trip for nothing. Saves
    // ~3-8 ms per bake on the default vcpus=1 path. With
    // multi-vCPU we still park (secondaries need to be in WFI for
    // the capture to be sound).
    let parked_for_base = if plan.vcpus > 1 {
        if crate::trace::enabled("phases") {
            eprintln!(
                "[phases] pre-base smpark_park_via_agent enter ({:?} since bake start)",
                total_t0.elapsed(),
            );
        }
        let r = smpark_park_via_agent(&vsock_exec_path);
        if crate::trace::enabled("phases") {
            eprintln!(
                "[phases] pre-base smpark_park done ({:?} since bake start)",
                total_t0.elapsed(),
            );
        }
        r
    } else {
        false
    };

    // SNAPSHOT_ASYNC base — non-blocking save (background).
    let async_send_t0 = Instant::now();
    if crate::trace::enabled("phases") {
        eprintln!(
            "[phases] sending SNAPSHOT_ASYNC ({:?} since bake start)",
            total_t0.elapsed(),
        );
    }
    let snap_base_str = snap_base
        .to_str()
        .ok_or_else(|| "snap_base path not UTF-8".to_owned())?;
    writeln!(writer, "SNAPSHOT_ASYNC {snap_base_str}")
        .map_err(|e| format!("write SNAPSHOT_ASYNC: {e}"))?;
    writer
        .flush()
        .map_err(|e| format!("flush SNAPSHOT_ASYNC: {e}"))?;
    read_supervisor_line_skip_save_notifications(&mut reader, &mut line)
        .map_err(|e| format!("read DONE_SNAPSHOT_ASYNC: {e}"))?;
    if crate::trace::enabled("phases") {
        eprintln!(
            "[phases] DONE_SNAPSHOT_ASYNC received ({:?} since bake start, \
             {:?} since send)",
            total_t0.elapsed(),
            async_send_t0.elapsed(),
        );
    }
    let line_trim = line.trim();
    let mut base_capture_us: u64 = 0;
    if let Some(rest) = line_trim.strip_prefix("DONE_SNAPSHOT_ASYNC") {
        for kv in rest.split_ascii_whitespace() {
            if let Some(v) = kv.strip_prefix("capture_us=") {
                base_capture_us = v.parse().unwrap_or(0);
            }
        }
    } else if let Some(rest) = line_trim.strip_prefix("ERR_SNAPSHOT ") {
        let _ = child.kill();
        let _ = child.wait();
        let _ = std::fs::remove_dir_all(&socks_dir);
        return Err(format!(
            "pipelined bake: base snapshot failed: {}; see {}",
            rest.trim(),
            log.display()
        ));
    } else {
        let _ = child.kill();
        let _ = child.wait();
        let _ = std::fs::remove_dir_all(&socks_dir);
        return Err(format!(
            "pipelined bake: bad SNAPSHOT_ASYNC response {:?}; see {}",
            line_trim,
            log.display()
        ));
    }
    let async_send_ms = async_send_t0.elapsed().as_millis();

    // Unpark immediately after base capture. The agent on vCPU 0
    // (pinned there for exactly this round-trip's reliability)
    // services the request, ioctls /dev/smpark UNPARK, smpark
    // fires an IPI to wake parked secondaries from WFI. By the
    // time the warmup callback's first `vm.exec` lands, all
    // secondaries are running again.
    if parked_for_base {
        // Try host-direct (supervisor-driven) unpark first. The
        // worker writes 1 to the smpark-signal byte using its
        // RAM mapping — bounded by a pipe write + a syscall, can't
        // be starved by guest CPU load. Falls back to the legacy
        // agent vsock RPC when:
        //   * smpark.ko is older (no sysfs-published GPA) → ERR
        //   * supervisor transport is broken → Err (we still try
        //     the agent path; the worker's likely dying anyway,
        //     and the agent path's failure logs are more useful
        //     for debugging that case).
        match smpark_unpark_via_supervisor(&mut writer, &mut reader) {
            Ok(true) => { /* host-direct path succeeded */ }
            Ok(false) => {
                let _ = smpark_unpark_via_agent(&vsock_exec_path);
            }
            Err(e) => {
                eprintln!("[bake] smpark_unpark_direct transport error ({e}); falling back");
                let _ = smpark_unpark_via_agent(&vsock_exec_path);
            }
        }
    }

    // Run the user's warmup callback. Guest is alive; the base
    // save runs in parallel on a worker-side thread.
    let warmup_t0 = Instant::now();
    let warmup_ctx = PipelinedWarmupContext {
        vsock_mux_path: vsock_mux_path.clone(),
        vsock_exec_path: vsock_exec_path.clone(),
    };
    if let Err(e) = (pipelined.callback)(&warmup_ctx) {
        let _ = writeln!(writer, "QUIT");
        let _ = writer.flush();
        let _ = child.wait();
        let _ = std::fs::remove_dir_all(&socks_dir);
        // If the deadline guard fired, the callback's exec failed only because
        // we SIGKILLed the wedged worker — surface that, not the raw exec error.
        return Err(take_panic_err(format!(
            "pipelined bake: warmup callback failed: {e}"
        )));
    }
    let warmup_ms = warmup_t0.elapsed().as_millis();

    // Park before WARM snapshot capture — this is the SHIPPABLE
    // capture (embedders restore from this one). The saved
    // snapshot freezes secondaries in WFI inside the smpark
    // park spin loop; on restore, the worker's post-restore
    // hook (in api.rs spawn_one) sends UNPARK and secondaries
    // wake up cleanly. With the agent CPU-0 pinning fix in
    // supermachine-agent, both the bake-time and restore-time
    // UNPARK RPCs reliably land on a runnable thread.
    // 0.7.44+ skip the pre-warm smpark_park RPC when:
    //   * skip_warm_snapshot=true → no warm capture follows; the
    //     park+unpark pair was pure waste (the hot `.build()`
    //     path runs here, so this is a per-bake win).
    //   * vcpus == 1 → no secondaries to park (same rationale as
    //     the pre-base gate above).
    // Together ~6-15 ms median saved per bake on the dominant
    // single-vCPU + skip-warm path.
    //
    // Flush guest dirty pages before the warm capture (and before
    // parking — see the pre-base sync above). THIS is the capture
    // that bit in the field: a warmup `npm install` writes hundreds
    // of MiB into a volume, the capture froze the guest with much
    // of it still dirty in the guest page cache, and the worker was
    // later killed without ever flushing — the volume backing file
    // kept a torn half-written package across every later run.
    // Skipped with skip_warm_snapshot (no capture follows; the
    // handoff VM keeps running and flushes naturally).
    if !pipelined.skip_warm_snapshot && !crate::api::guest_sync_via_agent(&vsock_exec_path) {
        eprintln!("[bake] guest sync before warm capture failed; proceeding (capture may leave volume backing files torn)");
    }
    let parked_for_warm = if plan.vcpus > 1 && !pipelined.skip_warm_snapshot {
        if crate::trace::enabled("phases") {
            eprintln!(
                "[phases] pre-warm smpark_park_via_agent enter ({:?} since bake start)",
                total_t0.elapsed(),
            );
        }
        let r = smpark_park_via_agent(&vsock_exec_path);
        if crate::trace::enabled("phases") {
            eprintln!(
                "[phases] pre-warm smpark_park done ({:?} since bake start)",
                total_t0.elapsed(),
            );
        }
        r
    } else {
        false
    };

    // SNAPSHOT warm — sync, with `base=<snap_base>` hint so the
    // runner uses APFS clonefile + diff-pwrite if the in-flight
    // base save is still in memory. Falls through to streaming
    // sync save inside the runner if anything blocks the diff
    // path (different filesystem, meta overflow, etc.) — caller
    // never sees the distinction.
    //
    // Skipped entirely when `pipelined.skip_warm_snapshot=true`
    // (the "always-pipelined for plain `.build()`" path): there's
    // no separate warm artifact, the base snapshot IS the user's
    // snapshot, and the worker's in-memory state is the warm
    // handoff for the first acquire.
    let mut warm_save_us: u64 = 0;
    let mut warm_bytes: u64 = 0;
    let warm_send_ms: u128;
    if !pipelined.skip_warm_snapshot {
        let warm_send_t0 = Instant::now();
        let snap_warm_str = snap_warm
            .to_str()
            .ok_or_else(|| "snap_warm path not UTF-8".to_owned())?;
        let snap_base_str_for_warm = snap_base
            .to_str()
            .ok_or_else(|| "snap_base path not UTF-8".to_owned())?;
        writeln!(
            writer,
            "SNAPSHOT {snap_warm_str} base={snap_base_str_for_warm}"
        )
        .map_err(|e| format!("write SNAPSHOT: {e}"))?;
        writer.flush().map_err(|e| format!("flush SNAPSHOT: {e}"))?;
        read_supervisor_line_skip_save_notifications(&mut reader, &mut line)
            .map_err(|e| format!("read DONE_SNAPSHOT: {e}"))?;
        let line_trim = line.trim();
        if let Some(rest) = line_trim.strip_prefix("DONE_SNAPSHOT") {
            for kv in rest.split_ascii_whitespace() {
                if let Some(v) = kv.strip_prefix("save_us=") {
                    warm_save_us = v.parse().unwrap_or(0);
                } else if let Some(v) = kv.strip_prefix("bytes_written=") {
                    warm_bytes = v.parse().unwrap_or(0);
                }
            }
        } else if let Some(rest) = line_trim.strip_prefix("ERR_SNAPSHOT ") {
            let _ = writeln!(writer, "QUIT");
            let _ = writer.flush();
            let _ = child.wait();
            let _ = std::fs::remove_dir_all(&socks_dir);
            return Err(format!(
                "pipelined bake: warm snapshot failed: {}; see {}",
                rest.trim(),
                log.display()
            ));
        } else {
            let _ = writeln!(writer, "QUIT");
            let _ = writer.flush();
            let _ = child.wait();
            let _ = std::fs::remove_dir_all(&socks_dir);
            return Err(format!(
                "pipelined bake: bad SNAPSHOT response {:?}; see {}",
                line_trim,
                log.display()
            ));
        }
        warm_send_ms = warm_send_t0.elapsed().as_millis();
    } else {
        // No warm SNAPSHOT round-trip; user gets snap_base.
        warm_send_ms = 0;
    }

    // Unpark secondaries on the live (keep-alive) worker so the
    // first pool acquire sees a fully running guest. The saved
    // snapshot file already contains the parked state — that's
    // captured on disk; the keep-alive worker we hand to the
    // pool gets unparked here.
    //
    // This is cycle 2 — the field-report failure point. The
    // host-direct path (smpark_unpark_via_supervisor) fixes
    // the cycle-2 5 s starvation by removing the agent thread
    // from the critical path.
    if parked_for_warm {
        match smpark_unpark_via_supervisor(&mut writer, &mut reader) {
            Ok(true) => {}
            Ok(false) => {
                let _ = smpark_unpark_via_agent(&vsock_exec_path);
            }
            Err(e) => {
                eprintln!("[bake] smpark_unpark_direct transport error ({e}); falling back");
                let _ = smpark_unpark_via_agent(&vsock_exec_path);
            }
        }
    }

    // KEEP-ALIVE FORK
    //
    // When `pipelined.keep_alive == true`, we DON'T send QUIT and
    // DON'T wait for the child.
    //
    // Synchronization contract:
    //   * `keep_alive=true && skip_warm_snapshot=false` (warmup
    //      pipeline): the warm SNAPSHOT (sync) joined the in-flight
    //      base save before writing, so both `snap_base` and
    //      `snap_warm` are on disk by the time we get here.
    //   * `keep_alive=true && skip_warm_snapshot=true` (always-
    //      pipelined plain `.build()`): the base SNAPSHOT_ASYNC's
    //      bg save MAY still be in flight when we hand the worker
    //      off. The first acquire uses the worker's in-memory
    //      state (which IS the snapshot's contents — async save
    //      copies from the same captured frame). For a Pool
    //      `spawn_one` that goes through restore-from-disk,
    //      api.rs's `spawn_one` polls for `snap_path.is_file()`
    //      before invoking the worker; `save_compact_to_file`
    //      atomic-renames `<path>.partial` → `<path>` so file
    //      existence ↔ save complete.
    //
    // We hand the worker handle back to the caller via the
    // `keep_alive_out` out-parameter. The caller stashes it on
    // the returned `Image` and the first `Pool::acquire()` claims
    // it as a pre-warm idle entry — saving spawn (~50 ms) +
    // restore (~5 ms) on the first cycle.
    //
    // Lifecycle / leak avoidance: if the caller drops the Image
    // without a Pool ever claiming the worker, the BakedWorker's
    // `Drop` impl in api.rs sends QUIT + waits + cleans the
    // socks_dir. We can't do that here because dropping straight
    // from bake.rs would lose the QUIT-then-wait ordering on
    // panic-paths, and the caller may want different semantics
    // (e.g. immediate kill on Image drop vs. graceful drain).
    //
    // Past BAKE_READY and warm capture (if any) — the kernel
    // booted cleanly and the workload ran far enough to fire the
    // readiness trigger. Stop the panic watcher so it doesn't race
    // with normal worker shutdown.
    watcher_stop.store(true, std::sync::atomic::Ordering::Release);
    let _ = watcher_handle.join();

    let quit_ms;
    if pipelined.keep_alive {
        // Disk-existence checks — only for the artifacts we
        // expected to land before this point.
        if !pipelined.skip_warm_snapshot {
            if !snap_base.is_file() {
                let _ = writeln!(writer, "QUIT");
                let _ = writer.flush();
                let _ = child.wait();
                let _ = std::fs::remove_dir_all(&socks_dir);
                return Err(format!(
                    "pipelined bake (keep_alive): base snapshot {} missing; see {}",
                    snap_base.display(),
                    log.display()
                ));
            }
            // Poll for warm snapshot to land on disk. As of 0.7.32
            // the runner's Sync+diff path spawns the save in a
            // background thread (the prior in-place save blocked
            // vCPU 0 for 20s+ on large images → RCU stall in guest
            // → in-guest vsock wedged → first post-acquire vm.exec
            // failed with `agent closed connection before sending
            // EXIT`). The capture-only DONE_SNAPSHOT response we
            // already received tells us the in-memory snapshot is
            // intact; the .partial → final atomic-rename happens
            // when the save thread finishes.
            //
            // Cap at 180 s. The save now competes for disk bandwidth
            // with the live guest's background activity (npm
            // postinstall TCP, ext4 writeback, virtio-fs page
            // faults), so the wall-clock save time scales with guest
            // I/O load. For an idle 8 GiB image on Apple Silicon
            // SSD this still lands in ~5–20 s; under heavy
            // concurrent I/O we've measured up to ~60 s. The 180 s
            // cap is a real disk-problem indicator (full / failing
            // SSD, jetsam pressure) rather than a busy-image hint.
            {
                let poll_t0 = std::time::Instant::now();
                let poll_deadline = poll_t0 + std::time::Duration::from_secs(180);
                let mut backoff = std::time::Duration::from_millis(5);
                while !snap_warm.is_file() {
                    if std::time::Instant::now() > poll_deadline {
                        let _ = writeln!(writer, "QUIT");
                        let _ = writer.flush();
                        let _ = child.wait();
                        let _ = std::fs::remove_dir_all(&socks_dir);
                        return Err(format!(
                            "pipelined bake (keep_alive): warm snapshot {} \
                             did not land on disk within 180 s (bg save \
                             still in flight; check disk space, IO load, \
                             or worker logs at {})",
                            snap_warm.display(),
                            log.display()
                        ));
                    }
                    std::thread::sleep(backoff);
                    backoff = (backoff * 2).min(std::time::Duration::from_millis(50));
                }
                if crate::trace::enabled("timings") {
                    eprintln!(
                        "[bake] waited {:?} for warm snapshot file to land",
                        poll_t0.elapsed()
                    );
                }

                // Auto-dedup the fresh warm snapshot. Same
                // mechanism as the base-snapshot dedup below;
                // see comment block there for full rationale.
                // Best-effort — failure leaves the fresh
                // snapshot untouched.
                let warm_dir = snap_warm
                    .parent()
                    .map(|p| p.to_path_buf())
                    .unwrap_or_else(|| std::path::PathBuf::from("."));
                if let Ok(meta_text) = std::fs::read_to_string(warm_dir.join("metadata.json")) {
                    if let Ok(meta) = serde_json::from_str::<serde_json::Value>(&meta_text) {
                        let image = meta.get("image").and_then(|v| v.as_str()).unwrap_or("");
                        let memory_mib = meta
                            .get("memory_mib")
                            .and_then(|v| v.as_u64())
                            .map(|v| v as u32)
                            .unwrap_or(0);
                        let baked_by_version = meta
                            .get("baked_by_version")
                            .and_then(|v| v.as_str())
                            .unwrap_or("");
                        if !image.is_empty() && memory_mib > 0 && !baked_by_version.is_empty() {
                            crate::dedup::auto_dedup_on_bake(
                                plan.snapshots_dir,
                                &warm_dir,
                                image,
                                memory_mib,
                                baked_by_version,
                            );
                        }
                    }
                }
            }
        }
        // For skip_warm + keep_alive: snap_warm is never written.
        // snap_base's bg save is in flight. ORIGINAL DESIGN said
        // "first acquire uses the worker's in-memory state, so
        // it doesn't care about disk." 0.7.25 added a wait for
        // the bg save to land because of an amd64-via-Rosetta
        // race: the bg save thread's APFS IO load back-pressured
        // the vCPU, rosettad's first-connect lazy init produced
        // a broken translation, glibc's ld.so panicked. See
        // docs/design/rosetta-in-vm-2026-05-16.md Round 8.
        //
        // 0.7.43+ revisited: the SAME 0.7.25 changeset also
        // shipped (a) the correct VZLinuxRosettaUnixSocketCachingOptions
        // FS-socket variant and (b) an embedded 608-byte amd64
        // ELF warmup that primes rosettad BEFORE the snapshot is
        // taken. Either of those two might individually be
        // sufficient — the wait was added as belt-and-suspenders.
        //
        // Empirical test (`__test__/rosetta_no_wait_safety.test.ts`):
        // 30 fresh python:3.12-slim (glibc-dynamic, the original
        // Round 8 trigger) amd64 bakes with the wait SKIPPED.
        // Zero ld.so assertions. Median bake-to-first-exec
        // dropped from 3733 ms → 2126 ms (-1607 ms / -43%).
        //
        // Result: skip the wait by default for ALL platforms.
        //
        // Safety net retained: spawn_one (api.rs:1211+) polls for
        // the file with a 30 s timeout, so any 2nd+ acquire from
        // disk handles in-flight save transparently regardless of
        // platform.
        //
        // Escape hatch: `SUPERMACHINE_BAKE_WAIT_SAVE=1` forces the
        // wait back on. If a user hits the original Round 8 panic
        // (we don't expect this), they can re-enable the wait
        // while we investigate.
        let force_wait_env = std::env::var("SUPERMACHINE_BAKE_WAIT_SAVE").ok();
        let wait_for_save = match force_wait_env.as_deref() {
            Some("0") | Some("false") => false,
            Some("1") | Some("true") => true,
            _ => false,
        };
        // Auto-dedup happens after the bg save lands. With
        // wait_for_save=true we run it synchronously; with
        // wait_for_save=false we defer it to a bg thread so
        // build() returns immediately.
        //
        // What auto-dedup does: scans for an existing sibling
        // snapshot (same image+memory+version+file_size), and if
        // one's found, replaces the freshly-written file with
        // clonefile(sibling) + pwrite-of-diff-pages. APFS-level
        // block sharing → physical disk shrinks, OS page cache
        // hits warm pages on subsequent restores, host RAM
        // shared across pool VMs.
        //
        // Best-effort: any failure (no sibling, clonefile EXDEV,
        // permission denied) leaves the fresh snapshot untouched.
        // Atomic rename means an interrupted run never corrupts.
        //
        // Opt-out: `SUPERMACHINE_AUTO_DEDUP=0`. (Doesn't apply to
        // the wait itself — only the dedup pass.)
        let do_poll_and_dedup = |snap_base_path: PathBuf,
                                 snapshots_dir: PathBuf,
                                 log_path: PathBuf,
                                 fatal_on_timeout: bool,
                                 trace_enabled_now: bool|
         -> Result<(), String> {
            let poll_t0 = std::time::Instant::now();
            let poll_deadline = poll_t0 + std::time::Duration::from_secs(60);
            let mut backoff = std::time::Duration::from_millis(5);
            while !snap_base_path.is_file() {
                if std::time::Instant::now() > poll_deadline {
                    if fatal_on_timeout {
                        return Err(format!(
                            "pipelined bake (keep_alive+skip_warm): base \
                             snapshot {} did not land on disk within 60s \
                             (bg save still in flight; check disk space, \
                             IO load, or worker logs at {})",
                            snap_base_path.display(),
                            log_path.display(),
                        ));
                    } else {
                        eprintln!(
                            "[bake-deferred-dedup] snap_base {} did not \
                             land in 60s; skipping auto-dedup",
                            snap_base_path.display(),
                        );
                        return Ok(());
                    }
                }
                std::thread::sleep(backoff);
                backoff = (backoff * 2).min(std::time::Duration::from_millis(50));
            }
            if trace_enabled_now {
                eprintln!(
                    "[bake] waited {:?} for base snapshot file to land",
                    poll_t0.elapsed(),
                );
            }
            let auto_dedup_t0 = std::time::Instant::now();
            let snap_dir = snap_base_path
                .parent()
                .map(|p| p.to_path_buf())
                .unwrap_or_else(|| std::path::PathBuf::from("."));
            if let Ok(meta_text) = std::fs::read_to_string(snap_dir.join("metadata.json")) {
                if let Ok(meta) = serde_json::from_str::<serde_json::Value>(&meta_text) {
                    let image = meta.get("image").and_then(|v| v.as_str()).unwrap_or("");
                    let memory_mib = meta
                        .get("memory_mib")
                        .and_then(|v| v.as_u64())
                        .map(|v| v as u32)
                        .unwrap_or(0);
                    let baked_by_version = meta
                        .get("baked_by_version")
                        .and_then(|v| v.as_str())
                        .unwrap_or("");
                    if !image.is_empty() && memory_mib > 0 && !baked_by_version.is_empty() {
                        crate::dedup::auto_dedup_on_bake(
                            &snapshots_dir,
                            &snap_dir,
                            image,
                            memory_mib,
                            baked_by_version,
                        );
                    }
                }
            }
            if trace_enabled_now {
                eprintln!("[bake] auto-dedup pass: {:?}", auto_dedup_t0.elapsed());
            }
            Ok(())
        };

        if wait_for_save {
            // amd64 (or env-forced): synchronously poll + dedup
            // before returning. Build wall-clock includes the
            // ~150-500 ms disk-save wait.
            if let Err(e) = do_poll_and_dedup(
                snap_base.clone(),
                plan.snapshots_dir.to_path_buf(),
                log.clone(),
                /* fatal_on_timeout */ true,
                crate::trace::enabled("timings"),
            ) {
                let _ = std::fs::remove_dir_all(&socks_dir);
                let _ = child.kill();
                let _ = child.wait();
                return Err(e);
            }
        } else {
            // arm64 native (default): defer poll + dedup to a bg
            // thread. Build returns immediately; dedup catches up
            // in the background. Subsequent acquires from disk
            // (via `Pool::spawn_one`) have their own polling at
            // `api.rs:1211+` with a 30 s timeout, so the file
            // not-yet-on-disk case is handled transparently.
            let snap_base_cloned = snap_base.clone();
            let snapshots_dir_cloned = plan.snapshots_dir.to_path_buf();
            let log_cloned = log.clone();
            let trace_now = crate::trace::enabled("timings");
            std::thread::Builder::new()
                .name("supermachine-bake-deferred-dedup".into())
                .stack_size(256 * 1024)
                .spawn(move || {
                    // Construct the closure inline since closures
                    // can't be moved across thread boundaries with
                    // captured `plan` references.
                    let poll_t0 = std::time::Instant::now();
                    let poll_deadline = poll_t0 + std::time::Duration::from_secs(60);
                    let mut backoff = std::time::Duration::from_millis(50);
                    while !snap_base_cloned.is_file() {
                        if std::time::Instant::now() > poll_deadline {
                            eprintln!(
                                "[bake-deferred-dedup] snap_base {} did not \
                                 land in 60s; skipping auto-dedup",
                                snap_base_cloned.display(),
                            );
                            return;
                        }
                        std::thread::sleep(backoff);
                        backoff = (backoff * 2).min(std::time::Duration::from_millis(100));
                    }
                    if trace_now {
                        eprintln!(
                            "[bake-deferred-dedup] waited {:?} for save",
                            poll_t0.elapsed(),
                        );
                    }
                    let dedup_t0 = std::time::Instant::now();
                    let snap_dir = snap_base_cloned
                        .parent()
                        .map(|p| p.to_path_buf())
                        .unwrap_or_else(|| std::path::PathBuf::from("."));
                    if let Ok(meta_text) = std::fs::read_to_string(snap_dir.join("metadata.json")) {
                        if let Ok(meta) = serde_json::from_str::<serde_json::Value>(&meta_text) {
                            let image = meta.get("image").and_then(|v| v.as_str()).unwrap_or("");
                            let memory_mib = meta
                                .get("memory_mib")
                                .and_then(|v| v.as_u64())
                                .map(|v| v as u32)
                                .unwrap_or(0);
                            let baked_by_version = meta
                                .get("baked_by_version")
                                .and_then(|v| v.as_str())
                                .unwrap_or("");
                            if !image.is_empty() && memory_mib > 0 && !baked_by_version.is_empty() {
                                crate::dedup::auto_dedup_on_bake(
                                    &snapshots_dir_cloned,
                                    &snap_dir,
                                    image,
                                    memory_mib,
                                    baked_by_version,
                                );
                            }
                        }
                    }
                    if trace_now {
                        eprintln!("[bake-deferred-dedup] dedup pass: {:?}", dedup_t0.elapsed(),);
                    }
                    let _ = log_cloned; // keep alive for capture
                })
                .ok();
        }

        // Pick the path the worker will treat as "last restored
        // from" for diff-via-clone hints on subsequent cycle
        // SNAPSHOT calls. With skip_warm, the worker most-
        // recently captured snap_base (via SNAPSHOT_ASYNC) and
        // its in-memory state matches that file once the bg save
        // lands. With the warm-pipeline, snap_warm is the most
        // recent capture.
        let last_restore_path = if pipelined.skip_warm_snapshot {
            snap_base.clone()
        } else {
            snap_warm.clone()
        };

        // Reclaim the writer + reader halves into BakedWorker. The
        // `reader` here is a `BufReader<UnixStream>` whose inner
        // stream we want to hand back; pull it out via `into_inner`.
        let reader_stream = reader.into_inner();
        *keep_alive_out = Some(BakedWorker {
            child,
            vsock_mux_path: vsock_mux_path.clone(),
            vsock_exec_path: vsock_exec_path.clone(),
            control_path: ctl_path.clone(),
            control_writer: writer,
            control_reader: reader_stream,
            socks_dir: socks_dir.clone(),
            last_restore_path,
        });
        // No QUIT, no wait, no socks_dir cleanup. All of those are
        // the BakedWorker owner's responsibility.
        quit_ms = 0u128;
    } else {
        // QUIT and wait for the worker to drain any in-flight async
        // saves. Without this `wait()`, the async save thread could
        // be reaped mid-write and leave a `.partial` instead of the
        // canonical file.
        let quit_t0 = Instant::now();
        writeln!(writer, "QUIT").map_err(|e| format!("write QUIT: {e}"))?;
        writer.flush().map_err(|e| format!("flush QUIT: {e}"))?;
        let _ = child.wait();
        quit_ms = quit_t0.elapsed().as_millis();

        let _ = std::fs::remove_dir_all(&socks_dir);

        if !snap_base.is_file() {
            return Err(format!(
                "pipelined bake: base snapshot {} missing after worker exit; see {}",
                snap_base.display(),
                log.display()
            ));
        }
        if !pipelined.skip_warm_snapshot && !snap_warm.is_file() {
            return Err(format!(
                "pipelined bake: warm snapshot {} missing after worker exit; see {}",
                snap_warm.display(),
                log.display()
            ));
        }
    }

    let vmm_bake_ms = elapsed_ms(bake_t0);
    // Stat the snapshot files defensively — under skip_warm +
    // keep_alive the base save may still be in flight when we
    // reach this point, in which case we record null bytes and
    // synthesize the snapshot id from the bake key (see below).
    let snapshot_logical_bytes = std::fs::metadata(&snap_base).ok().map(|m| m.len());
    let snapshot_physical_bytes = file_physical_bytes(&snap_base);
    let warm_logical_bytes = if pipelined.skip_warm_snapshot {
        None
    } else {
        std::fs::metadata(&snap_warm).ok().map(|m| m.len())
    };
    let warm_physical_bytes = if pipelined.skip_warm_snapshot {
        None
    } else {
        file_physical_bytes(&snap_warm)
    };

    let metadata_t0 = Instant::now();
    let runtime_sha = sha256_file(&sm22_bin)?;
    let runtime_sha16 = &runtime_sha[..runtime_sha.len().min(16)];
    let hash_mode =
        std::env::var("SUPERMACHINE_SNAPSHOT_HASH_MODE").unwrap_or_else(|_| "fast".to_owned());
    let baked_at = now_utc_iso()?;
    // Snapshot id is a 16-hex display chip in metadata.json; the
    // cache fast-path uses `native_bake_key`, not this. When the
    // base file isn't on disk yet (skip_warm + keep_alive: bg
    // save in flight), synthesize a stable id from the bake key
    // + baked_at instead of stat-failing — `compute_snapshot_id`
    // requires the file.
    let snapshot_id = if snap_base.is_file() {
        compute_snapshot_id(plan, &snap_base, runtime_sha16, &hash_mode)?
    } else {
        let h = sha256_text(&format!("{native_bake_key}\n{baked_at}\npending"))?;
        h[..h.len().min(16)].to_owned()
    };
    let warm_snapshot_id = if pipelined.skip_warm_snapshot {
        // No warm artifact; reuse the base id so consumers that
        // read this field don't get null surprise.
        snapshot_id.clone()
    } else {
        compute_snapshot_id(plan, &snap_warm, runtime_sha16, &hash_mode)?
    };
    let egress_policy = arg_value(plan.extra_args, "--egress-policy").unwrap_or("");
    let balloon_target_pages = compute_balloon_target_pages(plan.memory_mib);
    let metadata_prepare_ms = elapsed_ms(metadata_t0);
    let total_ms = elapsed_ms(total_t0);
    let timings = serde_json::json!({
        "total_ms": total_ms,
        "pull_inspect_ms": resolution.inspect_ms,
        "rootfs_prepare_ms": layer_plan.plan_ms,
        "rootfs_customize_ms": delta.prepare_ms,
        "init_oci_build_ms": 0,
        "squashfs_ms": squashfs_ms,
        "initramfs_ms": initramfs_ms,
        "vmm_bake_ms": vmm_bake_ms,
        "metadata_prepare_ms": metadata_prepare_ms,
        "guest_boot_to_listener_ms": listener_ready_ms,
        "listener_settle_config_ms": listener_settle_ms,
        "bake_ready_ms": bake_ready_ms,
        "snapshot_async_send_ms": async_send_ms,
        "warmup_ms": warmup_ms,
        "warm_send_ms": warm_send_ms,
        "quit_ms": quit_ms,
        "snapshot_capture_us": base_capture_us,
        "warm_save_us": warm_save_us,
        "snapshot_reason": "pipelined",
    });

    // `size_bytes` is captured so `Image::from_snapshot` can replay
    // the `--volume HOST:GUEST:SIZE` flag at pool-spawn time. Without
    // it, the warm-restored worker would lack the virtio-blk device,
    // but the guest's restored mount table still references it →
    // ext4 reads hang on /dev/vd<N> with no backing device.
    // (Field-report bug fixed 0.7.30.)
    //
    // Pristine capture: see counterpart in run_native_supermachine_bake.
    // Pipelined captures pristine here, at the warm-save-complete
    // point — the volume's host bytes reflect warmup-time state,
    // which matches the warm snapshot's RAM. Base metadata in
    // pipelined mode references the same pristine path (volume file
    // mutated through warmup; base RAM no longer fully consistent
    // with the host file in pipelined-base + volumes — but that
    // wasn't a clean restore point even pre-0.7.49). Common path
    // is warm-snapshot restore, which is consistent.
    //
    // Pristines are written into BOTH the base dir and the warm dir
    // (each as its own clonefile of the host file) so deleting one
    // dir doesn't strand the other. On APFS that's a metadata-only
    // op so the double-capture is essentially free.
    let parsed_volumes = parse_volume_args(plan.extra_args)?;
    let volume_pristines = capture_volume_pristines(&out_dir, &parsed_volumes)?;
    let volumes_meta: Vec<serde_json::Value> = parsed_volumes
        .iter()
        .zip(volume_pristines.iter())
        .map(|(v, pristine)| {
            serde_json::json!({
                "host_file": v.host_file.to_string_lossy(),
                "guest_path": v.guest_path,
                "size_bytes": v.size_bytes,
                "pristine": pristine.to_string_lossy(),
            })
        })
        .collect();
    // Warm metadata gets its own pristine paths under `warm_dir/volumes/`
    // so it stays self-contained — `Image::fromSnapshot(warm_dir)`
    // resolves pristines via the warm dir even if the base dir is
    // cleaned up later.
    let warm_volumes_meta: Vec<serde_json::Value> = if pipelined.skip_warm_snapshot {
        Vec::new()
    } else {
        let warm_pristines = capture_volume_pristines(&pipelined.warm_dir, &parsed_volumes)?;
        parsed_volumes
            .iter()
            .zip(warm_pristines.iter())
            .map(|(v, pristine)| {
                serde_json::json!({
                    "host_file": v.host_file.to_string_lossy(),
                    "guest_path": v.guest_path,
                    "size_bytes": v.size_bytes,
                    "pristine": pristine.to_string_lossy(),
                })
            })
            .collect()
    };
    let mounts_meta: Vec<serde_json::Value> = arg_values(plan.extra_args, "--mount")
        .into_iter()
        .filter_map(parse_mount_arg)
        .collect();
    let restart_policy = arg_value(plan.extra_args, "--restart").unwrap_or("no");
    let health_cmd = arg_value(plan.extra_args, "--health-cmd").unwrap_or("");
    let health_interval_secs: u32 = arg_value(plan.extra_args, "--health-interval")
        .and_then(|s| s.parse().ok())
        .unwrap_or(if health_cmd.is_empty() { 0 } else { 30 });
    // Mirror the cmdline-builder logic at line ~6115 so the same
    // value is persisted to metadata.json. Restore-side spawn_one
    // reads `pre_exec_sync` from metadata and sets the
    // SUPERMACHINE_PRE_EXEC_SYNC_RESUME=1 env so the worker's
    // resume thread pushes the unblock byte.
    let pre_exec_sync_recorded = pipelined.use_pre_exec_trigger
        && std::env::var("SUPERMACHINE_PRE_EXEC_SYNC")
            .map(|v| v != "0" && v != "false")
            .unwrap_or(true);

    let base_metadata = serde_json::json!({
        "name": plan.snapshot_name(),
        "image": plan.image,
        "port": plan.guest_port,
        "memory_mib": plan.memory_mib,
        "cmd": resolution.effective_cmd,
        "snapshot_sha16": snapshot_id,
        "snapshot_id16": snapshot_id,
        "snapshot_hash_mode": hash_mode,
        "runtime_sha16": runtime_sha16,
        "layers": layer_paths,
        "volumes": volumes_meta,
        "mounts": mounts_meta,
        "restart_policy": restart_policy,
        "health_cmd": health_cmd,
        "health_interval_secs": health_interval_secs,
        "delta_squashfs": delta_squashfs,
        "rootfs_squashfs": serde_json::Value::Null,
        "snapshot_base": snap_base,
        "snapshot_logical_bytes": snapshot_logical_bytes,
        "snapshot_physical_bytes": snapshot_physical_bytes,
        "snapshot_ram_data_mib": serde_json::Value::Null,
        "snapshot_ram_zero_mib": serde_json::Value::Null,
        "init_cpio": init_cpio,
        "kernel": kernel,
        "egress_policy": egress_policy,
        "vcpus": plan.vcpus,
        "ttl_seconds": serde_json::Value::Null,
        "egress_bps": serde_json::Value::Null,
        "cpu_nice": serde_json::Value::Null,
        "cpu_affinity": serde_json::Value::Null,
        "cpu_qos": serde_json::Value::Null,
        "balloon_target_pages": balloon_target_pages,
        "auth": {"type": "none"},
        "baked_by_version": env!("CARGO_PKG_VERSION"),
        "native_bake_key": native_bake_key,
        "native_bake_inputs": native_bake_inputs,
        "timings": timings,
        "baked_at": baked_at,
        "supermachine_version": "supermachine",
        "pipelined": true,
        // Per-snapshot TSI control-channel auth token. See
        // sequential-bake counterpart for rationale.
        "tsi_token": tsi_token_hex,
        // 0.7.44+ pre-exec sync. true → the bake passed
        // `supermachine.pre_exec_sync=1` and init-oci is paused
        // inside `read(0)` in the saved snapshot. spawn_one must
        // set SUPERMACHINE_PRE_EXEC_SYNC_RESUME=1 in the restored
        // worker's env so the resume thread pushes the unblock byte.
        "pre_exec_sync": pre_exec_sync_recorded,
    });
    let base_meta_path = out_dir.join("metadata.json");
    std::fs::write(
        &base_meta_path,
        serde_json::to_vec_pretty(&base_metadata)
            .map_err(|e| format!("encode base metadata: {e}"))?,
    )
    .map_err(|e| format!("write base metadata {}: {e}", base_meta_path.display()))?;

    // Warm metadata mirrors base but points at the warm snapshot
    // file. The warm dir REUSES the same delta_squashfs, layers,
    // init_cpio, kernel — Image::from_snapshot doesn't care that
    // they live outside `warm_dir`, only that the paths still
    // resolve.
    //
    // Skipped entirely when `pipelined.skip_warm_snapshot=true`
    // (no warm artifact to describe — base is the user's snapshot).
    if pipelined.skip_warm_snapshot {
        if crate::trace::enabled("phases") {
            eprintln!(
                "[phases] bake fn returning ({:?} since bake start, total_ms={})",
                total_t0.elapsed(),
                total_ms,
            );
        }
        return Ok(NativeBakeResult {
            total_ms,
            timings,
            reused: false,
        });
    }
    let warm_name = format!("{}__warm__{}", plan.snapshot_name(), pipelined.warm_tag);
    let warm_metadata = serde_json::json!({
        "name": warm_name,
        "image": plan.image,
        "port": plan.guest_port,
        "memory_mib": plan.memory_mib,
        "cmd": resolution.effective_cmd,
        "snapshot_sha16": warm_snapshot_id,
        "snapshot_id16": warm_snapshot_id,
        "snapshot_hash_mode": hash_mode,
        "runtime_sha16": runtime_sha16,
        "layers": layer_paths,
        "volumes": warm_volumes_meta,
        "mounts": mounts_meta,
        "restart_policy": restart_policy,
        "health_cmd": health_cmd,
        "health_interval_secs": health_interval_secs,
        "delta_squashfs": delta_squashfs,
        "rootfs_squashfs": serde_json::Value::Null,
        "snapshot_base": snap_warm,
        "snapshot_logical_bytes": warm_logical_bytes,
        "snapshot_physical_bytes": warm_physical_bytes,
        "snapshot_ram_data_mib": serde_json::Value::Null,
        "snapshot_ram_zero_mib": serde_json::Value::Null,
        "init_cpio": init_cpio,
        "kernel": kernel,
        "egress_policy": egress_policy,
        "vcpus": plan.vcpus,
        "ttl_seconds": serde_json::Value::Null,
        "egress_bps": serde_json::Value::Null,
        "cpu_nice": serde_json::Value::Null,
        "cpu_affinity": serde_json::Value::Null,
        "cpu_qos": serde_json::Value::Null,
        "balloon_target_pages": balloon_target_pages,
        "auth": {"type": "none"},
        "baked_by_version": env!("CARGO_PKG_VERSION"),
        "native_bake_key": native_bake_key,
        "native_bake_inputs": native_bake_inputs,
        "timings": timings,
        "baked_at": baked_at,
        "supermachine_version": "supermachine",
        "pipelined": true,
        "warm_tag": pipelined.warm_tag,
        "warm_of": plan.snapshot_name(),
        "bytes_written": warm_bytes,
        // Same token as base — the warm snapshot is captured from
        // the same VM after a re-entry, so its memory image holds
        // the same kernel BSS bytes.
        "tsi_token": tsi_token_hex,
    });
    let warm_meta_path = pipelined.warm_dir.join("metadata.json");
    std::fs::write(
        &warm_meta_path,
        serde_json::to_vec_pretty(&warm_metadata)
            .map_err(|e| format!("encode warm metadata: {e}"))?,
    )
    .map_err(|e| format!("write warm metadata {}: {e}", warm_meta_path.display()))?;

    Ok(NativeBakeResult {
        total_ms,
        timings,
        reused: false,
    })
}

/// Helper shared by [`run_native_supermachine_bake`] and
/// [`run_native_supermachine_bake_pipelined`] for computing the
/// snapshot id (16-hex prefix used as cache fingerprint and chip
/// in metadata).
fn compute_snapshot_id(
    plan: &BakePlan<'_>,
    snap_path: &Path,
    runtime_sha16: &str,
    hash_mode: &str,
) -> Result<String, String> {
    if hash_mode == "content" {
        let h = sha256_file(snap_path)?;
        Ok(h[..h.len().min(16)].to_owned())
    } else if hash_mode == "fast" {
        let meta = std::fs::metadata(snap_path)
            .map_err(|e| format!("stat snapshot {}: {e}", snap_path.display()))?;
        let mtime_ns = meta
            .modified()
            .map_err(|e| format!("snapshot mtime: {e}"))?
            .duration_since(UNIX_EPOCH)
            .map_err(|e| format!("snapshot mtime before epoch: {e}"))?
            .as_nanos();
        let material = format!(
            "{}\0{}\0{}\0{}\0{}",
            plan.snapshot_name(),
            plan.image,
            runtime_sha16,
            meta.len(),
            mtime_ns
        );
        let h = sha256_text(&material)?;
        Ok(h[..h.len().min(16)].to_owned())
    } else {
        Err("SUPERMACHINE_SNAPSHOT_HASH_MODE must be fast or content".to_owned())
    }
}

/// Pipelined entry point — same fast-cache + image-resolve +
/// layer-materialize + delta-materialize pipeline as
/// [`run_push`], but the bake step is the
/// [`run_native_supermachine_bake_pipelined`] flow that overlaps
/// the base-snapshot disk write with the warmup workload.
///
/// On a cache hit (warm snapshot already on disk + matching
/// fingerprint) this returns `Ok(())` without invoking the
/// callback; callers should re-check `Image::from_snapshot(warm_dir)`
/// before deciding to call this.
pub fn run_push_pipelined(
    request: &BakeRequest,
    run_t0: Instant,
    root: &Path,
    pipelined: PipelinedWarmup<'_>,
) -> Result<Option<BakedWorker>, String> {
    let _span = tracing::info_span!(
        "supermachine.bake_pipelined",
        image = %request.image,
        memory_mib = request.memory_mib,
        vcpus = request.vcpus,
        warm_tag = %pipelined.warm_tag,
    )
    .entered();
    let plan = BakePlan::from_request(request);
    if plan.runtime != "supermachine" {
        return Err(format!(
            "pipelined bake only supports the native supermachine runtime, got {:?}",
            plan.runtime
        ));
    }

    // Sibling-clone fast path (0.7.42+) — runs BEFORE
    // `select_image_source`/`resolve_image` so we skip the ~300-500ms
    // docker-inspect/manifest-fetch cost when a sibling already
    // matches the cheap input subset. See try_sibling_clone_fast_path
    // for the full rationale. Only fires on the skip-warm path
    // (plain `.build()` no warmup); with-warmup paths need to run
    // the warmup callback against a fresh boot, so we can't clone
    // someone else's snapshot for them.
    if pipelined.skip_warm_snapshot
        && std::env::var("SUPERMACHINE_NATIVE_BAKE_TAIL")
            .map(|v| v != "0" && v != "false")
            .unwrap_or(true)
    {
        if let Some(_result) = try_sibling_clone_fast_path(&plan, root, run_t0)? {
            if trace_enabled() {
                eprintln!(
                    "supermachine: pipelined-bake sibling-clone total={}ms",
                    elapsed_ms(run_t0)
                );
            }
            // No BakedWorker — caller falls back to spawn-from-disk
            // on first acquire, same as native_supermachine_early_reuse.
            return Ok(None);
        }
    }

    // Per-snapshot-name bake lock (CORRECTNESS): serialize concurrent
    // same-name cold bakes across processes — see acquire_bake_name_lock.
    // Held for the rest of the bake.
    let _name_lock = acquire_bake_name_lock(&plan, run_t0)?;

    // Double-checked locking: a concurrent baker may have produced the
    // snapshot while we waited for the name lock. Re-check the cheap
    // sibling-clone path under the lock (the same-name early-reuse
    // short-circuit below also runs under the lock, after resolve).
    if pipelined.skip_warm_snapshot
        && std::env::var("SUPERMACHINE_NATIVE_BAKE_TAIL")
            .map(|v| v != "0" && v != "false")
            .unwrap_or(true)
        && try_sibling_clone_fast_path(&plan, root, run_t0)?.is_some()
    {
        return Ok(None);
    }

    let source = select_image_source(plan.image, plan.arch())?;
    let resolution = resolve_image(&plan, source.as_ref())?;
    if trace_enabled() {
        emit_image_resolution_trace(&resolution);
    }

    // Early-reuse short-circuit: if the base snapshot's
    // metadata.json already matches the current bake inputs,
    // return without invoking the pipelined driver. Mirrors what
    // `run_push` does for the sequential path; without this every
    // `.build()` re-runs the full bake even on a cache hit.
    //
    // For the skip-warm path (no-warmup `.build()`), the snapshot
    // dir IS the base dir — if base inputs match, the snapshot is
    // good and we can short-circuit.
    //
    // For the with-warmup path, the api.rs caller already checked
    // `Image::from_snapshot(&warm_dir)` upstream. If that succeeded,
    // we never enter this function. If it failed, we MUST run the
    // bake (including the warmup callback) to write the missing
    // warm dir — even if the BASE snapshot's inputs match. Gating
    // early-reuse on `skip_warm_snapshot` ensures we never short-
    // circuit a warm-tag-change re-bake that the user explicitly
    // requested with a new `warmup_tag`. Before this gate, a
    // user-side `with_warmup_tag("v2")` after a prior `("v1")`
    // bake would return Ok from this function without writing
    // `__warm__v2/`, then the api.rs loader would error with
    // "snapshot path not found".
    let early_reuse_eligible = pipelined.skip_warm_snapshot;
    if early_reuse_eligible
        && std::env::var("SUPERMACHINE_NATIVE_BAKE_TAIL")
            .map(|v| v != "0" && v != "false")
            .unwrap_or(true)
    {
        if let Some(_result) =
            native_supermachine_early_reuse_snapshot(&plan, &resolution, root, run_t0)?
        {
            if trace_enabled() {
                eprintln!(
                    "supermachine: pipelined-bake reused base snapshot total={}ms",
                    elapsed_ms(run_t0)
                );
            }
            // No BakedWorker — the caller's path will fall back
            // to spawn-from-disk on first acquire, which is fine
            // because the snapshot file is already on disk and
            // recently mmap-resident.
            return Ok(None);
        }
    }

    let layer_plan = plan_layers(&plan, &resolution, source.as_ref())?;
    let layer_materialization_result = match layer_plan.as_ref() {
        Some(layer_plan) => {
            materialize_missing_layers(plan.image, layer_plan, source.as_ref()).map(Some)
        }
        None => Ok(None),
    };
    if let Some(work_dir) = layer_plan
        .as_ref()
        .and_then(|layer_plan| layer_plan.save_work_dir.as_deref())
    {
        let _ = std::fs::remove_dir_all(work_dir);
    }
    let _layer_materialization = layer_materialization_result?;
    let delta_materialization = materialize_delta_cache(&plan, &resolution, root)?;
    let layer_plan = layer_plan
        .as_ref()
        .ok_or_else(|| "pipelined bake: missing layer plan".to_owned())?;
    let delta = delta_materialization
        .as_ref()
        .ok_or_else(|| "pipelined bake: missing delta cache".to_owned())?;
    let (native_bake_key, native_bake_inputs) =
        native_supermachine_bake_key(&plan, &resolution, layer_plan, delta, root)?;

    let _native_t0 = Instant::now();
    let mut keep_alive_out: Option<BakedWorker> = None;
    let result = run_native_supermachine_bake_pipelined(
        &plan,
        &resolution,
        layer_plan,
        delta,
        root,
        &native_bake_key,
        &native_bake_inputs,
        pipelined,
        &mut keep_alive_out,
    )?;
    if trace_enabled() {
        eprintln!(
            "supermachine: pipelined bake finished after {}ms total={}ms{}",
            result.total_ms,
            elapsed_ms(run_t0),
            if keep_alive_out.is_some() {
                " (worker kept alive for warm-handoff)"
            } else {
                ""
            },
        );
        eprintln!("supermachine: bake timings {}", result.timings);
    }
    Ok(keep_alive_out)
}

fn materialize_delta_cache(
    plan: &BakePlan<'_>,
    resolution: &ImageResolution,
    root: &Path,
) -> Result<Option<DeltaMaterialization>, String> {
    #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
    let _ = root;

    if plan.runtime != "supermachine" {
        return Ok(None);
    }
    let prepare_t0 = Instant::now();
    let mut result = DeltaMaterialization {
        prepare_ms: 0,
        materialize_ms: 0,
        cache_hit: false,
        skipped: None,
        key: None,
        cache_path: None,
    };
    if arg_present(plan.extra_args, "--inbound-tls-autogen") {
        result.prepare_ms = elapsed_ms(prepare_t0);
        result.skipped = Some("inbound-tls-autogen".to_owned());
        return Ok(Some(result));
    }

    let cmd_json = plan.cmd_override.map(ToOwned::to_owned).unwrap_or_else(|| {
        serde_json::to_string(&resolution.effective_cmd).unwrap_or_else(|_| "[]".to_owned())
    });
    let mut key_material = format!("cmd={cmd_json}");
    let stage = temp_work_dir("supermachine-delta-stage")?;
    let materialize_t0 = Instant::now();
    let materialized = (|| {
        write_lines(&stage.join(".supermachine-cmd"), &resolution.effective_cmd)?;

        if let Some(cwd) = resolution
            .working_dir
            .as_deref()
            .filter(|cwd| !cwd.is_empty() && *cwd != "/")
        {
            std::fs::write(stage.join(".supermachine-workdir"), format!("{cwd}\n"))
                .map_err(|e| format!("write .supermachine-workdir: {e}"))?;
            key_material.push_str(&format!("\ncwd={cwd}"));
        }
        if let Some(user) = resolution.user.as_deref().filter(|user| !user.is_empty()) {
            std::fs::write(stage.join(".supermachine-user"), format!("{user}\n"))
                .map_err(|e| format!("write .supermachine-user: {e}"))?;
            key_material.push_str(&format!("\nuser={user}"));
        }

        // `--hostname HOSTNAME`: init-oci sets the kernel's
        // hostname before exec'ing the workload. Limit to 63
        // bytes (POSIX HOST_NAME_MAX-ish) and reject newlines so
        // the file is one trustable line.
        if let Some(hostname) = arg_value(plan.extra_args, "--hostname") {
            if hostname.is_empty()
                || hostname.len() > 63
                || hostname.contains(|c: char| c.is_whitespace() || c == '\0')
            {
                return Err(format!(
                    "--hostname {hostname:?} invalid (1..=63 chars, no whitespace)"
                ));
            }
            std::fs::write(
                stage.join(".supermachine-hostname"),
                format!("{hostname}\n"),
            )
            .map_err(|e| format!("write .supermachine-hostname: {e}"))?;
            key_material.push_str(&format!("\nhostname={hostname}"));
        }

        for extra in arg_values(plan.extra_args, "--extra-file") {
            let Some((host, guest)) = extra.split_once(':') else {
                return Err(format!("bad --extra-file '{extra}' (want host:guest)"));
            };
            let host_path = PathBuf::from(host);
            let guest_path = guest.trim_start_matches('/');
            let dst = stage.join(guest_path);
            if let Some(parent) = dst.parent() {
                std::fs::create_dir_all(parent)
                    .map_err(|e| format!("create extra parent {}: {e}", parent.display()))?;
            }
            std::fs::copy(&host_path, &dst).map_err(|e| {
                format!(
                    "copy extra {} -> {}: {e}",
                    host_path.display(),
                    dst.display()
                )
            })?;
            let mode = file_mode_octal(&host_path)?;
            let sum = sha256_file(&host_path)?;
            key_material.push_str(&format!("\nextra={guest}:{mode}:{sum}"));
        }

        let tls_cert = arg_value(plan.extra_args, "--inbound-tls-cert");
        let tls_key = arg_value(plan.extra_args, "--inbound-tls-key");
        if tls_cert.is_some() || tls_key.is_some() {
            let cert = tls_cert
                .ok_or_else(|| "need both --inbound-tls-cert and --inbound-tls-key".to_owned())?;
            let key = tls_key
                .ok_or_else(|| "need both --inbound-tls-cert and --inbound-tls-key".to_owned())?;
            let tls_dir = stage.join("etc/supermachine");
            std::fs::create_dir_all(&tls_dir)
                .map_err(|e| format!("create TLS dir {}: {e}", tls_dir.display()))?;
            let cert_path = PathBuf::from(cert);
            let key_path = PathBuf::from(key);
            std::fs::copy(&cert_path, tls_dir.join("cert.pem"))
                .map_err(|e| format!("copy TLS cert {}: {e}", cert_path.display()))?;
            std::fs::copy(&key_path, tls_dir.join("key.pem"))
                .map_err(|e| format!("copy TLS key {}: {e}", key_path.display()))?;
            set_mode(&tls_dir.join("cert.pem"), 0o644)?;
            set_mode(&tls_dir.join("key.pem"), 0o600)?;
            let cert_sum = sha256_file(&cert_path)?;
            let key_sum = sha256_file(&key_path)?;
            key_material.push_str(&format!("\ntls=provided:{cert_sum}:{key_sum}"));
        }

        for dir in ["proc", "sys", "dev", "dev/shm", "tmp", "run"] {
            std::fs::create_dir_all(stage.join(dir))
                .map_err(|e| format!("create delta dir {dir}: {e}"))?;
        }

        #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
        {
            let env_pairs = env_pairs_for_workload_script(plan, resolution);
            if let Some(script) = sentry_workload_script(
                &resolution.effective_cmd,
                &env_pairs,
                resolution.working_dir.as_deref(),
            ) {
                let sm_dir = stage.join(".supermachine");
                std::fs::create_dir_all(&sm_dir)
                    .map_err(|e| format!("create {}: {e}", sm_dir.display()))?;
                let run = sm_dir.join("run-workload");
                std::fs::write(&run, script.as_bytes())
                    .map_err(|e| format!("write {}: {e}", run.display()))?;
                set_mode(&run, 0o755)?;
                key_material.push_str("\nkvm-run-workload=1");
            } else {
                key_material.push_str("\nkvm-run-workload=0");
            }
        }

        #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))]
        {
            let init = ensure_init_oci(root)?;
            let init_dst = stage.join("init");
            std::fs::copy(&init, &init_dst)
                .map_err(|e| format!("copy init-oci {}: {e}", init.display()))?;
            set_mode(&init_dst, 0o755)?;
            key_material.push_str(&format!("\ninit={}", file_size_mtime(&init)?));
        }

        // Volumes: drop a `/.supermachine-volumes` file with one
        // GUEST_PATH per line. init-oci reads this post-pivot,
        // matches each line to /dev/vd<letter> by index (volumes
        // come after the layer block-devices), and mounts them as
        // ext4. Bake fingerprint includes the guest paths so adding
        // / removing / renaming a mount re-bakes; the host file's
        // current contents are runtime-only and never contribute.
        let volumes = parse_volume_args(plan.extra_args)?;
        if !volumes.is_empty() {
            let body: String = volumes
                .iter()
                .map(|v| format!("{}\n", v.guest_path))
                .collect();
            std::fs::write(stage.join(".supermachine-volumes"), &body)
                .map_err(|e| format!("write .supermachine-volumes: {e}"))?;
            for v in &volumes {
                key_material.push_str(&format!("\nvolume={}", v.guest_path));
            }
        }

        // virtio-fs binds: drop a `/.supermachine-virtiofs-binds`
        // file with one `TAG\tGUEST_PATH` per line. init-oci reads
        // this post-pivot and runs `mount -t virtiofs <tag>
        // <path>` for each, BEFORE mount_volumes(). Auto-mount
        // layering: parent binds first, then volumes (which may
        // nest under bind paths) overlay on top — Docker / Apple
        // `container` ordering.
        let binds: Vec<(String, String)> = arg_values(plan.extra_args, "--mount")
            .into_iter()
            .filter_map(|raw| {
                // HOST:TAG:GUEST_PATH[:POLICY] — we only need tag + path here.
                let parts: Vec<&str> = raw.splitn(4, ':').collect();
                if parts.len() < 3 || parts[1].is_empty() || parts[2].is_empty() {
                    return None;
                }
                Some((parts[1].to_owned(), parts[2].to_owned()))
            })
            .collect();
        if !binds.is_empty() {
            let body: String = binds.iter().map(|(t, p)| format!("{t}\t{p}\n")).collect();
            std::fs::write(stage.join(".supermachine-virtiofs-binds"), &body)
                .map_err(|e| format!("write .supermachine-virtiofs-binds: {e}"))?;
            for (t, p) in &binds {
                key_material.push_str(&format!("\nvirtiofs-bind={t}={p}"));
            }
        }

        #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))]
        {
            // Drop the in-guest exec agent into the delta layer at
            // /supermachine-agent. init-oci forks + execs it post-pivot.
            // The agent is statically-linked aarch64 musl so it runs
            // unconditionally regardless of the workload's libc.
            let agent = ensure_supermachine_agent(root)?;
            let agent_dst = stage.join("supermachine-agent");
            std::fs::copy(&agent, &agent_dst)
                .map_err(|e| format!("copy supermachine-agent {}: {e}", agent.display()))?;
            set_mode(&agent_dst, 0o755)?;
            key_material.push_str(&format!("\nagent={}", file_size_mtime(&agent)?));
        }

        let key = sha256_text(&format!("{key_material}\n"))?;
        let delta_cache_dir = layer_cache_dir().join("deltas");
        std::fs::create_dir_all(&delta_cache_dir)
            .map_err(|e| format!("create delta cache dir {}: {e}", delta_cache_dir.display()))?;
        let cache_path = delta_cache_dir.join(format!("{key}.squashfs"));
        result.prepare_ms = elapsed_ms(prepare_t0);
        result.key = Some(key);
        result.cache_path = Some(cache_path.clone());
        if cache_path.is_file() {
            result.cache_hit = true;
            return Ok(());
        }

        let unique = TEMP_COUNTER.fetch_add(1, Ordering::Relaxed);
        let tmp = delta_cache_dir.join(format!(
            ".delta.{}.{}.squashfs.tmp",
            std::process::id(),
            unique
        ));
        let _ = std::fs::remove_file(&tmp);
        // In-process squashfs (zstd level 3), no `mksquashfs` subprocess. The
        // delta layer holds supermachine's own injected files; ownership comes
        // straight from the on-disk metadata, matching the prior invocation
        // (which ran without `-all-root`).
        squashfs::write_squashfs(&stage, &tmp, &squashfs::Ownership::FromMetadata)
            .map_err(|e| format!("squashfs delta cache: {e}"))?;
        if cache_path.is_file() {
            let _ = std::fs::remove_file(&tmp);
            result.cache_hit = true;
        } else {
            std::fs::rename(&tmp, &cache_path).map_err(|e| {
                format!(
                    "install delta cache {} -> {}: {e}",
                    tmp.display(),
                    cache_path.display()
                )
            })?;
            result.cache_hit = false;
        }
        Ok(())
    })();
    result.materialize_ms = elapsed_ms(materialize_t0);
    let _ = std::fs::remove_dir_all(&stage);
    materialized?;
    Ok(Some(result))
}

fn resolve_image(plan: &BakePlan<'_>, source: &dyn ImageSource) -> Result<ImageResolution, String> {
    let t0 = Instant::now();
    let local_arch = source.local_arch(plan.image);
    let pull_action = match plan.pull_policy {
        "always" => {
            source.pull(plan.image, true)?;
            "always".to_owned()
        }
        "missing" => {
            if local_arch.as_deref() == Some("arm64") {
                "skipped-local-arm64".to_owned()
            } else {
                source.pull(plan.image, false)?;
                "missing-pulled-arm64".to_owned()
            }
        }
        "never" => {
            if local_arch.is_none() {
                return Err(format!(
                    "image {} not present locally and --pull never was set",
                    plan.image
                ));
            }
            "never".to_owned()
        }
        other => return Err(format!("unknown --pull policy: {other}")),
    };

    let image_obj = source.inspect(plan.image)?;
    let cfg = image_obj
        .get("Config")
        .ok_or_else(|| "image inspect missing Config".to_owned())?;
    let image_id = image_obj
        .get("Id")
        .and_then(|v| v.as_str())
        .map(|s| s.strip_prefix("sha256:").unwrap_or(s).to_owned());
    let architecture = image_obj
        .get("Architecture")
        .and_then(|v| v.as_str())
        .map(ToOwned::to_owned);
    let env_count = cfg
        .get("Env")
        .and_then(|v| v.as_array())
        .map(|v| v.len())
        .unwrap_or(0);
    let env = value_string_array(cfg.get("Env"));
    // docker-style runtime overrides. `--workdir` / `--user`
    // replace the image's WorkingDir / User. Empty string is
    // explicitly allowed (means "no chdir / run as root") so
    // customers can override-to-default.
    let workdir_override = arg_value(plan.extra_args, "--workdir").map(ToOwned::to_owned);
    let user_override = arg_value(plan.extra_args, "--user").map(ToOwned::to_owned);
    let working_dir = workdir_override.or_else(|| {
        cfg.get("WorkingDir")
            .and_then(|v| v.as_str())
            .filter(|s| !s.is_empty())
            .map(ToOwned::to_owned)
    });
    let user = user_override.or_else(|| {
        cfg.get("User")
            .and_then(|v| v.as_str())
            .filter(|s| !s.is_empty())
            .map(ToOwned::to_owned)
    });

    // `--entrypoint`: replaces the image's ENTRYPOINT. Combined
    // with the image's CMD (or `--cmd` override) the final argv
    // becomes [entrypoint, ...cmd-args]. Mirrors
    // `docker run --entrypoint X my-image arg1 arg2`.
    let entrypoint_override = arg_value(plan.extra_args, "--entrypoint").map(ToOwned::to_owned);

    let effective_cmd = if let Some(cmd_override) = plan.cmd_override {
        let cmd_argv = serde_json::from_str::<Vec<String>>(cmd_override)
            .map_err(|e| format!("--cmd must be a JSON string array: {e}"))?;
        match &entrypoint_override {
            Some(ep) if !ep.is_empty() => {
                let mut argv = vec![ep.clone()];
                argv.extend(cmd_argv);
                argv
            }
            _ => {
                // Docker / OCI semantics: `--cmd` replaces only the
                // image's CMD; the image's ENTRYPOINT is preserved
                // and prepended as the wrapping invocation. Equivalent
                // to `docker run image cmd...` — *not*
                // `docker run --entrypoint='' image cmd...`.
                //
                // Without this, images that bootstrap services from
                // their ENTRYPOINT script (e.g. `entrypoint.sh` that
                // starts Xvfb, dbus, postgres init, etc., then exec
                // "$@") drop their bootstrap when the integrator
                // passes `cmd: ['sleep', 'infinity']` — Xvfb never
                // starts → headed Chrome/Playwright errors with
                // "Missing X server", postgres errors with
                // "no PGDATA", and so on. The Apple `container` and
                // libkrun stacks both prepend ENTRYPOINT here; we now
                // match.
                //
                // Explicit `--entrypoint ''` still allows callers to
                // *replace* (clear + override) the entrypoint — that
                // branch is the `Some(ep) if !ep.is_empty()` arm
                // above plus the empty-string fall-through here is
                // treated as "no override → keep image's".
                let mut argv = value_string_array(cfg.get("Entrypoint"));
                argv.extend(cmd_argv);
                argv
            }
        }
    } else if let Some(ep) = &entrypoint_override {
        // Entrypoint without --cmd: keep the image's CMD as args
        // (docker semantics) and replace ENTRYPOINT with the override.
        let mut argv = vec![ep.clone()];
        argv.extend(value_string_array(cfg.get("Cmd")));
        if argv.len() == 1 && ep.is_empty() {
            return Err("--entrypoint cannot be empty without --cmd".to_owned());
        }
        argv
    } else {
        // Reuse a previous bake's `cmd` when the user didn't override.
        // Without this, re-running `supermachine run X` after a custom
        // first-run with `--cmd Y` falls back to the image's default
        // CMD, mismatches the cached snapshot inputs, and forces a
        // re-bake (often failing if the default CMD doesn't open a
        // listener — e.g. python:3.x's CMD is the REPL, not a server).
        if let Some(prev_cmd) = previous_metadata_cmd(plan) {
            prev_cmd
        } else {
            let mut argv = value_string_array(cfg.get("Entrypoint"));
            argv.extend(value_string_array(cfg.get("Cmd")));
            if argv.is_empty() {
                return Err(format!(
                    "image {} has no CMD/ENTRYPOINT; pass --cmd '<argv json>'",
                    plan.image
                ));
            }
            argv
        }
    };

    Ok(ImageResolution {
        local_arch,
        architecture,
        image_id,
        effective_cmd,
        working_dir,
        user,
        env,
        env_count,
        pull_action,
        inspect_ms: elapsed_ms(t0),
    })
}

/// Best-effort `smpark_park` CONTROL RPC to the in-guest agent
/// over the worker's exec-vsock unix socket. Returns true if the
/// agent ack'd success, false if the module isn't available or
/// the RPC failed for any reason. Caller treats false as "fall
/// back to the rendezvous-only capture path".
///
/// Used by the pipelined bake to bracket SNAPSHOT_ASYNC and the
/// warm SNAPSHOT calls — drives secondaries into known-byte-
/// identical parked-WFI state before HVF captures them, fixing
/// the multi-vCPU restore-reliability failure class documented
/// in `docs/design/multi-vcpu-snapshot-intermittency-2026-04-27.md`.
fn smpark_park_via_agent(vsock_exec_path: &Path) -> bool {
    smpark_agent_rpc_with_retry(vsock_exec_path, "smpark_park")
}

/// Counterpart to [`smpark_park_via_agent`]. Wakes the parked
/// secondaries by setting the unpark signal + firing an IPI from
/// the kernel module's ioctl handler. Best-effort; returns false
/// on any failure.
fn smpark_unpark_via_agent(vsock_exec_path: &Path) -> bool {
    smpark_agent_rpc_with_retry(vsock_exec_path, "smpark_unpark")
}

/// Drive an smpark park/unpark over the in-guest agent vsock RPC, with a
/// bounded retry on failure.
///
/// The agent's connection-handler thread is pinned to vCPU 0. During a
/// multi-vCPU bake whose warmup leaves CPU stressors on vCPU 0, that
/// thread can be starved long enough that the 5 s ack read times out
/// (`EAGAIN`) even though the guest is healthy — a transient scheduling
/// artifact, not a real failure. A bare single-shot then (a) flaked the
/// multi-vCPU repro test and (b) SILENTLY degraded snapshot quality: a
/// park-FAIL captures the snapshot with secondaries un-parked, the exact
/// state smpark exists to avoid, which can RCU-stall on restore.
///
/// So retry on a FRESH connection with a short backoff that yields the
/// CPU to the starved agent thread. park/unpark are idempotent
/// (re-parking already-parked secondaries / re-asserting the unpark
/// signal is a no-op), so a retry is safe. The happy path is unchanged
/// (one attempt). The durable fix is a host-direct park path mirroring
/// `smpark_unpark_via_supervisor` (no guest thread in the loop), but that
/// must wait for secondaries to reach WFI from the worker side — a larger
/// change to the parking state machine left for later.
fn smpark_agent_rpc_with_retry(vsock_exec_path: &Path, action: &str) -> bool {
    const ATTEMPTS: u32 = 3;
    let body = serde_json::json!({ "action": action });
    for attempt in 1..=ATTEMPTS {
        let t0 = Instant::now();
        match crate::exec::send_control_with_ack(
            vsock_exec_path,
            &body,
            Some(Duration::from_secs(5)),
        ) {
            Ok(_) => {
                eprintln!(
                    "[bake] {action}: OK in {} ms{}",
                    t0.elapsed().as_millis(),
                    if attempt > 1 {
                        format!(" (attempt {attempt})")
                    } else {
                        String::new()
                    }
                );
                return true;
            }
            Err(e) => {
                eprintln!(
                    "[bake] {action}: attempt {attempt}/{ATTEMPTS} FAIL in {} ms ({e})",
                    t0.elapsed().as_millis()
                );
                if attempt < ATTEMPTS {
                    // Sleep to hand the CPU-0-pinned agent thread a
                    // scheduling window before the next fresh connect.
                    std::thread::sleep(Duration::from_millis(200));
                }
            }
        }
    }
    eprintln!("[bake] {action}: FAIL after {ATTEMPTS} attempts");
    false
}

/// Host-direct smpark_unpark. Sends `SMPARK_UNPARK_DIRECT` over
/// the supervisor socket; the worker writes 1 to the smpark
/// unpark-signal byte (whose GPA was published via PL011 at
/// smpark.ko load time). Returns `Ok(true)` on success,
/// `Ok(false)` when the worker reports the GPA wasn't announced
/// (= older smpark.ko, caller should fall back to the agent
/// RPC), and `Err` on transport failure.
///
/// Unlike the agent vsock RPC, this path is bounded by a single
/// pipe write/read + a syscall to write to a mapped page. Cannot
/// be starved by guest CPU load — no guest-side code is involved
/// until the parked secondaries naturally wake on their next
/// vtimer tick (≤10 ms).
fn smpark_unpark_via_supervisor(
    writer: &mut std::os::unix::net::UnixStream,
    reader: &mut std::io::BufReader<std::os::unix::net::UnixStream>,
) -> Result<bool, String> {
    use std::io::Write;
    let t0 = Instant::now();
    writeln!(writer, "SMPARK_UNPARK_DIRECT")
        .map_err(|e| format!("write SMPARK_UNPARK_DIRECT: {e}"))?;
    writer
        .flush()
        .map_err(|e| format!("flush SMPARK_UNPARK_DIRECT: {e}"))?;
    let mut line = String::new();
    read_supervisor_line_skip_save_notifications(reader, &mut line)
        .map_err(|e| format!("read DONE_SMPARK_UNPARK: {e}"))?;
    let trimmed = line.trim();
    if trimmed == "DONE_SMPARK_UNPARK" {
        eprintln!(
            "[bake] smpark_unpark_direct: OK in {} ms",
            t0.elapsed().as_millis()
        );
        Ok(true)
    } else if let Some(rest) = trimmed.strip_prefix("ERR_SMPARK_UNPARK ") {
        eprintln!(
            "[bake] smpark_unpark_direct: unavailable in {} ms ({rest}); falling back",
            t0.elapsed().as_millis()
        );
        Ok(false)
    } else {
        Err(format!("bad ack: {trimmed:?}"))
    }
}

#[cfg(test)]
mod bake_helper_tests {
    use super::*;

    #[test]
    fn snapshot_name_sanitizes_ref_punctuation() {
        assert_eq!(snapshot_name_for_image("ubuntu:22.04"), "ubuntu_22_04");
        assert_eq!(
            snapshot_name_for_image("ghcr.io/owner/img:tag"),
            "ghcr_io_owner_img_tag"
        );
    }

    #[test]
    fn snapshot_name_is_capped_at_60_chars() {
        let long = format!("repo/{}", "a".repeat(200));
        let name = snapshot_name_for_image(&long);
        assert_eq!(name.chars().count(), 60);
    }

    #[test]
    fn strip_sha256_prefix() {
        assert_eq!(strip_sha256("sha256:deadbeef"), "deadbeef");
        assert_eq!(strip_sha256("deadbeef"), "deadbeef");
    }

    #[test]
    fn sha256_path_component_accepts_clean_hex() {
        let hex = "a".repeat(64);
        assert_eq!(
            sha256_path_component(&format!("sha256:{hex}")).unwrap(),
            hex
        );
        // Already-stripped digests are accepted too.
        assert_eq!(sha256_path_component(&hex).unwrap(), hex);
    }

    #[test]
    fn sha256_path_component_rejects_traversal_and_empty() {
        assert!(sha256_path_component("sha256:").is_err()); // empty
        assert!(sha256_path_component("sha256:../etc/passwd").is_err()); // ..
        assert!(sha256_path_component("sha256:ab/cd").is_err()); // separator
        assert!(sha256_path_component("sha256:../../x").is_err());
    }

    #[test]
    fn value_string_array_forms() {
        let arr = serde_json::json!(["a", "b", "c"]);
        assert_eq!(
            value_string_array(Some(&arr)),
            vec!["a".to_string(), "b".to_string(), "c".to_string()]
        );
        // A lone non-empty string becomes a one-element vec.
        let s = serde_json::json!("solo");
        assert_eq!(value_string_array(Some(&s)), vec!["solo".to_string()]);
        // Empty string, numbers, null, and None all yield empty.
        assert!(value_string_array(Some(&serde_json::json!(""))).is_empty());
        assert!(value_string_array(Some(&serde_json::json!(42))).is_empty());
        assert!(value_string_array(Some(&serde_json::Value::Null)).is_empty());
        assert!(value_string_array(None).is_empty());
    }
}

#[cfg(test)]
mod confined_join_tests {
    use super::*;

    #[test]
    fn joins_clean_relative_paths_under_root() {
        let root = Path::new("/save");
        assert_eq!(
            confined_join(root, "blobs/sha256/abc").unwrap(),
            Path::new("/save/blobs/sha256/abc")
        );
        assert_eq!(
            confined_join(root, "config.json").unwrap(),
            Path::new("/save/config.json")
        );
        // A `.` component is harmless.
        assert_eq!(confined_join(root, "./x").unwrap(), Path::new("/save/./x"));
    }

    #[test]
    fn rejects_traversal_and_absolute_paths() {
        let root = Path::new("/save");
        // A hostile legacy manifest `"Config"`/`"Layers"` value must not escape.
        for evil in [
            "../../../../etc/passwd",
            "a/../../etc/passwd",
            "/etc/passwd",
            "/abs",
            "../x",
        ] {
            assert!(
                confined_join(root, evil).is_err(),
                "must reject traversal: {evil:?}"
            );
        }
    }
}

#[cfg(test)]
mod bake_deadline_guard_tests {
    use super::*;
    use std::sync::atomic::{AtomicBool, Ordering};
    use std::sync::Arc;
    use std::time::Duration;

    fn spawn_sleeper() -> std::process::Child {
        std::process::Command::new("sleep")
            .arg("60")
            .spawn()
            .expect("spawn sleep")
    }

    #[test]
    fn deadline_guard_kills_stuck_child() {
        use std::os::unix::process::ExitStatusExt;
        let mut child = spawn_sleeper();
        let pid = child.id() as i32;
        let timed_out = Arc::new(AtomicBool::new(false));
        let _g = BakeDeadlineGuard::arm_with(pid, Duration::from_millis(150), timed_out.clone());
        // Poll for up to 3 s for the watchdog to fire + reap.
        let mut status = None;
        for _ in 0..60 {
            std::thread::sleep(Duration::from_millis(50));
            if let Some(s) = child.try_wait().expect("try_wait") {
                status = Some(s);
                break;
            }
        }
        let status = status.expect("watchdog should have killed the stuck child");
        assert_eq!(
            status.signal(),
            Some(libc::SIGKILL),
            "child must be SIGKILLed by the deadline guard"
        );
        assert!(
            timed_out.load(Ordering::Acquire),
            "guard must flag timed_out so the driver surfaces a deadline error"
        );
    }

    #[test]
    fn deadline_guard_cancelled_on_drop_spares_child() {
        let mut child = spawn_sleeper();
        let pid = child.id() as i32;
        let timed_out = Arc::new(AtomicBool::new(false));
        {
            // A 5 s deadline, but we drop the guard immediately — Drop signals
            // the watchdog to stop before it ever fires.
            let _g = BakeDeadlineGuard::arm_with(pid, Duration::from_secs(5), timed_out.clone());
        }
        std::thread::sleep(Duration::from_millis(400));
        assert!(
            !timed_out.load(Ordering::Acquire),
            "a cancelled guard must NOT time out"
        );
        assert!(
            child.try_wait().expect("try_wait").is_none(),
            "child must still be alive after the guard was cancelled"
        );
        let _ = child.kill();
        let _ = child.wait();
    }

    #[test]
    fn deadline_secs_zero_disables_guard() {
        let mut child = spawn_sleeper();
        let pid = child.id() as i32;
        let timed_out = Arc::new(AtomicBool::new(false));
        let g = BakeDeadlineGuard::arm(pid, 0, timed_out.clone());
        assert!(
            g.handle.is_none(),
            "secs=0 must not spawn a watchdog thread"
        );
        std::thread::sleep(Duration::from_millis(200));
        assert!(!timed_out.load(Ordering::Acquire));
        assert!(
            child.try_wait().expect("try_wait").is_none(),
            "child survives"
        );
        let _ = child.kill();
        let _ = child.wait();
    }
}
