megacode-agent-sdk 0.30.0

Drive a MEGA harness agent session from a Rust program.
Documentation
//! Which harness binary a session runs (spec slice-ar R12). In order: the
//! `binary` option; `MEGACODE_AGENT_SDK_BINARY`; the verified cache; a
//! download of the exact matching release, checked against the digests
//! pinned in this crate (`pins.rs`).

// Without the download only the first two are used.
#![cfg_attr(not(all(unix, feature = "download")), allow(dead_code, unused_imports))]

#[cfg(all(unix, feature = "download"))]
mod cache;
#[cfg(all(unix, feature = "download"))]
mod download;
mod pins;
#[cfg(all(unix, feature = "download"))]
mod tar;

use std::ffi::OsString;
use std::path::{Path, PathBuf};

use crate::error::Error;
use crate::options::Options;
use crate::process::BINARY_ENV;

use pins::{PINS, PINS_VERSION, Pin};

/// Where releases are downloaded from (`crates/cli/src/update.rs`'s
/// `SITE_DL`).
const DEFAULT_BASE: &str = "https://megacode-harness.vercel.app/dl";
/// Overrides [`DEFAULT_BASE`].
const BASE_ENV: &str = "MEGACODE_AGENT_SDK_DOWNLOAD_BASE";

/// The release targets (spec R12.6, `release.yml`'s matrix).
const TARGETS: [&str; 3] = [
    "x86_64-unknown-linux-musl",
    "aarch64-apple-darwin",
    "x86_64-apple-darwin",
];

/// The binary a session runs (R12.1).
pub(crate) async fn locate(options: &Options) -> Result<PathBuf, Error> {
    if let Some(path) = named(options.binary.as_deref(), std::env::var_os(BINARY_ENV)) {
        return Ok(path);
    }
    #[cfg(all(unix, feature = "download"))]
    {
        Release::current()?.fetch().await
    }
    #[cfg(not(all(unix, feature = "download")))]
    {
        Err(Error::BinaryUnavailable(format!(
            "no binary was named, and this build of the SDK does not download one: set \
             Options::binary or {BINARY_ENV}"
        )))
    }
}

/// The binary the program named: the option, else the environment
/// variable. Both are trusted as given.
fn named(option: Option<&Path>, env: Option<OsString>) -> Option<PathBuf> {
    match (option, env) {
        (Some(path), _) => Some(path.to_path_buf()),
        (None, Some(path)) if !path.is_empty() => Some(PathBuf::from(path)),
        _ => None,
    }
}

/// The release target for `os` and `arch` (`std::env::consts`).
fn target_for(os: &str, arch: &str) -> Result<&'static str, Error> {
    match (os, arch) {
        ("linux", "x86_64") => Ok(TARGETS[0]),
        ("macos", "aarch64") => Ok(TARGETS[1]),
        ("macos", "x86_64") => Ok(TARGETS[2]),
        _ => Err(Error::BinaryUnavailable(format!(
            "no harness release is built for {os} on {arch}: releases exist for {}, {} and {}. \
             Name an installed harness with the `binary` option (Options::binary) or \
             {BINARY_ENV}",
            TARGETS[0], TARGETS[1], TARGETS[2]
        ))),
    }
}

/// `<cache>` (R12.3): `~/Library/Caches` on macOS; elsewhere
/// `$XDG_CACHE_HOME` when it is an absolute path, else `~/.cache`.
fn cache_root_from(
    os: &str,
    xdg_cache_home: Option<OsString>,
    home: Option<OsString>,
) -> Result<PathBuf, Error> {
    if os != "macos"
        && let Some(xdg) = xdg_cache_home.map(PathBuf::from)
        && xdg.is_absolute()
    {
        return Ok(xdg);
    }
    let home = home
        .map(PathBuf::from)
        .filter(|h| h.is_absolute())
        .ok_or_else(|| {
            Error::BinaryUnavailable(format!(
                "no cache folder for the harness download: HOME is not set to an absolute \
                 path. Name an installed harness with Options::binary or {BINARY_ENV}"
            ))
        })?;
    Ok(if os == "macos" {
        home.join("Library").join("Caches")
    } else {
        home.join(".cache")
    })
}

/// The pin for `target` when the pins are for `version`.
fn pinned(pins_version: &str, pins: &[(&str, Pin)], version: &str, target: &str) -> Option<Pin> {
    if pins_version != version {
        return None;
    }
    pins.iter().find(|(t, _)| *t == target).map(|(_, p)| *p)
}

/// Lowercase hex.
fn hex(bytes: &[u8]) -> String {
    use std::fmt::Write as _;
    bytes.iter().fold(String::new(), |mut s, b| {
        let _ = write!(s, "{b:02x}");
        s
    })
}

/// One version's release for one target, where it is downloaded from, the
/// cache it goes to, and its pins.
#[cfg(all(unix, feature = "download"))]
#[derive(Debug, Clone)]
pub struct Release {
    version: String,
    target: String,
    base: String,
    cache: PathBuf,
    archive_sha256: String,
    executable_sha256: String,
}

#[cfg(all(unix, feature = "download"))]
impl Release {
    /// A release with the given pins: the test-only constructor
    /// (`megacode_agent_sdk::__testing`); a program's sessions use the pins
    /// in this crate.
    pub fn new(
        version: &str,
        target: &str,
        base: &str,
        cache: &Path,
        archive_sha256: &str,
        executable_sha256: &str,
    ) -> Release {
        Release {
            version: version.into(),
            target: target.into(),
            base: base.trim_end_matches('/').into(),
            cache: cache.to_path_buf(),
            archive_sha256: archive_sha256.into(),
            executable_sha256: executable_sha256.into(),
        }
    }

    /// This crate's version for this platform, with its pins.
    fn current() -> Result<Release, Error> {
        let target = target_for(std::env::consts::OS, std::env::consts::ARCH)?;
        let version = env!("CARGO_PKG_VERSION");
        let pin = pinned(PINS_VERSION, PINS, version, target).ok_or_else(|| {
            Error::BinaryUnavailable(format!(
                "this build of megacode-agent-sdk {version} carries no pinned harness release \
                 for {target}, so it cannot download one. Name an installed harness with \
                 Options::binary or {BINARY_ENV}"
            ))
        })?;
        let base = std::env::var(BASE_ENV)
            .ok()
            .filter(|b| !b.is_empty())
            .unwrap_or_else(|| DEFAULT_BASE.to_string());
        let cache = cache_root_from(
            std::env::consts::OS,
            std::env::var_os("XDG_CACHE_HOME"),
            std::env::var_os("HOME"),
        )?;
        Ok(Release::new(
            version,
            target,
            &base,
            &cache,
            pin.archive_sha256,
            pin.executable_sha256,
        ))
    }

    /// The verified cached executable, downloading it when it is missing
    /// or its digest is not the pinned one (R12.3–R12.4). Nothing that
    /// fails a check is run or kept.
    pub async fn fetch(&self) -> Result<PathBuf, Error> {
        use cache::Cached;
        let layout = cache::Layout::new(&self.cache, &self.version, &self.target);
        // A file with another digest is never run; the verified download is
        // renamed over it. It is not removed first: another program may have
        // renamed its own verified file into place since this check.
        if let Cached::Verified = self.check(&layout).await? {
            return Ok(layout.file);
        }
        let executable = self.download().await.map_err(Error::BinaryUnavailable)?;
        let (l, digest) = (layout.clone(), self.executable_sha256.clone());
        tokio::task::spawn_blocking(move || cache::install(&l, &executable, &digest))
            .await
            .map_err(|e| Error::BinaryUnavailable(format!("the cache write stopped: {e}")))??;
        match self.check(&layout).await? {
            Cached::Verified => Ok(layout.file),
            Cached::Missing | Cached::Mismatch => Err(Error::BinaryUnavailable(format!(
                "the harness cached at {} does not match its pinned SHA-256 after a fresh \
                 download; it is not run",
                layout.file.display()
            ))),
        }
    }

    async fn check(&self, layout: &cache::Layout) -> Result<cache::Cached, Error> {
        let (l, digest) = (layout.clone(), self.executable_sha256.clone());
        tokio::task::spawn_blocking(move || cache::check(&l, &digest))
            .await
            .map_err(|e| Error::BinaryUnavailable(format!("the cache check stopped: {e}")))?
    }

    /// The archive, checked, and the executable from it, checked.
    async fn download(&self) -> Result<Vec<u8>, String> {
        let url = format!(
            "{}/v{}/harness-{}.tar.gz",
            self.base, self.version, self.target
        );
        let archive = download::archive(&url, &self.archive_sha256).await?;
        let digest = self.executable_sha256.clone();
        let unpacked = tokio::task::spawn_blocking(move || {
            let executable = tar::executable(&archive)?;
            let got = {
                use sha2::Digest as _;
                hex(&sha2::Sha256::digest(&executable))
            };
            if got != digest {
                return Err(format!(
                    "the harness in the archive does not match its pinned SHA-256 (got {got}, \
                     pinned {digest})"
                ));
            }
            Ok(executable)
        })
        .await
        .map_err(|e| e.to_string())?;
        unpacked.map_err(|why| format!("cannot use {url}: {why}; nothing was cached"))
    }
}

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

    #[test]
    fn the_option_comes_before_the_environment_and_both_before_the_cache() {
        let opt = Path::new("/opt/harness");
        assert_eq!(
            named(Some(opt), Some("/env/harness".into())),
            Some(opt.to_path_buf())
        );
        assert_eq!(
            named(None, Some("/env/harness".into())),
            Some(PathBuf::from("/env/harness"))
        );
        assert_eq!(named(None, Some(OsString::new())), None);
        assert_eq!(named(None, None), None);
    }

    #[test]
    fn another_platform_is_told_the_three_targets_and_the_binary_option() {
        assert_eq!(
            target_for("linux", "x86_64").unwrap(),
            "x86_64-unknown-linux-musl"
        );
        assert_eq!(
            target_for("macos", "aarch64").unwrap(),
            "aarch64-apple-darwin"
        );
        assert_eq!(
            target_for("macos", "x86_64").unwrap(),
            "x86_64-apple-darwin"
        );
        for (os, arch) in [("linux", "aarch64"), ("windows", "x86_64")] {
            let Err(Error::BinaryUnavailable(why)) = target_for(os, arch) else {
                panic!("{os} {arch}");
            };
            for t in TARGETS {
                assert!(why.contains(t), "{why}");
            }
            assert!(why.contains("`binary` option"), "{why}");
        }
    }

    #[test]
    fn the_cache_folder_per_platform() {
        let some = |s: &str| Some(OsString::from(s));
        assert_eq!(
            cache_root_from("linux", some("/x/cache"), some("/home/u")).unwrap(),
            PathBuf::from("/x/cache")
        );
        assert_eq!(
            cache_root_from("linux", some("relative"), some("/home/u")).unwrap(),
            PathBuf::from("/home/u/.cache")
        );
        assert_eq!(
            cache_root_from("linux", None, some("/home/u")).unwrap(),
            PathBuf::from("/home/u/.cache")
        );
        assert_eq!(
            cache_root_from("macos", some("/x/cache"), some("/Users/u")).unwrap(),
            PathBuf::from("/Users/u/Library/Caches")
        );
        assert!(cache_root_from("linux", None, None).is_err());
        assert!(cache_root_from("linux", None, some("relative")).is_err());
    }

    #[test]
    fn pins_of_another_version_are_no_pins() {
        let pin = Pin {
            archive_sha256: "a",
            executable_sha256: "b",
        };
        let pins = [("x86_64-unknown-linux-musl", pin)];
        assert_eq!(
            pinned("0.30.0", &pins, "0.30.0", "x86_64-unknown-linux-musl"),
            Some(pin)
        );
        assert_eq!(
            pinned("0.30.0", &pins, "0.31.0", "x86_64-unknown-linux-musl"),
            None
        );
        assert_eq!(
            pinned("0.30.0", &pins, "0.30.0", "aarch64-apple-darwin"),
            None
        );
    }

    /// R12.5: the pins are empty, or one well-formed entry per release
    /// target with its version named.
    #[test]
    fn the_pins_are_empty_or_well_formed() {
        if PINS.is_empty() {
            return;
        }
        let digest =
            |d: &str| d.len() == 64 && d.bytes().all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'f'));
        assert!(!PINS_VERSION.is_empty(), "pins without a version");
        let mut targets: Vec<&str> = PINS.iter().map(|(t, _)| *t).collect();
        targets.sort_unstable();
        let mut known = TARGETS.to_vec();
        known.sort_unstable();
        assert_eq!(targets, known, "one pin per release target");
        for (t, p) in PINS {
            assert!(digest(p.archive_sha256), "{t}: {}", p.archive_sha256);
            assert!(digest(p.executable_sha256), "{t}: {}", p.executable_sha256);
        }
    }
}