#![cfg(all(unix, feature = "download"))]
use std::collections::HashMap;
use std::io::{Read, Write};
use std::net::TcpListener;
use std::os::unix::fs::{PermissionsExt, symlink};
use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use megacode_agent_sdk::__testing::Release;
use megacode_agent_sdk::Error;
use sha2::{Digest, Sha256};
const VERSION: &str = "0.30.0";
const TARGET: &str = "x86_64-unknown-linux-musl";
const TARGETS: [&str; 3] = [
"x86_64-unknown-linux-musl",
"aarch64-apple-darwin",
"x86_64-apple-darwin",
];
const EXECUTABLE: &[u8] = b"#!/bin/sh\necho 'harness 0.30.0'\n";
struct Scratch {
root: PathBuf,
paths: Vec<PathBuf>,
}
impl Scratch {
fn new(tag: &str) -> Self {
let tmp = std::fs::canonicalize(std::env::temp_dir()).unwrap();
let root = tmp.join(format!(
"megacode-sdk-binary-{tag}-{}-{:x}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
std::fs::create_dir(&root).unwrap();
std::fs::set_permissions(&root, std::fs::Permissions::from_mode(0o700)).unwrap();
Self {
root,
paths: Vec::new(),
}
}
fn path(&mut self, rel: &str) -> PathBuf {
let p = self.root.join(rel);
self.paths.push(p.clone());
p
}
fn dir(&mut self, rel: &str, mode: u32) -> PathBuf {
let p = self.path(rel);
std::fs::create_dir(&p).unwrap();
std::fs::set_permissions(&p, std::fs::Permissions::from_mode(mode)).unwrap();
p
}
fn file(&mut self, rel: &str, bytes: &[u8], mode: u32) -> PathBuf {
let p = self.path(rel);
std::fs::write(&p, bytes).unwrap();
std::fs::set_permissions(&p, std::fs::Permissions::from_mode(mode)).unwrap();
p
}
fn cache_tree(&mut self, cache: &str) {
for rel in [
"megacode-agent-sdk".to_string(),
format!("megacode-agent-sdk/{VERSION}"),
format!("megacode-agent-sdk/{VERSION}/{TARGET}"),
format!("megacode-agent-sdk/{VERSION}/{TARGET}/harness"),
] {
self.path(&format!("{cache}/{rel}"));
}
}
}
impl Drop for Scratch {
fn drop(&mut self) {
for p in self.paths.iter().rev() {
if std::fs::remove_file(p).is_err() {
let _ = std::fs::remove_dir(p);
}
}
let _ = std::fs::remove_dir(&self.root);
}
}
fn entries(dir: &Path) -> Vec<String> {
let mut out = Vec::new();
let mut todo = vec![dir.to_path_buf()];
while let Some(d) = todo.pop() {
let Ok(rd) = std::fs::read_dir(&d) else {
continue;
};
for e in rd.flatten() {
let p = e.path();
out.push(p.strip_prefix(dir).unwrap().display().to_string());
if e.file_type().unwrap().is_dir() {
todo.push(p);
}
}
}
out.sort();
out
}
fn hex(bytes: &[u8]) -> String {
Sha256::digest(bytes)
.iter()
.map(|b| format!("{b:02x}"))
.collect()
}
struct Member {
name: String,
kind: u8,
body: Vec<u8>,
link: String,
}
fn regular(name: &str, body: &[u8]) -> Member {
Member {
name: name.into(),
kind: b'0',
body: body.to_vec(),
link: String::new(),
}
}
fn tar_header(m: &Member) -> [u8; 512] {
let mut h = [0u8; 512];
h[..m.name.len()].copy_from_slice(m.name.as_bytes());
h[100..107].copy_from_slice(b"0000755");
h[108..115].copy_from_slice(b"0001750");
h[116..123].copy_from_slice(b"0001750");
let size = format!("{:011o}", m.body.len());
h[124..135].copy_from_slice(size.as_bytes());
h[136..147].copy_from_slice(b"14000000000");
h[156] = m.kind;
h[157..157 + m.link.len()].copy_from_slice(m.link.as_bytes());
h[257..263].copy_from_slice(b"ustar\0");
h[263..265].copy_from_slice(b"00");
h[148..156].copy_from_slice(b" ");
let sum: u32 = h.iter().map(|&b| u32::from(b)).sum();
h[148..156].copy_from_slice(format!("{sum:06o}\0 ").as_bytes());
h
}
fn archive(members: &[Member]) -> Vec<u8> {
let mut tar = Vec::new();
for m in members {
tar.extend_from_slice(&tar_header(m));
tar.extend_from_slice(&m.body);
tar.resize(tar.len().div_ceil(512) * 512, 0);
}
tar.extend_from_slice(&[0u8; 1024]);
let mut gz = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::fast());
gz.write_all(&tar).unwrap();
gz.finish().unwrap()
}
fn good_archive() -> Vec<u8> {
archive(&[regular("harness", EXECUTABLE)])
}
#[derive(Clone)]
enum Reply {
Body(Vec<u8>),
Unsized(Vec<u8>),
Redirect(String),
}
struct Server {
base: String,
seen: Arc<Mutex<Vec<String>>>,
}
#[derive(Clone, Copy)]
enum Hold {
None,
Until(usize),
}
impl Server {
fn start(routes: HashMap<String, Reply>, hold: Hold) -> Server {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let base = format!("http://{}", listener.local_addr().unwrap());
let seen = Arc::new(Mutex::new(Vec::new()));
let log = seen.clone();
let routes = Arc::new(routes);
std::thread::spawn(move || {
for sock in listener.incoming() {
let Ok(mut sock) = sock else { return };
let routes = routes.clone();
let log = log.clone();
std::thread::spawn(move || {
let path = read_path(&mut sock);
log.lock().unwrap().push(path.clone());
if let Hold::Until(n) = hold {
let deadline = std::time::Instant::now() + Duration::from_secs(10);
while log.lock().unwrap().len() < n && std::time::Instant::now() < deadline
{
std::thread::sleep(Duration::from_millis(10));
}
}
let head = |status: &str, extra: &str, len: Option<usize>| {
let len = len.map(|n| format!("Content-Length: {n}\r\n"));
format!(
"HTTP/1.1 {status}\r\nConnection: close\r\n{extra}{}\r\n",
len.unwrap_or_default()
)
};
let _ = match routes.get(&path) {
Some(Reply::Body(b)) => sock
.write_all(head("200 OK", "", Some(b.len())).as_bytes())
.and_then(|()| sock.write_all(b)),
Some(Reply::Unsized(b)) => sock
.write_all(head("200 OK", "", None).as_bytes())
.and_then(|()| sock.write_all(b)),
Some(Reply::Redirect(to)) => sock.write_all(
head("302 Found", &format!("Location: {to}\r\n"), Some(0)).as_bytes(),
),
None => sock.write_all(head("404 Not Found", "", Some(0)).as_bytes()),
};
let _ = sock.flush();
});
}
});
Server { base, seen }
}
fn requests(&self) -> Vec<String> {
self.seen.lock().unwrap().clone()
}
}
fn read_path(sock: &mut impl Read) -> String {
let mut head = Vec::new();
let mut byte = [0u8; 1];
while !head.ends_with(b"\r\n\r\n") {
match sock.read(&mut byte) {
Ok(1) => head.push(byte[0]),
_ => break,
}
}
let head = String::from_utf8_lossy(&head);
head.split(' ').nth(1).unwrap_or("").to_string()
}
fn archive_path(target: &str) -> String {
format!("/dl/v{VERSION}/harness-{target}.tar.gz")
}
fn release_of(archive: Vec<u8>) -> Server {
Server::start(
HashMap::from([(archive_path(TARGET), Reply::Body(archive))]),
Hold::None,
)
}
fn release(
server: &Server,
cache: &Path,
archive_sha256: &str,
executable_sha256: &str,
) -> Release {
Release::new(
VERSION,
TARGET,
&format!("{}/dl", server.base),
cache,
archive_sha256,
executable_sha256,
)
}
fn cached(cache: &Path) -> PathBuf {
cache.join(format!("megacode-agent-sdk/{VERSION}/{TARGET}/harness"))
}
fn unavailable(r: Result<PathBuf, Error>) -> String {
match r {
Err(Error::BinaryUnavailable(why)) => why,
other => panic!("expected BinaryUnavailable, got {other:?}"),
}
}
#[tokio::test]
async fn download_and_verify_then_start_from_the_cache() {
let mut s = Scratch::new("good");
let cache = s.dir("cache", 0o700);
s.cache_tree("cache");
let archive = good_archive();
let server = release_of(archive.clone());
let r = release(&server, &cache, &hex(&archive), &hex(EXECUTABLE));
let path = r.fetch().await.unwrap();
assert_eq!(path, cached(&cache));
assert_eq!(std::fs::read(&path).unwrap(), EXECUTABLE);
let mode = |p: &Path| std::fs::metadata(p).unwrap().permissions().mode() & 0o7777;
assert_eq!(mode(&path), 0o755);
for d in [
cache.join("megacode-agent-sdk"),
cache.join(format!("megacode-agent-sdk/{VERSION}")),
cache.join(format!("megacode-agent-sdk/{VERSION}/{TARGET}")),
] {
assert_eq!(mode(&d), 0o700, "{}", d.display());
}
assert_eq!(server.requests(), vec![archive_path(TARGET)]);
assert_eq!(
entries(&cache.join(format!("megacode-agent-sdk/{VERSION}/{TARGET}"))),
vec!["harness"],
"no temporary file is left"
);
assert_eq!(r.fetch().await.unwrap(), path);
assert_eq!(
server.requests().len(),
1,
"the second start reads the cache"
);
}
#[tokio::test]
async fn pax_headers_and_appledouble_members_are_skipped() {
let mut s = Scratch::new("pax");
let cache = s.dir("cache", 0o700);
s.cache_tree("cache");
let pax = |kind| Member {
name: "PaxHeader/harness".into(),
kind,
body: b"30 mtime=1700000000.123456789\n".to_vec(),
link: String::new(),
};
let archive = archive(&[
pax(b'g'),
regular("./._harness", b"\0\x05\x16\x07appledouble"),
pax(b'x'),
regular("./harness", EXECUTABLE),
]);
let server = release_of(archive.clone());
let path = release(&server, &cache, &hex(&archive), &hex(EXECUTABLE))
.fetch()
.await
.unwrap();
assert_eq!(std::fs::read(path).unwrap(), EXECUTABLE);
}
#[tokio::test]
async fn each_bad_release_fails_and_leaves_nothing_in_the_cache() {
let oversized = vec![0u8; 64 * 1024 * 1024 + 1];
let link = |kind: u8, name: &str| Member {
name: name.into(),
kind,
body: Vec::new(),
link: "/bin/sh".into(),
};
let wrong = "0".repeat(64);
let cases: Vec<(&str, Reply, Option<String>, String, &str)> = vec![
(
"wrong archive digest",
Reply::Body(good_archive()),
Some(wrong.clone()),
hex(EXECUTABLE),
"does not match",
),
(
"wrong executable digest",
Reply::Body(good_archive()),
None,
wrong.clone(),
"does not match",
),
(
"a second regular file",
Reply::Body(archive(&[
regular("harness", EXECUTABLE),
regular("README", b"hi"),
])),
None,
hex(EXECUTABLE),
"README",
),
(
"a second harness",
Reply::Body(archive(&[
regular("harness", EXECUTABLE),
regular("./harness", EXECUTABLE),
])),
None,
hex(EXECUTABLE),
"second",
),
(
"a symlink member",
Reply::Body(archive(&[link(b'2', "harness")])),
None,
hex(EXECUTABLE),
"harness",
),
(
"a hard link member",
Reply::Body(archive(&[regular("harness", EXECUTABLE), link(b'1', "sh")])),
None,
hex(EXECUTABLE),
"sh",
),
(
"a .. path",
Reply::Body(archive(&[
regular("../._x", b"appledouble"),
regular("harness", EXECUTABLE),
])),
None,
hex(EXECUTABLE),
"..",
),
(
"no harness at all",
Reply::Body(archive(&[])),
None,
hex(EXECUTABLE),
"no harness",
),
(
"an oversized archive, its length stated",
Reply::Body(oversized.clone()),
None,
hex(EXECUTABLE),
"64 MiB",
),
(
"an oversized archive, no length stated",
Reply::Unsized(oversized),
None,
hex(EXECUTABLE),
"64 MiB",
),
(
"a redirect to http",
Reply::Redirect(format!(
"http://megacode-release.invalid{}",
archive_path(TARGET)
)),
Some(wrong.clone()),
hex(EXECUTABLE),
"not https",
),
(
"a redirect loop",
Reply::Redirect(archive_path(TARGET)),
Some(wrong.clone()),
hex(EXECUTABLE),
"redirect",
),
];
for (case, reply, archive_pin, exe_pin, says) in cases {
let mut s = Scratch::new("bad");
let cache = s.dir("cache", 0o700);
s.cache_tree("cache");
let archive_pin = archive_pin.unwrap_or_else(|| match &reply {
Reply::Body(b) | Reply::Unsized(b) => hex(b),
Reply::Redirect(_) => unreachable!(),
});
let server = Server::start(HashMap::from([(archive_path(TARGET), reply)]), Hold::None);
let why = unavailable(
release(&server, &cache, &archive_pin, &exe_pin)
.fetch()
.await,
);
assert!(why.contains(says), "{case}: {why}");
assert_eq!(entries(&cache), Vec::<String>::new(), "{case}: {why}");
}
}
#[tokio::test]
async fn a_base_with_user_info_or_plain_http_elsewhere_is_refused() {
let mut s = Scratch::new("base");
let cache = s.dir("cache", 0o700);
for (base, says) in [
("http://user:pw@127.0.0.1:9/dl", "user info"),
("http://megacode-release.invalid/dl", "not https"),
] {
let r = Release::new(
VERSION,
TARGET,
base,
&cache,
&"0".repeat(64),
&"0".repeat(64),
);
let why = unavailable(r.fetch().await);
assert!(why.contains(says), "{base}: {why}");
assert_eq!(entries(&cache), Vec::<String>::new());
}
}
#[tokio::test]
async fn a_planted_or_half_written_cache_file_is_replaced_never_run() {
for case in ["planted", "half-written"] {
let mut s = Scratch::new("planted");
let cache = s.dir("cache", 0o700);
let marker = s.path("ran");
s.dir("cache/megacode-agent-sdk", 0o700);
s.dir(&format!("cache/megacode-agent-sdk/{VERSION}"), 0o700);
s.dir(
&format!("cache/megacode-agent-sdk/{VERSION}/{TARGET}"),
0o700,
);
let body = match case {
"planted" => format!("#!/bin/sh\ntouch '{}'\n", marker.display()).into_bytes(),
_ => EXECUTABLE[..10].to_vec(),
};
let file = s.file(
&format!("cache/megacode-agent-sdk/{VERSION}/{TARGET}/harness"),
&body,
0o755,
);
let archive = good_archive();
let server = release_of(archive.clone());
let path = release(&server, &cache, &hex(&archive), &hex(EXECUTABLE))
.fetch()
.await
.unwrap();
assert_eq!(path, file);
assert_eq!(std::fs::read(&path).unwrap(), EXECUTABLE, "{case}");
assert!(!marker.exists(), "{case}: the planted file ran");
assert_eq!(server.requests().len(), 1, "{case}");
}
}
#[tokio::test]
async fn a_stale_temporary_file_is_swept_and_a_fresh_one_kept() {
let mut s = Scratch::new("sweep");
let cache = s.dir("cache", 0o700);
let dir = format!("cache/megacode-agent-sdk/{VERSION}/{TARGET}");
s.dir("cache/megacode-agent-sdk", 0o700);
s.dir(&format!("cache/megacode-agent-sdk/{VERSION}"), 0o700);
s.dir(&dir, 0o700);
s.path(&format!("{dir}/harness"));
let stale = s.file(&format!("{dir}/.harness.1.0.tmp"), b"half", 0o700);
let fresh = s.file(&format!("{dir}/.harness.2.0.tmp"), b"half", 0o700);
let old = std::time::SystemTime::now() - Duration::from_secs(2 * 60 * 60);
std::fs::File::options()
.write(true)
.open(&stale)
.unwrap()
.set_modified(old)
.unwrap();
let archive = good_archive();
let server = release_of(archive.clone());
release(&server, &cache, &hex(&archive), &hex(EXECUTABLE))
.fetch()
.await
.unwrap();
assert!(!stale.exists(), "the stale file is swept");
assert!(fresh.exists(), "a live writer's file is kept");
}
#[tokio::test]
async fn a_symlinked_or_group_writable_cache_folder_or_ancestor_is_refused() {
let archive = good_archive();
let server = release_of(archive.clone());
let sdk = "megacode-agent-sdk".to_string();
let ver = format!("{sdk}/{VERSION}");
let tgt = format!("{ver}/{TARGET}");
for level in ["", sdk.as_str(), ver.as_str(), tgt.as_str()] {
for how in ["symlink", "group-writable", "other-writable"] {
let mut s = Scratch::new("folder");
let cache = s.dir("cache", 0o700);
let mut bad = cache.clone();
if level.is_empty() {
if how == "symlink" {
s.dir("elsewhere", 0o700);
bad = s.path("cache-link");
symlink(s.root.join("elsewhere"), &bad).unwrap();
} else {
let mode = if how == "group-writable" {
0o770
} else {
0o707
};
std::fs::set_permissions(&cache, std::fs::Permissions::from_mode(mode))
.unwrap();
}
} else {
let parts: Vec<&str> = level.split('/').collect();
for i in 0..parts.len() - 1 {
s.dir(&format!("cache/{}", parts[..=i].join("/")), 0o700);
}
bad = cache.join(level);
if how == "symlink" {
s.dir("elsewhere", 0o700);
s.path(&format!("cache/{level}"));
symlink(s.root.join("elsewhere"), &bad).unwrap();
} else {
let mode = if how == "group-writable" {
0o770
} else {
0o707
};
s.dir(&format!("cache/{level}"), mode);
}
}
let root = if level.is_empty() && how == "symlink" {
bad.clone()
} else {
cache.clone()
};
let r = release(&server, &root, &hex(&archive), &hex(EXECUTABLE));
let why = unavailable(r.fetch().await);
assert!(
why.contains(&bad.display().to_string()),
"{level}/{how}: {why}"
);
}
}
for how in ["symlink", "group-writable"] {
let mut s = Scratch::new("ancestor");
let bad = if how == "symlink" {
s.dir("real", 0o700);
let link = s.path("link");
symlink(s.root.join("real"), &link).unwrap();
s.dir("real/cache", 0o700);
link
} else {
s.dir("shared", 0o770);
s.dir("shared/cache", 0o700);
s.root.join("shared")
};
let r = release(
&server,
&bad.join("cache"),
&hex(&archive),
&hex(EXECUTABLE),
);
let why = unavailable(r.fetch().await);
assert!(
why.contains(&bad.display().to_string()),
"ancestor {how}: {why}"
);
}
assert_eq!(
server.requests(),
Vec::<String>::new(),
"nothing downloaded"
);
}
#[tokio::test]
async fn a_symlinked_or_group_writable_cached_executable_is_refused() {
let archive = good_archive();
let server = release_of(archive.clone());
for how in ["symlink", "group-writable", "other-writable"] {
let mut s = Scratch::new("exe");
let cache = s.dir("cache", 0o700);
s.dir("cache/megacode-agent-sdk", 0o700);
s.dir(&format!("cache/megacode-agent-sdk/{VERSION}"), 0o700);
s.dir(
&format!("cache/megacode-agent-sdk/{VERSION}/{TARGET}"),
0o700,
);
let rel = format!("cache/megacode-agent-sdk/{VERSION}/{TARGET}/harness");
let file = match how {
"symlink" => {
let real = s.file("real-harness", EXECUTABLE, 0o755);
let file = s.path(&rel);
symlink(real, &file).unwrap();
file
}
"group-writable" => s.file(&rel, EXECUTABLE, 0o775),
_ => s.file(&rel, EXECUTABLE, 0o757),
};
let r = release(&server, &cache, &hex(&archive), &hex(EXECUTABLE));
let why = unavailable(r.fetch().await);
assert!(why.contains(&file.display().to_string()), "{how}: {why}");
}
assert_eq!(
server.requests(),
Vec::<String>::new(),
"nothing downloaded"
);
}
#[test]
fn two_concurrent_first_starts_end_with_one_verified_file() {
let mut s = Scratch::new("race");
let cache = s.dir("cache", 0o700);
s.cache_tree("cache");
let archive = good_archive();
let server = Server::start(
HashMap::from([(archive_path(TARGET), Reply::Body(archive.clone()))]),
Hold::Until(2),
);
let starts: Vec<_> = (0..2)
.map(|_| {
let r = release(&server, &cache, &hex(&archive), &hex(EXECUTABLE));
std::thread::spawn(move || {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(r.fetch())
})
})
.collect();
for start in starts {
let path = start.join().unwrap().unwrap();
assert_eq!(path, cached(&cache));
}
assert_eq!(
server.requests().len(),
2,
"both started before either finished"
);
assert_eq!(std::fs::read(cached(&cache)).unwrap(), EXECUTABLE);
assert_eq!(
entries(&cache.join(format!("megacode-agent-sdk/{VERSION}/{TARGET}"))),
vec!["harness"]
);
}
fn repo() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("../..")
}
fn script_tree(s: &mut Scratch, pins: &str) -> PathBuf {
script_tree_with(s, pins, EMPTY_PY_PINS, EMPTY_TS_PINS)
}
fn script_tree_with(s: &mut Scratch, pins: &str, py: &str, ts: &str) -> PathBuf {
let tree = s.dir("tree", 0o700);
for d in [
"tree/scripts",
"tree/scripts/sdk",
"tree/sdk",
"tree/sdk/rust",
"tree/sdk/rust/src",
"tree/sdk/rust/src/binary",
"tree/sdk/python",
"tree/sdk/python/src",
"tree/sdk/python/src/megacode_agent_sdk",
"tree/sdk/typescript",
"tree/sdk/typescript/src",
] {
s.dir(d, 0o700);
}
s.file(
"tree/sdk/python/src/megacode_agent_sdk/_pins.py",
py.as_bytes(),
0o644,
);
s.file(
"tree/sdk/python/src/megacode_agent_sdk/_version.py",
format!("__version__ = \"{VERSION}\"\n").as_bytes(),
0o644,
);
s.file("tree/sdk/typescript/src/pins.ts", ts.as_bytes(), 0o644);
s.file(
"tree/sdk/typescript/src/version.ts",
format!("// The SDK's version.\nexport const VERSION: string = \"{VERSION}\";\n")
.as_bytes(),
0o644,
);
s.file(
"tree/sdk/typescript/package.json",
format!("{{\n \"name\": \"megacode-agent-sdk\",\n \"version\": \"{VERSION}\"\n}}\n")
.as_bytes(),
0o644,
);
s.file(
"tree/sdk/typescript/package-lock.json",
format!("{{\n \"version\": \"{VERSION}\",\n \"packages\": {{\n \"\": {{\n \"version\": \"{VERSION}\"\n }}\n }}\n}}\n").as_bytes(),
0o644,
);
for f in [
"pin.sh",
"package.sh",
"harness-from-archive.py",
"check-pins.py",
] {
let body = std::fs::read(repo().join("scripts/sdk").join(f)).unwrap();
s.file(&format!("tree/scripts/sdk/{f}"), &body, 0o755);
}
s.file("tree/sdk/rust/src/binary/pins.rs", pins.as_bytes(), 0o644);
s.file(
"tree/Cargo.toml",
format!("[workspace]\nmembers = []\n\n[workspace.package]\nedition = \"2024\"\nversion = \"{VERSION}\"\n")
.as_bytes(),
0o644,
);
tree
}
const EMPTY_PINS: &str = r#"//! The pinned digests of this SDK's harness release (spec slice-ar R12.5),
//! written by `scripts/sdk/pin.sh` from the signed release; empty until a
//! release is pinned. `scripts/sdk/package.sh` refuses to package empty or
//! malformed pins.
/// One target's SHA-256 digests, 64 lowercase hex characters each.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Pin {
/// Of the release archive, as downloaded.
pub archive_sha256: &'static str,
/// Of the `harness` executable inside it.
pub executable_sha256: &'static str,
}
/// The harness version the pins are for; a crate of another version has no
/// pins.
pub const PINS_VERSION: &str = "";
/// `(target, pin)` for each release target.
pub const PINS: &[(&str, Pin)] = &[];
"#;
const EMPTY_PY_PINS: &str = r#""""The pinned digests of this SDK's harness release (spec slice-ar R12.5),
written by `scripts/sdk/pin.sh` from the signed release; empty until a
release is pinned. `scripts/sdk/package.sh` refuses to package empty or
malformed pins.
"""
# The harness version the pins are for; a package of another version has no pins.
PINS_VERSION: str = ""
# target -> (archive SHA-256, executable SHA-256), 64 lowercase hex characters each.
PINS: dict[str, tuple[str, str]] = {}
"#;
const EMPTY_TS_PINS: &str = r#"// The pinned digests of this SDK's harness release (spec slice-ar R12.5),
// written by `scripts/sdk/pin.sh` from the signed release; empty until a
// release is pinned. `scripts/sdk/package.sh` refuses to package empty or
// malformed pins.
// The harness version the pins are for; a package of another version has no pins.
export const PINS_VERSION: string = "";
// target -> SHA-256 of the release archive and of the executable inside it,
// 64 lowercase hex characters each.
export const PINS: Readonly<Record<string, { archiveSha256: string; executableSha256: string }>> = {};
"#;
fn empty_pins() -> String {
EMPTY_PINS.to_string()
}
fn run(cmd: &mut Command) -> (bool, String) {
let out = cmd.output().unwrap();
let text = format!(
"{}{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
(out.status.success(), text)
}
fn key(s: &mut Scratch, name: &str) -> (PathBuf, String) {
let k = s.path(name);
s.path(&format!("{name}.pub"));
let (ok, out) = run(Command::new("ssh-keygen")
.args(["-q", "-t", "ed25519", "-N", "", "-C", "test", "-f"])
.arg(&k));
assert!(ok, "{out}");
let public = std::fs::read_to_string(format!("{}.pub", k.display())).unwrap();
let mut words = public.split_whitespace();
let line = format!(
"harness-release namespaces=\"harness-release\" {} {}\n",
words.next().unwrap(),
words.next().unwrap()
);
(k, line)
}
fn sign(s: &mut Scratch, key: &Path, message: &str) -> Vec<u8> {
let msg = s.file(&format!("msg-{}", s.paths.len()), message.as_bytes(), 0o600);
let out = Command::new("ssh-keygen")
.args([
"-Y",
"sign",
"-n",
"harness-release",
"-O",
"hashalg=sha512",
"-f",
])
.arg(key)
.stdin(std::fs::File::open(msg).unwrap())
.output()
.unwrap();
assert!(
out.status.success(),
"{}",
String::from_utf8_lossy(&out.stderr)
);
out.stdout
}
fn signed_release(
s: &mut Scratch,
good: &Path,
other: &Path,
bad_sig_for: Option<&str>,
) -> (Server, Vec<(String, String, String)>) {
let mut routes = HashMap::new();
let mut pins = Vec::new();
for t in TARGETS {
let exe = format!("#!/bin/sh\necho 'harness {VERSION} {t}'\n").into_bytes();
let a = archive(&[regular("harness", &exe)]);
let sum = hex(&a);
let message = format!("harness-release-v1\n{VERSION}\n{t}\n{sum}\n");
let signer = if bad_sig_for == Some(t) { other } else { good };
let sig = sign(s, signer, &message);
let path = archive_path(t);
routes.insert(
format!("{path}.sha256"),
Reply::Body(format!("{sum} harness-{t}.tar.gz\n").into_bytes()),
);
routes.insert(format!("{path}.sig"), Reply::Body(sig));
routes.insert(path, Reply::Body(a));
pins.push((t.to_string(), sum, hex(&exe)));
}
(Server::start(routes, Hold::None), pins)
}
fn pin_sh(tree: &Path, server: &Server, signers: &Path) -> (bool, String) {
run(Command::new("bash")
.arg(tree.join("scripts/sdk/pin.sh"))
.arg(format!("v{VERSION}"))
.arg(signers)
.env(
"MEGACODE_AGENT_SDK_DOWNLOAD_BASE",
format!("{}/dl", server.base),
))
}
#[test]
fn the_download_base_under_cargo_test_is_loopback() {
let base = std::env::var("MEGACODE_AGENT_SDK_DOWNLOAD_BASE").unwrap_or_default();
let host = base
.strip_prefix("http://")
.and_then(|rest| rest.split(['/', ':']).next())
.unwrap_or("");
assert!(
host.parse::<std::net::Ipv4Addr>()
.is_ok_and(|ip| ip.is_loopback()),
"{base:?}"
);
}
#[test]
fn pin_sh_writes_the_table_from_a_signed_release() {
let mut s = Scratch::new("pin");
let tree = script_tree(&mut s, &empty_pins());
let (good, line) = key(&mut s, "key");
let (other, _) = key(&mut s, "other");
let signers = s.file("allowed_signers", line.as_bytes(), 0o644);
let (server, pins) = signed_release(&mut s, &good, &other, None);
let (ok, out) = pin_sh(&tree, &server, &signers);
assert!(ok, "{out}");
let written = std::fs::read_to_string(tree.join("sdk/rust/src/binary/pins.rs")).unwrap();
assert!(
written.contains(&format!("pub const PINS_VERSION: &str = \"{VERSION}\";")),
"{written}"
);
let rows: Vec<(&str, String, String)> = pins
.iter()
.map(|(t, a, e)| (t.as_str(), a.clone(), e.clone()))
.collect();
assert_eq!(written, table(VERSION, &rows));
let py =
std::fs::read_to_string(tree.join("sdk/python/src/megacode_agent_sdk/_pins.py")).unwrap();
assert_eq!(py, py_table(VERSION, &rows));
let ts = std::fs::read_to_string(tree.join("sdk/typescript/src/pins.ts")).unwrap();
assert_eq!(ts, ts_table(VERSION, &rows));
let (ok, out) = check_pins(&tree, &["--versions"]);
assert!(ok, "{out}");
let (ok, out) = run(Command::new("bash")
.arg(tree.join("scripts/sdk/package.sh"))
.arg("--check"));
assert!(ok, "{out}");
}
fn check_pins(tree: &Path, args: &[&str]) -> (bool, String) {
run(Command::new("python3")
.arg("-I")
.arg(tree.join("scripts/sdk/check-pins.py"))
.args(args))
}
#[test]
fn check_pins_refuses_copies_that_disagree() {
let rows: Vec<(&str, String, String)> = TARGETS
.iter()
.map(|t| (*t, "a".repeat(64), "b".repeat(64)))
.collect();
let mut s = Scratch::new("chkdis");
let tree = script_tree_with(
&mut s,
&table(VERSION, &rows),
&py_table(VERSION, &rows),
&ts_table(VERSION, &rows),
);
let (ok, out) = check_pins(&tree, &["--versions"]);
assert!(ok, "{out}");
let mut other = rows.clone();
other[1].2 = "c".repeat(64);
let mut s = Scratch::new("chkdis2");
let tree = script_tree_with(
&mut s,
&table(VERSION, &rows),
&py_table(VERSION, &rows),
&ts_table(VERSION, &other),
);
let (ok, out) = check_pins(&tree, &["--versions"]);
assert!(!ok, "{out}");
assert!(out.contains("sdk/typescript/src/pins.ts"), "{out}");
assert!(out.contains("aarch64-apple-darwin"), "{out}");
let mut s = Scratch::new("chkdis3");
let tree = script_tree_with(
&mut s,
&table(VERSION, &rows),
&py_table(VERSION, &other),
&ts_table(VERSION, &rows),
);
let (ok, out) = check_pins(&tree, &["--versions"]);
assert!(!ok, "{out}");
assert!(
out.contains("sdk/python/src/megacode_agent_sdk/_pins.py"),
"{out}"
);
}
#[test]
fn check_pins_allows_unpinned_tables() {
let (ok, out) = {
let mut s = Scratch::new("chkun1");
let tree = script_tree(&mut s, &empty_pins());
check_pins(&tree, &["--versions", "--allow-unpinned"])
};
assert!(ok, "empty: {out}");
let rows: Vec<(&str, String, String)> = TARGETS
.iter()
.map(|t| (*t, "a".repeat(64), "b".repeat(64)))
.collect();
let mut s = Scratch::new("chkun2");
let tree = script_tree_with(
&mut s,
&table("0.29.0", &rows),
&py_table("0.29.0", &rows),
&ts_table("0.29.0", &rows),
);
let (ok, out) = check_pins(&tree, &["--versions", "--allow-unpinned"]);
assert!(ok, "0.29.0: {out}");
let (ok, out) = check_pins(&tree, &["--versions"]);
assert!(!ok, "strict refuses pins for another version: {out}");
}
#[test]
fn check_pins_refuses_a_truncated_table() {
let rows: Vec<(&str, String, String)> = TARGETS
.iter()
.map(|t| (*t, "a".repeat(64), "b".repeat(64)))
.collect();
let rust = table(VERSION, &rows);
let cut = &rust[..rust.rfind("];").unwrap()];
let mut s = Scratch::new("chktr");
let tree = script_tree_with(
&mut s,
cut,
&py_table(VERSION, &rows),
&ts_table(VERSION, &rows),
);
let (ok, out) = check_pins(&tree, &[]);
assert!(!ok, "{out}");
assert!(out.contains("no end of the PINS table"), "{out}");
assert!(!out.contains("Traceback"), "{out}");
let ts = ts_table(VERSION, &rows);
let cut = &ts[..ts.rfind("};").unwrap()];
let mut s = Scratch::new("chktr2");
let tree = script_tree_with(&mut s, &rust, &py_table(VERSION, &rows), cut);
let (ok, out) = check_pins(&tree, &[]);
assert!(!ok, "{out}");
assert!(
out.contains("sdk/typescript/src/pins.ts: no end of the PINS table"),
"{out}"
);
assert!(!out.contains("Traceback"), "{out}");
}
#[test]
fn check_pins_refuses_a_version_mismatch() {
let rows: Vec<(&str, String, String)> = TARGETS
.iter()
.map(|t| (*t, "a".repeat(64), "b".repeat(64)))
.collect();
let mut s = Scratch::new("chkver");
let tree = script_tree_with(
&mut s,
&table(VERSION, &rows),
&py_table(VERSION, &rows),
&ts_table(VERSION, &rows),
);
let pkg = tree.join("sdk/typescript/package.json");
let body = std::fs::read_to_string(&pkg).unwrap();
std::fs::write(&pkg, body.replace(VERSION, "0.29.0")).unwrap();
let (ok, out) = check_pins(&tree, &["--versions"]);
assert!(!ok, "{out}");
assert!(out.contains("sdk/typescript/package.json"), "{out}");
assert!(out.contains("0.29.0"), "{out}");
let (ok, out) = check_pins(&tree, &[]);
assert!(ok, "without --versions only the pins are read: {out}");
}
#[test]
fn check_pins_refuses_a_typescript_source_version_mismatch() {
let rows: Vec<(&str, String, String)> = TARGETS
.iter()
.map(|t| (*t, "a".repeat(64), "b".repeat(64)))
.collect();
let mut s = Scratch::new("chkvts");
let tree = script_tree_with(
&mut s,
&table(VERSION, &rows),
&py_table(VERSION, &rows),
&ts_table(VERSION, &rows),
);
let (ok, out) = check_pins(&tree, &["--versions"]);
assert!(ok, "{out}");
let src = tree.join("sdk/typescript/src/version.ts");
let body = std::fs::read_to_string(&src).unwrap();
std::fs::write(&src, body.replace(VERSION, "0.29.0")).unwrap();
let (ok, out) = check_pins(&tree, &["--versions"]);
assert!(!ok, "{out}");
assert!(
out.contains(&format!(
"sdk/typescript/src/version.ts is '0.29.0', Cargo.toml is '{VERSION}'"
)),
"{out}"
);
}
#[test]
fn pin_sh_refuses_a_bad_signature_and_writes_nothing() {
let mut s = Scratch::new("pinbad");
let pins = empty_pins();
let tree = script_tree(&mut s, &pins);
let (good, line) = key(&mut s, "key");
let (other, _) = key(&mut s, "other");
let signers = s.file("allowed_signers", line.as_bytes(), 0o644);
let (server, _) = signed_release(&mut s, &good, &other, Some("aarch64-apple-darwin"));
let (ok, out) = pin_sh(&tree, &server, &signers);
assert!(!ok, "{out}");
assert!(out.contains("aarch64-apple-darwin"), "{out}");
for (rel, empty) in [
("sdk/rust/src/binary/pins.rs", pins.as_str()),
("sdk/python/src/megacode_agent_sdk/_pins.py", EMPTY_PY_PINS),
("sdk/typescript/src/pins.ts", EMPTY_TS_PINS),
] {
assert_eq!(std::fs::read_to_string(tree.join(rel)).unwrap(), empty);
}
assert_eq!(
entries(&tree.join("sdk/rust/src/binary")),
vec!["pins.rs"],
"no temporary file is left"
);
assert_eq!(
entries(&tree.join("sdk/python/src/megacode_agent_sdk")),
vec!["_pins.py", "_version.py"],
"no temporary file is left"
);
assert_eq!(
entries(&tree.join("sdk/typescript/src")),
vec!["pins.ts", "version.ts"],
"no temporary file is left"
);
}
fn table(version: &str, rows: &[(&str, String, String)]) -> String {
let mut entries = String::new();
for (t, a, e) in rows {
entries.push_str(&format!(
" (\n \"{t}\",\n Pin {{\n archive_sha256: \"{a}\",\n executable_sha256: \"{e}\",\n }},\n ),\n"
));
}
empty_pins()
.replace(
"pub const PINS_VERSION: &str = \"\";",
&format!("pub const PINS_VERSION: &str = \"{version}\";"),
)
.replace(
"pub const PINS: &[(&str, Pin)] = &[];",
&format!("pub const PINS: &[(&str, Pin)] = &[\n{entries}];"),
)
}
fn py_table(version: &str, rows: &[(&str, String, String)]) -> String {
let mut entries = String::new();
for (t, a, e) in rows {
entries.push_str(&format!(
" \"{t}\": (\n \"{a}\",\n \"{e}\",\n ),\n"
));
}
EMPTY_PY_PINS
.replace(
"PINS_VERSION: str = \"\"",
&format!("PINS_VERSION: str = \"{version}\""),
)
.replace(
"PINS: dict[str, tuple[str, str]] = {}",
&format!("PINS: dict[str, tuple[str, str]] = {{\n{entries}}}"),
)
}
fn ts_table(version: &str, rows: &[(&str, String, String)]) -> String {
let mut entries = String::new();
for (t, a, e) in rows {
entries.push_str(&format!(
" \"{t}\": {{\n archiveSha256: \"{a}\",\n executableSha256: \"{e}\",\n }},\n"
));
}
EMPTY_TS_PINS
.replace(
"PINS_VERSION: string = \"\"",
&format!("PINS_VERSION: string = \"{version}\""),
)
.replace(
"{ archiveSha256: string; executableSha256: string }>> = {}",
&format!("{{ archiveSha256: string; executableSha256: string }}>> = {{\n{entries}}}"),
)
}
#[test]
fn package_sh_refuses_empty_or_malformed_pins() {
let digest = |c: char| c.to_string().repeat(64);
let full = |a: String| -> Vec<(&str, String, String)> {
TARGETS
.iter()
.map(|t| (*t, a.clone(), digest('b')))
.collect()
};
type Rows = Vec<(&'static str, String, String)>;
let cases: Vec<(&str, &str, Rows, bool)> = vec![
("empty", "", vec![], false),
("good", VERSION, full(digest('a')), true),
("uppercase", VERSION, full(digest('A')), false),
("short", VERSION, full("a".repeat(63)), false),
(
"a target missing",
VERSION,
full(digest('a'))[..2].to_vec(),
false,
),
("another version", "0.29.0", full(digest('a')), false),
];
for (case, version, rows, accepted) in cases {
let pins = if rows.is_empty() {
empty_pins()
} else {
table(version, &rows)
};
let (py, ts) = if rows.is_empty() {
(EMPTY_PY_PINS.to_string(), EMPTY_TS_PINS.to_string())
} else {
(py_table(version, &rows), ts_table(version, &rows))
};
let mut s = Scratch::new("pkg");
let tree = script_tree_with(&mut s, &pins, &py, &ts);
let (ok, out) = run(Command::new("bash")
.arg(tree.join("scripts/sdk/package.sh"))
.arg("--check"));
assert_eq!(ok, accepted, "{case}: {out}\n{pins}");
}
}