use std::{
env,
ffi::{OsStr, OsString},
fs,
path::{Path, PathBuf},
process::{Command, Output},
time::{SystemTime, UNIX_EPOCH},
};
use anyhow::{bail, Context, Result};
use serde_json::{json, Value};
use crate::model::{QuipuFlavor, ToolName};
mod manifest;
mod quipu_alias;
pub(crate) use manifest::{
find_program, identify_member, prove_release, release_target, single_program, stage, verify_at,
};
pub trait Adapter {
fn name(&self) -> ToolName;
fn desired_version(&self) -> String;
fn install(&self) -> Result<()>;
fn version(&self) -> Result<String>;
fn verify(&self) -> Result<()>;
fn package_adapter(&self) -> Result<Option<Box<dyn Adapter>>> {
Ok(None)
}
fn is_current(&self, installed: &str) -> bool {
installed == self.desired_version()
}
}
pub fn release_blocker(name: ToolName, quipu_flavor: QuipuFlavor) -> Option<String> {
let result = match name {
ToolName::Quipu if quipu_flavor == QuipuFlavor::Release => quipu_release_target(),
ToolName::Bobbin => bobbin_release_target(),
ToolName::Yupana => yupana_release_target(),
ToolName::Member(name) => match crate::members::get(name) {
Some(m) => manifest::release_target(m),
None => return Some(format!("unknown stack member {name}")),
},
ToolName::Quipu | ToolName::Camayoc | ToolName::DesirePath => return None,
};
result.err().map(|error| format!("{error:#}"))
}
pub fn prerequisites(name: ToolName, quipu_flavor: QuipuFlavor) -> &'static [&'static str] {
match name {
ToolName::Quipu if quipu_flavor == QuipuFlavor::Lancedb => &["cargo"],
ToolName::Quipu => &["curl", "tar"],
ToolName::Camayoc => &["curl", "tar", "sha256sum", "bash", "python3"],
ToolName::Bobbin => &["curl", "tar", "git"],
ToolName::Yupana => &["curl", "tar"],
ToolName::DesirePath => &["go"],
ToolName::Member(name) => crate::members::prerequisites(name),
}
}
pub fn source_install_command(name: ToolName) -> Option<String> {
match name {
ToolName::Quipu => Some(format!("cargo {}", quipu_install_args(QuipuFlavor::Release).join(" "))),
ToolName::Yupana => Some(format!(
"cargo install --git https://github.com/scbrown/yupana --tag v{YUPANA_VERSION} --locked --features mcp"
)),
ToolName::Camayoc | ToolName::Bobbin | ToolName::DesirePath | ToolName::Member(_) => None,
}
}
pub fn programs(name: ToolName) -> &'static [&'static str] {
match name {
ToolName::Quipu => &["qp", "quipu", "quipu-server"],
ToolName::Camayoc => &[],
ToolName::Bobbin => &["bobbin"],
ToolName::Yupana => &["yupana"],
ToolName::DesirePath => &["dp"],
ToolName::Member(name) => crate::members::programs(name),
}
}
pub fn which_all(program: &str, path: Option<&std::ffi::OsStr>) -> Vec<PathBuf> {
let Some(path) = path else {
return Vec::new();
};
let mut found: Vec<PathBuf> = Vec::new();
let mut targets: Vec<PathBuf> = Vec::new();
for candidate in env::split_paths(path).map(|directory| directory.join(program)) {
if !is_executable(&candidate) {
continue;
}
let target = candidate
.canonicalize()
.unwrap_or_else(|_| candidate.clone());
if !targets.contains(&target) {
targets.push(target);
found.push(candidate);
}
}
found
}
pub(crate) fn is_executable(path: &Path) -> bool {
let Ok(metadata) = path.metadata() else {
return false;
};
if !metadata.is_file() {
return false;
}
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
metadata.permissions().mode() & 0o111 != 0
}
#[cfg(not(unix))]
true
}
#[derive(Debug, PartialEq, Eq)]
pub enum PathResolution {
Managed,
NotOnPath,
SameBuild { runs: PathBuf, managed: PathBuf },
Shadowed {
runs: PathBuf,
runs_version: String,
managed: PathBuf,
managed_version: String,
},
}
pub fn path_resolution(program: &str, path: Option<&OsStr>) -> PathResolution {
let Some(managed) = managed_bin_dir().map(|dir| dir.join(program)) else {
return PathResolution::Managed;
};
if !is_executable(&managed) {
return PathResolution::Managed;
}
let Some(runs) = which_all(program, path).into_iter().next() else {
return PathResolution::NotOnPath;
};
let canonical = |p: &Path| p.canonicalize().unwrap_or_else(|_| p.to_path_buf());
if canonical(&runs) == canonical(&managed) {
return PathResolution::Managed;
}
let version = |p: &Path| {
p.to_str()
.and_then(|s| read_version(s).ok())
.unwrap_or_else(|| "unreadable".to_owned())
};
let (runs_version, managed_version) = (version(&runs), version(&managed));
if runs_version == managed_version {
PathResolution::SameBuild { runs, managed }
} else {
PathResolution::Shadowed {
runs,
runs_version,
managed,
managed_version,
}
}
}
pub fn managed_bin_dir() -> Option<PathBuf> {
env::var_os("CARGO_HOME")
.map(PathBuf::from)
.or_else(|| env::var_os("HOME").map(|home| PathBuf::from(home).join(".cargo")))
.map(|home| home.join("bin"))
}
pub fn adapter(name: ToolName, quipu_flavor: QuipuFlavor) -> Box<dyn Adapter> {
match name {
ToolName::Quipu => Box::new(Quipu {
flavor: quipu_flavor,
external: None,
}),
ToolName::Camayoc => Box::new(Camayoc),
ToolName::Bobbin => Box::new(Bobbin),
ToolName::Yupana => Box::new(Yupana),
ToolName::DesirePath => Box::new(DesirePath),
ToolName::Member(name) => Box::new(manifest::ManifestAdapter(
crate::members::get(name)
.expect("a Member name is only minted for an embedded manifest"),
)),
}
}
pub fn adapter_for_plan(name: ToolName, plan: &crate::model::Plan) -> Result<Box<dyn Adapter>> {
if name != ToolName::Quipu || !plan.external_quipu {
return Ok(adapter(name, plan.quipu_flavor));
}
let path = env::var_os("PATH");
let resolve = |name| -> Result<PathBuf> {
which_all(name, path.as_deref())
.into_iter()
.next()
.with_context(|| format!("external Quipu requires {name} on PATH"))?
.canonicalize()
.with_context(|| format!("resolve external {name}"))
};
Ok(Box::new(Quipu {
flavor: plan.quipu_flavor,
external: Some((resolve("quipu")?, resolve("quipu-server")?)),
}))
}
fn output<P, I, S>(program: P, args: I, cwd: Option<&Path>) -> Result<Output>
where
P: AsRef<OsStr>,
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let args: Vec<OsString> = args
.into_iter()
.map(|arg| arg.as_ref().to_os_string())
.collect();
let program = program.as_ref();
let mut command = Command::new(program);
command.args(&args);
if let Some(cwd) = cwd {
command.current_dir(cwd);
}
command.output().with_context(|| {
format!(
"run {} {}",
program.to_string_lossy(),
args.iter()
.map(|arg| arg.to_string_lossy())
.collect::<Vec<_>>()
.join(" ")
)
})
}
pub(crate) fn checked<P, I, S>(program: P, args: I, cwd: Option<&Path>) -> Result<Output>
where
P: AsRef<OsStr>,
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let program = program.as_ref();
let result = output(program, args, cwd)?;
if !result.status.success() {
bail!(
"{} failed ({})\nstdout:\n{}\nstderr:\n{}",
program.to_string_lossy(),
result.status,
String::from_utf8_lossy(&result.stdout).trim(),
String::from_utf8_lossy(&result.stderr).trim()
);
}
Ok(result)
}
fn read_version(program: &str) -> Result<String> {
let result = checked(program, ["--version"], None)?;
let version = String::from_utf8_lossy(&result.stdout).trim().to_owned();
if version.is_empty() {
bail!("{} --version returned no version", program);
}
Ok(version)
}
fn read_cargo_version(program: &str) -> Result<String> {
let program_path = cargo_program(program);
read_version(
program_path
.to_str()
.with_context(|| format!("{} path is not valid UTF-8", program_path.display()))?,
)
}
fn cargo_program(program: &str) -> PathBuf {
let cargo_home = env::var_os("CARGO_HOME")
.map(PathBuf::from)
.or_else(|| env::var_os("HOME").map(|home| PathBuf::from(home).join(".cargo")));
if let Some(path) = cargo_home.map(|home| home.join("bin").join(program)) {
if path.is_file() {
return path;
}
}
PathBuf::from(program)
}
fn require_minimum_version(output: &str, minimum: (u64, u64, u64), program: &str) -> Result<()> {
let raw = output
.split_whitespace()
.nth(1)
.with_context(|| format!("{program} --version omitted a semantic version"))?;
let mut parts = raw.trim_start_matches('v').split('.');
let found = (
parts.next().and_then(|value| value.parse().ok()),
parts.next().and_then(|value| value.parse().ok()),
parts.next().and_then(|value| value.parse().ok()),
);
let found = match found {
(Some(major), Some(minor), Some(patch)) => (major, minor, patch),
_ => bail!("{program} returned an invalid semantic version: {raw}"),
};
if found < minimum {
bail!(
"{program} {}.{}.{} is too old; Tambo requires at least {}.{}.{}",
found.0,
found.1,
found.2,
minimum.0,
minimum.1,
minimum.2
);
}
Ok(())
}
struct Quipu {
flavor: QuipuFlavor,
external: Option<(PathBuf, PathBuf)>,
}
const QUIPU_VERSION: &str = "0.9.0";
fn quipu_features(flavor: QuipuFlavor) -> &'static str {
match flavor {
QuipuFlavor::Release => "full",
QuipuFlavor::Lancedb => "full,lancedb",
}
}
impl Adapter for Quipu {
fn package_adapter(&self) -> Result<Option<Box<dyn Adapter>>> {
let path = env::var_os("PATH");
let resolve = |name| {
which_all(name, path.as_deref())
.into_iter()
.next()
.with_context(|| format!("package Quipu requires {name} on PATH"))
};
Ok(Some(Box::new(Self {
flavor: self.flavor,
external: Some((resolve("quipu")?, resolve("quipu-server")?)),
})))
}
fn name(&self) -> ToolName {
ToolName::Quipu
}
fn desired_version(&self) -> String {
match self.flavor {
QuipuFlavor::Release => format!("quipu {QUIPU_VERSION}; quipu-server {QUIPU_VERSION}"),
QuipuFlavor::Lancedb => {
format!("quipu {QUIPU_VERSION}; quipu-server {QUIPU_VERSION} (+lancedb)")
}
}
}
fn is_current(&self, _installed: &str) -> bool {
true
}
fn install(&self) -> Result<()> {
if self.external.is_some() {
bail!("external Quipu is not owned by Tambo; installation refused");
}
preflight_owned_quipu_alias()?;
match self.flavor {
QuipuFlavor::Release => install_quipu_release(),
QuipuFlavor::Lancedb => {
let mut args: Vec<OsString> = quipu_install_args(self.flavor)
.iter()
.map(OsString::from)
.collect();
args.push("--root".into());
args.push(
owned_quipu_bin()?
.parent()
.context("cargo bin has no root")?
.as_os_str()
.into(),
);
checked("cargo", args, None)?;
ensure_owned_quipu_alias()
}
}
}
fn version(&self) -> Result<String> {
let client = if let Some((client, _)) = &self.external {
read_version(
client
.to_str()
.context("external Quipu path is not UTF-8")?,
)?
} else {
read_cargo_version("quipu")?
};
require_minimum_version(&client, (0, 3, 27), "quipu")?;
let server = if let Some((_, server)) = &self.external {
read_version(
server
.to_str()
.context("external server path is not UTF-8")?,
)?
} else {
read_cargo_version("quipu-server")?
};
require_minimum_version(&server, (0, 3, 27), "quipu-server")?;
Ok(match &self.external {
Some((c, s)) => format!(
"{client}; {server}; external executables: {} | {}",
c.display(),
s.display()
),
None => format!("{client}; {server}"),
})
}
fn verify(&self) -> Result<()> {
let client = self
.external
.as_ref()
.map(|(c, _)| c.clone())
.map(Ok)
.unwrap_or_else(selected_quipu_client)?;
verify_quipu_alias(&client)?;
self.verify_core()
}
}
impl Quipu {
fn verify_core(&self) -> Result<()> {
if self.flavor == QuipuFlavor::Lancedb {
verify_quipu_lancedb_feature()?;
}
let client = self
.external
.as_ref()
.map(|(client, _)| client.as_path())
.unwrap_or_else(|| Path::new("quipu"));
let root = tempfile::tempdir().context("create quipu verification directory")?;
let db = root.path().join("verify.db");
let episode = root.path().join("episode.json");
let marker = "caboodle-verify-roundtrip";
fs::write(
&episode,
format!(
r#"{{"name":"caboodle verification","episode_body":"functional round trip","source":"caboodle","group_id":"caboodle-verification","nodes":[{{"name":"{marker}","type":"Verification","description":"caboodle isolated verification"}}],"edges":[]}}"#
),
)
.context("write quipu verification episode")?;
let query = format!(
"SELECT ?s ?label WHERE {{ ?s <http://www.w3.org/2000/01/rdf-schema#label> ?label . FILTER(?label = \"{marker}\") }}"
);
let before = checked(
client,
[
OsStr::new("read"),
OsStr::new(&query),
OsStr::new("--db"),
db.as_os_str(),
],
None,
)?;
if String::from_utf8_lossy(&before.stdout).contains(marker) {
bail!("quipu negative control unexpectedly found the verification node");
}
checked(
client,
[
OsStr::new("episode"),
episode.as_os_str(),
OsStr::new("--db"),
db.as_os_str(),
],
None,
)?;
let after = checked(
client,
[
OsStr::new("read"),
OsStr::new(&query),
OsStr::new("--db"),
db.as_os_str(),
],
None,
)?;
if !String::from_utf8_lossy(&after.stdout).contains(marker) {
bail!("quipu episode landed but read-back did not find the verification node");
}
crate::vocabulary::verify_at(client).context("Quechua vocabulary verification")?;
Ok(())
}
}
pub(crate) fn verify_quipu_pair(client: &Path, server: &Path) -> Result<()> {
Quipu {
flavor: QuipuFlavor::Release,
external: Some((client.to_path_buf(), server.to_path_buf())),
}
.verify_core()?;
let help = checked(server, ["--help"], None)?;
let help = String::from_utf8_lossy(&help.stdout);
if !help.contains("--db") || !help.contains("--bind") {
bail!("Quipu server omitted isolated database/bind command contract");
}
Ok(())
}
pub(crate) fn quipu_release_target() -> Result<&'static str> {
quipu_release_target_for(env::consts::ARCH, env::consts::OS)
}
fn quipu_release_target_for(arch: &str, os: &str) -> Result<&'static str> {
match (arch, os) {
("x86_64", "linux") => Ok("x86_64-unknown-linux-gnu"),
("aarch64", "linux") => Ok("aarch64-unknown-linux-gnu"),
("x86_64", "macos") => Ok("x86_64-apple-darwin"),
("aarch64", "macos") => Ok("aarch64-apple-darwin"),
(arch, os) => bail!("Quipu has no checksummed Tambo release for {arch}-{os}"),
}
}
fn fetch_quipu_release(target: &str, download: &Path) -> Result<PathBuf> {
let tag = format!("quipu-ai-v{QUIPU_VERSION}");
let directory = format!("quipu-{tag}-{target}");
let archive_name = format!("{directory}.tar.gz");
let base = format!("https://github.com/scbrown/quipu/releases/download/{tag}");
let archive = download.join(&archive_name);
let checksum = download.join(format!("{archive_name}.sha256"));
download_https(&format!("{base}/{archive_name}"), &archive)?;
download_https(&format!("{base}/{archive_name}.sha256"), &checksum)?;
verify_checksum(&archive, &checksum)?;
let unpacked = download.join(directory);
checked(
"tar",
[
OsStr::new("-xzf"),
archive.as_os_str(),
OsStr::new("-C"),
download.as_os_str(),
],
None,
)?;
for program in ["quipu", "quipu-server"] {
if !unpacked.join(program).is_file() {
bail!("Quipu release archive omitted {program}");
}
}
Ok(unpacked)
}
fn install_quipu_release() -> Result<()> {
let target = quipu_release_target()?;
let download = tempfile::tempdir().context("create Quipu download directory")?;
let unpacked = fetch_quipu_release(target, download.path())?;
let home = env::var_os("HOME").context("HOME is required to install Quipu")?;
let bin = env::var_os("CARGO_HOME")
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from(home).join(".cargo"))
.join("bin");
fs::create_dir_all(&bin).with_context(|| format!("create {}", bin.display()))?;
for program in ["quipu", "quipu-server"] {
let source = unpacked.join(program);
if !source.is_file() {
bail!("Quipu release archive omitted {}", source.display());
}
fs::copy(&source, bin.join(program))
.with_context(|| format!("install checksummed Quipu binary {program}"))?;
}
ensure_owned_quipu_alias()
}
fn owned_quipu_bin() -> Result<PathBuf> {
env::var_os("CARGO_HOME")
.map(PathBuf::from)
.or_else(|| env::var_os("HOME").map(|h| PathBuf::from(h).join(".cargo")))
.map(|root| root.join("bin"))
.context("HOME or CARGO_HOME is required for owned Quipu installation")
}
pub(crate) fn preflight_owned_quipu_alias() -> Result<()> {
quipu_alias::preflight(&owned_quipu_bin()?).map_err(anyhow::Error::msg)
}
pub(crate) fn ensure_owned_quipu_alias() -> Result<()> {
quipu_alias::ensure(&owned_quipu_bin()?).map_err(anyhow::Error::msg)
}
#[cfg(unix)]
pub fn repair_owned_quipu_alias() -> Result<String> {
use quipu_alias::Repair;
let bin = owned_quipu_bin()?;
let qp = bin.join("qp");
Ok(
match quipu_alias::repair(&bin).map_err(anyhow::Error::msg)? {
Repair::NoQuipu => format!("no owned quipu in {}; nothing to repair", bin.display()),
Repair::Current => format!("{} -> quipu already current", qp.display()),
Repair::Created => format!("created {} -> quipu", qp.display()),
Repair::Retargeted => format!("retargeted {} -> quipu", qp.display()),
Repair::ForeignPreserved => format!(
"{} is not a symlink; preserved it, qp NOT repaired",
qp.display()
),
Repair::PackageManaged(package) => format!(
"quipu in {} is managed by apt (package {package}); qp left to the package",
bin.display()
),
},
)
}
fn selected_quipu_client() -> Result<PathBuf> {
let owned = cargo_program("quipu");
if owned.is_file() {
return Ok(owned);
}
which_all("quipu", env::var_os("PATH").as_deref())
.into_iter()
.next()
.context("Quipu CLI is not on PATH")
}
pub(crate) fn ensure_existing_quipu_alias() -> Result<()> {
let client = selected_quipu_client()?;
let bin = client
.parent()
.context("selected Quipu CLI has no directory")?;
quipu_alias::ensure(bin).map_err(anyhow::Error::msg)
}
pub(crate) fn verify_existing_quipu_alias() -> Result<()> {
verify_quipu_alias(&selected_quipu_client()?)
}
fn verify_quipu_alias(client: &Path) -> Result<()> {
let bin = client.parent().context("Quipu CLI has no directory")?;
let alias = bin.join("qp");
let resolved = client.canonicalize()?;
let resolved_bin = resolved.parent().context("Quipu CLI has no directory")?;
let relative =
|dir: &Path| fs::read_link(dir.join("qp")).ok().as_deref() == Some(Path::new("quipu"));
if !relative(bin) && !relative(resolved_bin) {
bail!(
"Quipu verification requires relative qp -> quipu in {}",
bin.display()
);
}
if alias.canonicalize().ok().as_deref() != Some(resolved.as_path()) {
bail!("qp and quipu do not resolve to the same executable");
}
if read_version(alias.to_str().context("qp path is not UTF-8")?)?
!= read_version(client.to_str().context("Quipu path is not UTF-8")?)?
{
bail!("qp and quipu version outputs disagree");
}
Ok(())
}
fn quipu_install_args(flavor: QuipuFlavor) -> [&'static str; 8] {
[
"install",
"quipu-ai",
"--version",
QUIPU_VERSION,
"--locked",
"--features",
quipu_features(flavor),
"--bins",
]
}
fn verify_quipu_lancedb_feature() -> Result<()> {
let server = env::var("QUIPU_SERVER").unwrap_or_else(|_| "http://localhost:3030".to_owned());
let version = curl_get_json(&format!("{server}/version"))
.context("read the Quipu server per-feature compile map")?;
require_compiled_feature(&version, "lancedb")
}
fn require_compiled_feature(version: &Value, feature: &str) -> Result<()> {
let compiled = version
.get("features")
.and_then(Value::as_object)
.with_context(|| {
format!(
"Quipu /version returned no per-feature compile map; cannot prove the {feature} flavor"
)
})?;
if compiled.get(feature).and_then(Value::as_bool) != Some(true) {
bail!(
"the plan requires the {feature} flavor but the running Quipu was compiled without it; \
reinstall the reviewed revision with `cargo install quipu-ai --version {QUIPU_VERSION} \
--locked --features full,{feature} --bins`"
);
}
Ok(())
}
struct Bobbin;
struct Yupana;
struct DesirePath;
struct Camayoc;
const BOBBIN_VERSION: &str = "0.30.0";
impl Adapter for Camayoc {
fn name(&self) -> ToolName {
ToolName::Camayoc
}
fn desired_version(&self) -> String {
format!("camayoc {CAMAYOC_REVISION}")
}
fn install(&self) -> Result<()> {
install_camayoc_bundle()
}
fn version(&self) -> Result<String> {
let revision = fs::read_to_string(camayoc_root()?.join("REVISION"))
.context("read installed Camayoc revision")?;
let revision = revision.trim();
if revision.is_empty() {
bail!("installed Camayoc revision is empty");
}
Ok(format!("camayoc {revision}"))
}
fn verify(&self) -> Result<()> {
let root = camayoc_root()?;
let scratch = tempfile::tempdir().context("create Camayoc verification directory")?;
let port = free_local_port()?;
let server = format!("http://127.0.0.1:{port}");
let _guard = ScratchServer(scratch.path().join(".quipu/server.pid"));
let bootstrap = Command::new("bash")
.arg(root.join("scripts/bootstrap.sh"))
.current_dir(scratch.path())
.env("QUIPU_SERVER", &server)
.env("CLAUDE_PROJECT_DIR", scratch.path())
.env_remove("QUIPU_AUTH_TOKEN")
.output()
.context("run Camayoc bootstrap against a scratch Quipu")?;
if !bootstrap.status.success() {
bail!(
"Camayoc bootstrap failed against scratch Quipu {server} ({})\nstdout:\n{}\nstderr:\n{}",
bootstrap.status,
String::from_utf8_lossy(&bootstrap.stdout).trim(),
String::from_utf8_lossy(&bootstrap.stderr).trim()
);
}
if !scratch.path().join(".quipu/server.pid").exists() {
bail!("Camayoc bootstrap did not start a scratch Quipu at {server}; refusing to verify against a server caboodle does not own");
}
verify_camayoc_first_ingest(&root, &server)
}
}
struct ScratchServer(PathBuf);
impl Drop for ScratchServer {
fn drop(&mut self) {
if let Ok(pid) = fs::read_to_string(&self.0) {
let pid = pid.trim();
if !pid.is_empty() && pid.chars().all(|c| c.is_ascii_digit()) {
let _ = Command::new("kill").arg(pid).status();
}
}
}
}
fn free_local_port() -> Result<u16> {
let listener =
std::net::TcpListener::bind("127.0.0.1:0").context("reserve a free localhost port")?;
Ok(listener.local_addr()?.port())
}
impl Adapter for Bobbin {
fn name(&self) -> ToolName {
ToolName::Bobbin
}
fn desired_version(&self) -> String {
format!("bobbin {BOBBIN_VERSION}")
}
fn is_current(&self, installed: &str) -> bool {
installed.strip_prefix("bobbin ").is_some_and(|rest| {
rest == BOBBIN_VERSION || rest.starts_with(&format!("{BOBBIN_VERSION} "))
})
}
fn install(&self) -> Result<()> {
install_bobbin_release()
}
fn version(&self) -> Result<String> {
read_version("bobbin")
}
fn verify(&self) -> Result<()> {
let root = tempfile::tempdir().context("create bobbin verification repository")?;
let nonce = SystemTime::now()
.duration_since(UNIX_EPOCH)
.context("system clock predates Unix epoch")?
.as_nanos();
let repo = format!("caboodle-verify-{}-{nonce}", std::process::id());
let marker = format!("caboodle_verify_marker_{}_{nonce}", std::process::id());
checked("git", ["init", "--quiet"], Some(root.path()))?;
checked("bobbin", ["init"], Some(root.path()))?;
fs::write(
root.path().join("control.rs"),
"/// Proves the negative-control index is live.\npub fn caboodle_control() -> usize {\n 1\n}\n",
)
.context("write bobbin negative-control fixture")?;
checked(
"bobbin",
[
"index",
".",
"--source",
".",
"--repo",
&repo,
"--skip-calibrate",
],
Some(root.path()),
)?;
let before = checked(
"bobbin",
["grep", &marker, "--repo", &repo, "--json"],
Some(root.path()),
)?;
if bobbin_result_count(&before.stdout)? != 0 {
bail!("bobbin negative control unexpectedly found the fixture marker");
}
fs::write(
root.path().join("fixture.rs"),
format!(
"/// A unique Tambo verification routine.\npub fn {marker}() -> &'static str {{\n \"indexed\"\n}}\n"
),
)
.context("write bobbin verification fixture")?;
checked(
"bobbin",
[
"index",
".",
"--source",
".",
"--repo",
&repo,
"--skip-calibrate",
],
Some(root.path()),
)?;
let after = checked(
"bobbin",
["grep", &marker, "--repo", &repo, "--json"],
Some(root.path()),
)?;
if bobbin_result_count(&after.stdout)? == 0 {
bail!("bobbin indexed the fixture but search did not return its marker");
}
Ok(())
}
}
const YUPANA_VERSION: &str = "0.16.2";
impl Adapter for Yupana {
fn name(&self) -> ToolName {
ToolName::Yupana
}
fn desired_version(&self) -> String {
format!("yupana {YUPANA_VERSION}")
}
fn install(&self) -> Result<()> {
let (target, checksum) = yupana_release_for(env::consts::ARCH, env::consts::OS)?;
let root = tempfile::tempdir().context("create Yupana download directory")?;
fetch_yupana_release(target, checksum, root.path())?;
let home = env::var_os("HOME").context("HOME is required to install Yupana")?;
let bin = env::var_os("CARGO_HOME")
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from(home).join(".cargo"))
.join("bin");
fs::create_dir_all(&bin).with_context(|| format!("create {}", bin.display()))?;
fs::copy(root.path().join("yupana"), bin.join("yupana"))
.context("install checksummed Yupana binary")?;
Ok(())
}
fn version(&self) -> Result<String> {
let version = read_version("yupana")?;
require_minimum_version(&version, (0, 6, 4), "yupana")?;
Ok(version)
}
fn verify(&self) -> Result<()> {
let root = tempfile::tempdir().context("create Yupana verification repository")?;
let state = root.path().join("state");
fs::write(
root.path().join("control.rs"),
"pub fn caboodle_yupana_control() -> usize { 1 }\n",
)?;
yupana_checked(root.path(), &state, ["analyze", "."])?;
let before = yupana_checked(
root.path(),
&state,
["callers", "caboodle_yupana_target", "."],
)?;
if String::from_utf8_lossy(&before.stdout).contains("caboodle_yupana_caller") {
bail!("Yupana negative control unexpectedly found the fixture caller");
}
fs::write(
root.path().join("fixture.rs"),
"pub fn caboodle_yupana_target() -> usize { 1 }\npub fn caboodle_yupana_caller() -> usize { caboodle_yupana_target() }\n",
)?;
yupana_checked(root.path(), &state, ["analyze", "."])?;
let after = yupana_checked(
root.path(),
&state,
["callers", "caboodle_yupana_target", "."],
)?;
if !String::from_utf8_lossy(&after.stdout).contains("caboodle_yupana_caller") {
bail!("Yupana analyzed the fixture but callers did not return its caller");
}
Ok(())
}
}
fn yupana_release_target() -> Result<&'static str> {
yupana_release_for(env::consts::ARCH, env::consts::OS).map(|(target, _)| target)
}
fn yupana_release_for(arch: &str, os: &str) -> Result<(&'static str, &'static str)> {
match (arch, os) {
("x86_64", "linux") => Ok((
"x86_64-linux-gnu",
"565507459d3d67b2cc73b6ebd91f784f0522689c5b62257a8fde82793580dc5d",
)),
("aarch64", "macos") => Ok((
"aarch64-apple-darwin",
"9f3682c6f4dffd7365248ae2d686e9fcdca26ba08ebd915f8ce692e8111179a2",
)),
("x86_64", "macos") => Ok((
"x86_64-apple-darwin",
"7763d171c7f6e2df395bdf9961a79991b66b4876c3394ce970d1f3e2cddcb4ed",
)),
_ => bail!("Yupana v{YUPANA_VERSION} has no checksummed Tambo release for {arch}-{os}"),
}
}
fn fetch_yupana_release(target: &str, checksum: &str, root: &Path) -> Result<()> {
let archive_name = format!("yupana-v{YUPANA_VERSION}-{target}.tar.gz");
let archive = root.join(&archive_name);
download_https(
&format!(
"https://github.com/scbrown/yupana/releases/download/v{YUPANA_VERSION}/{archive_name}"
),
&archive,
)?;
verify_yupana_checksum(&archive, checksum)?;
checked(
"tar",
[
OsStr::new("-xzf"),
archive.as_os_str(),
OsStr::new("-C"),
root.as_os_str(),
],
None,
)?;
Ok(())
}
fn verify_yupana_checksum(archive: &Path, expected: &str) -> Result<()> {
if manifest::sha256_file(archive)? != expected {
bail!("Yupana release checksum mismatch");
}
Ok(())
}
fn yupana_checked<I, S>(cwd: &Path, state: &Path, args: I) -> Result<Output>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let home = cwd.join("home");
fs::create_dir_all(&home)?;
let mut command = Command::new("yupana");
command
.args(args)
.current_dir(cwd)
.env("HOME", home)
.env("XDG_STATE_HOME", state);
let result = command
.output()
.context("run isolated Yupana verification")?;
if !result.status.success() {
bail!(
"isolated Yupana verification failed ({}): {}",
result.status,
String::from_utf8_lossy(&result.stderr).trim()
);
}
Ok(result)
}
fn desire_path_program() -> Result<PathBuf> {
if let Some(path) = env::var_os("PATH") {
for directory in env::split_paths(&path) {
let candidate = directory.join("dp");
if candidate.is_file() {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
if candidate.metadata()?.permissions().mode() & 0o111 == 0 {
continue;
}
}
return fs::canonicalize(candidate).context("resolve Desire Path executable");
}
}
}
let home = env::var_os("HOME").context("HOME is required to install Desire Path")?;
Ok(PathBuf::from(home).join(".local/bin/dp"))
}
const DESIRE_PATH_REVISION: &str = "06812336fda6a6fd3fe4cbcae2d50a303e95a024";
const DESIRE_PATH_VERSION: &str = "v0.10.0";
impl Adapter for DesirePath {
fn name(&self) -> ToolName {
ToolName::DesirePath
}
fn desired_version(&self) -> String {
format!("dp {DESIRE_PATH_VERSION} ({})", &DESIRE_PATH_REVISION[..7])
}
fn is_current(&self, installed: &str) -> bool {
let expected = self.desired_version();
installed.trim_start_matches("dp ").trim_start_matches('v')
== expected.trim_start_matches("dp ").trim_start_matches('v')
}
fn install(&self) -> Result<()> {
let executable = desire_path_program()?;
let bin = executable
.parent()
.context("Desire Path needs an install directory")?;
fs::create_dir_all(bin).with_context(|| format!("create {}", bin.display()))?;
let ldflags = format!(
"-X github.com/scbrown/desire-path/internal/cli.Version={DESIRE_PATH_VERSION} -X github.com/scbrown/desire-path/internal/cli.Commit={DESIRE_PATH_REVISION}"
);
let mut command = Command::new("go");
command
.args([
"install",
"-ldflags",
&ldflags,
&format!("github.com/scbrown/desire-path/cmd/dp@{DESIRE_PATH_REVISION}"),
])
.env("GOBIN", bin);
let result = command
.output()
.context("build pinned Desire Path revision")?;
if !result.status.success() {
bail!(
"Desire Path install failed ({}): {}",
result.status,
String::from_utf8_lossy(&result.stderr).trim()
);
}
Ok(())
}
fn version(&self) -> Result<String> {
let result = checked(desire_path_program()?, ["version"], None)?;
let version = String::from_utf8_lossy(&result.stdout).trim().to_owned();
if version.is_empty() {
bail!("dp version returned no version");
}
Ok(version)
}
fn verify(&self) -> Result<()> {
let dp = desire_path_program()?;
let root = tempfile::tempdir().context("create Desire Path verification directory")?;
let db = root.path().join("desires.db");
let marker = "caboodle_desire_path_marker";
let before = checked(
&dp,
[
"--db",
db.to_str().context("Desire Path DB path is not UTF-8")?,
"--json",
"list",
],
Some(root.path()),
)?;
if String::from_utf8_lossy(&before.stdout).contains(marker) {
bail!("Desire Path negative control unexpectedly found the marker");
}
let mut child = Command::new(&dp)
.args([
"--db",
db.to_str().unwrap(),
"ingest",
"--source",
"claude-code",
])
.current_dir(root.path())
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.context("start Desire Path ingest verification")?;
use std::io::Write as _;
child
.stdin
.take()
.context("open Desire Path verification stdin")?
.write_all(
format!(r#"{{"tool_name":"{marker}","error":"fixture failure"}}"#).as_bytes(),
)?;
let ingested = child.wait_with_output()?;
if !ingested.status.success() {
bail!(
"Desire Path ingest verification failed ({}): {}",
ingested.status,
String::from_utf8_lossy(&ingested.stderr).trim()
);
}
let after = checked(
&dp,
["--db", db.to_str().unwrap(), "--json", "list"],
Some(root.path()),
)?;
if !String::from_utf8_lossy(&after.stdout).contains(marker) {
bail!("Desire Path ingested the fixture but its reader path did not return it");
}
Ok(())
}
}
fn bobbin_result_count(stdout: &[u8]) -> Result<u64> {
let response: serde_json::Value =
serde_json::from_slice(stdout).context("parse bobbin search JSON")?;
response["count"]
.as_u64()
.context("bobbin search JSON omitted numeric count")
}
fn bobbin_release_target() -> Result<&'static str> {
match (env::consts::ARCH, env::consts::OS) {
("x86_64", "linux") => Ok("x86_64-unknown-linux-gnu"),
("aarch64", "linux") => Ok("aarch64-unknown-linux-gnu"),
("x86_64", "macos") => Ok("x86_64-apple-darwin"),
("aarch64", "macos") => Ok("aarch64-apple-darwin"),
(arch, os) => bail!("bobbin has no Tambo release target for {arch}-{os}"),
}
}
fn install_bobbin_release() -> Result<()> {
let target = bobbin_release_target()?;
let archive = format!("bobbin-v{BOBBIN_VERSION}-{target}.tar.gz");
let base = format!("https://github.com/scbrown/bobbin/releases/download/v{BOBBIN_VERSION}");
let download = tempfile::tempdir().context("create bobbin download directory")?;
let archive_path = download.path().join(&archive);
let sums_path = download.path().join("SHA256SUMS.txt");
download_https(&format!("{base}/{archive}"), &archive_path)?;
download_https(&format!("{base}/SHA256SUMS.txt"), &sums_path)?;
verify_checksum(&archive_path, &sums_path)?;
let home = env::var_os("HOME").context("HOME is required to install bobbin")?;
let install_root = PathBuf::from(&home)
.join(".local/share/caboodle/bobbin")
.join(format!("v{BOBBIN_VERSION}"));
fs::create_dir_all(&install_root)
.with_context(|| format!("create bobbin install root {}", install_root.display()))?;
checked(
"tar",
[
OsStr::new("-xzf"),
archive_path.as_os_str(),
OsStr::new("--strip-components=1"),
OsStr::new("-C"),
install_root.as_os_str(),
],
None,
)?;
let bin_dir = env::var_os("CARGO_HOME")
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from(home).join(".cargo"))
.join("bin");
fs::create_dir_all(&bin_dir)
.with_context(|| format!("create binary directory {}", bin_dir.display()))?;
let link = bin_dir.join("bobbin");
let target = install_root.join("bobbin");
ensure_bobbin_link(&link, &target)?;
Ok(())
}
fn ensure_bobbin_link(link: &Path, target: &Path) -> Result<()> {
if !target.is_file() {
bail!("Bobbin release archive omitted {}", target.display());
}
match link.symlink_metadata() {
Ok(metadata)
if metadata.file_type().is_symlink()
&& fs::read_link(link).ok().as_deref() == Some(target) =>
{
return Ok(());
}
Ok(metadata) if metadata.file_type().is_symlink() && !link.exists() => {
fs::remove_file(link).with_context(|| format!("remove dangling {}", link.display()))?;
}
Ok(_) => {
bail!(
"refusing to replace existing {}; preserve it at a new backup path such as {}.before-tambo, then rerun apply; see Installing > Replacing a cargo-installed Bobbin for backup and rollback commands",
link.display(),
link.display()
);
}
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
Err(error) => return Err(error).with_context(|| format!("inspect {}", link.display())),
}
#[cfg(unix)]
std::os::unix::fs::symlink(target, link)
.with_context(|| format!("link bobbin into {}", link.display()))?;
#[cfg(not(unix))]
bail!("bobbin release installation currently requires a Unix host");
Ok(())
}
const CAMAYOC_REVISION: &str = "f443bf19974d06ab662c8ae3deeccfbb1e082f48";
const CAMAYOC_ARCHIVE_SHA256: &str =
"67b1945615d811ac3c0251412d6b5d619f00d58a7ca87785474c0f1db46ba544";
pub(crate) fn camayoc_root() -> Result<PathBuf> {
if let Some(path) = env::var_os("CABOODLE_CAMAYOC_ROOT") {
return Ok(PathBuf::from(path));
}
let home = env::var_os("HOME").context("HOME is required to install Camayoc")?;
Ok(PathBuf::from(home)
.join(".local/share/caboodle/camayoc")
.join(CAMAYOC_REVISION))
}
fn install_camayoc_bundle() -> Result<()> {
let root = camayoc_root()?;
if root.join("REVISION").exists() {
return Ok(());
}
let download = tempfile::tempdir().context("create Camayoc download directory")?;
let archive = download.path().join("camayoc.tar.gz");
download_https(
&format!("https://github.com/scbrown/camayoc/archive/{CAMAYOC_REVISION}.tar.gz"),
&archive,
)?;
let digest = checked("sha256sum", [archive.as_os_str()], None)?;
let digest = String::from_utf8_lossy(&digest.stdout);
if digest.split_whitespace().next() != Some(CAMAYOC_ARCHIVE_SHA256) {
bail!("Camayoc archive checksum mismatch");
}
fs::create_dir_all(&root)
.with_context(|| format!("create Camayoc install root {}", root.display()))?;
checked(
"tar",
[
OsStr::new("-xzf"),
archive.as_os_str(),
OsStr::new("--strip-components=1"),
OsStr::new("-C"),
root.as_os_str(),
],
None,
)?;
fs::write(root.join("REVISION"), format!("{CAMAYOC_REVISION}\n"))
.context("write installed Camayoc revision")?;
Ok(())
}
fn verify_camayoc_first_ingest(root: &Path, server: &str) -> Result<()> {
let namespace = camayoc_aegis_namespace(&root.join("ontology/core.ttl"))?;
let control = "caboodle-camayoc-control-must-stay-absent";
let marker = "caboodle-camayoc-first-ingest-v1";
if label_count(server, &namespace, control)? != 0 {
bail!("Camayoc negative control unexpectedly exists");
}
let turtle = format!(
"@prefix aegis: <{namespace}> .\n@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n\
aegis:{marker} a aegis:Verification ; rdfs:label \"{marker}\" ; \
aegis:sourceKind \"observed\" ; aegis:falsifier \"the marker remains absent after ingest\" .\n"
);
let payload = json!({
"turtle": turtle,
"actor": "caboodle",
"source": "caboodle Camayoc first-ingest verification"
});
if label_count(server, &namespace, marker)? == 0 {
let first = curl_json(&format!("{server}/knot"), &payload)?;
if first.get("count").and_then(Value::as_u64).unwrap_or(0) == 0 {
bail!("Camayoc first ingest wrote no triples");
}
}
if label_count(server, &namespace, marker)? == 0 {
bail!("Camayoc first ingest was not retrievable");
}
curl_json(&format!("{server}/knot"), &payload)?;
if label_count(server, &namespace, marker)? != 1 {
bail!("Camayoc idempotent replay wrote duplicate triples");
}
Ok(())
}
pub(crate) fn camayoc_aegis_namespace(path: &Path) -> Result<String> {
let body = fs::read_to_string(path)
.with_context(|| format!("read Camayoc ontology {}", path.display()))?;
body.lines()
.find_map(|line| {
line.trim()
.strip_prefix("@prefix aegis: <")
.and_then(|rest| rest.strip_suffix("> ."))
.map(str::to_owned)
})
.context("Camayoc ontology does not declare the aegis namespace")
}
fn label_count(server: &str, namespace: &str, label: &str) -> Result<u64> {
let query = format!(
"SELECT ?s WHERE {{ ?s <http://www.w3.org/2000/01/rdf-schema#label> \"{label}\" . FILTER(STRSTARTS(STR(?s), \"{namespace}\")) }}"
);
let response = curl_json(&format!("{server}/query"), &json!({"query": query}))?;
response["count"]
.as_u64()
.context("Quipu query response omitted numeric count")
}
pub(crate) fn curl_json(url: &str, payload: &Value) -> Result<Value> {
curl_json_request(url, Some(payload.to_string()))
}
fn curl_get_json(url: &str) -> Result<Value> {
curl_json_request(url, None)
}
fn quipu_auth_config() -> Result<Option<tempfile::NamedTempFile>> {
crate::quipu_auth::config()
}
pub(crate) fn curl_auth_header_line(token: &str) -> Result<String> {
if token.contains(['\n', '\r']) {
bail!("QUIPU_AUTH_TOKEN contains a line break; refusing to pass it to curl");
}
let escaped = token.replace('\\', "\\\\").replace('"', "\\\"");
Ok(format!("header = \"Authorization: Bearer {escaped}\"\n"))
}
pub(crate) fn quipu_write_probe(server: &str) -> Result<(u16, String, bool)> {
let body_file = tempfile::NamedTempFile::new().context("create write-probe body file")?;
let auth = quipu_auth_config()?;
let mut args: Vec<OsString> = [
"--silent",
"--max-time",
"5",
"--request",
"POST",
"--header",
"Content-Type: application/json",
"--data",
"{}",
"--write-out",
"%{http_code}",
"--output",
]
.iter()
.map(OsString::from)
.collect();
args.push(body_file.path().as_os_str().to_owned());
if let Some(file) = &auth {
args.push(OsString::from("--config"));
args.push(file.path().as_os_str().to_owned());
}
args.push(OsString::from(format!(
"{}/episode",
server.trim_end_matches('/')
)));
let result = output("curl", args, None)?;
let code = String::from_utf8_lossy(&result.stdout)
.trim()
.parse::<u16>()
.unwrap_or(0);
let body = fs::read_to_string(body_file.path()).unwrap_or_default();
Ok((code, body, auth.is_some()))
}
fn curl_json_request(url: &str, body: Option<String>) -> Result<Value> {
let auth = quipu_auth_config()?;
let mut args = vec![
OsString::from("--fail"),
OsString::from("--silent"),
OsString::from("--show-error"),
];
if let Some(body) = body {
args.extend([
OsString::from("--request"),
OsString::from("POST"),
OsString::from("--header"),
OsString::from("Content-Type: application/json"),
OsString::from("--data"),
OsString::from(body),
]);
}
if let Some(file) = &auth {
args.push(OsString::from("--config"));
args.push(file.path().as_os_str().to_owned());
}
args.push(OsString::from(url));
let result = checked("curl", args, None)?;
serde_json::from_slice(&result.stdout)
.with_context(|| format!("parse JSON response from {url}"))
}
pub(crate) fn download_https(url: &str, destination: &Path) -> Result<()> {
const ATTEMPTS: u32 = 3;
for attempt in 1..=ATTEMPTS {
let pending = tempfile::NamedTempFile::new_in(
destination
.parent()
.filter(|p| !p.as_os_str().is_empty())
.unwrap_or(Path::new(".")),
)
.context("create pending download")?;
let result = output(
"curl",
[
OsStr::new("--disable"),
OsStr::new("--fail"),
OsStr::new("--location"),
OsStr::new("--silent"),
OsStr::new("--show-error"),
OsStr::new("--proto"),
OsStr::new("=https"),
OsStr::new("--tlsv1.2"),
OsStr::new("--connect-timeout"),
OsStr::new("10"),
OsStr::new("--max-time"),
OsStr::new("120"),
OsStr::new("--write-out"),
OsStr::new("%{http_code}"),
OsStr::new("--output"),
pending.path().as_os_str(),
OsStr::new(url),
],
None,
)?;
if result.status.success() {
pending
.persist(destination)
.context("publish downloaded artifact")?;
if attempt > 1 {
eprintln!("download {url}: succeeded on attempt {attempt}/{ATTEMPTS}");
}
return Ok(());
}
let http = String::from_utf8_lossy(&result.stdout)
.trim()
.parse::<u16>()
.unwrap_or(0);
let detail = format!(
"download {url}: attempt {attempt}/{ATTEMPTS}, curl {}, HTTP {http:03}: {}",
result.status,
String::from_utf8_lossy(&result.stderr).trim()
);
if !retryable_download(result.status.code(), http) {
bail!("{detail}; terminal failure (not retried)");
}
if attempt == ATTEMPTS {
bail!("{detail}; retry limit exhausted");
}
let delay = 1 << (attempt - 1);
eprintln!("{detail}; retrying in {delay}s");
drop(pending);
std::thread::sleep(std::time::Duration::from_secs(delay));
}
unreachable!("every final attempt returns")
}
fn retryable_download(code: Option<i32>, http: u16) -> bool {
if (400..500).contains(&http) {
return false;
}
match code {
Some(22) => (500..600).contains(&http),
Some(5 | 6 | 7 | 18 | 28 | 52 | 55 | 56 | 92) => true,
_ => false,
}
}
fn verify_checksum(archive: &Path, sums: &Path) -> Result<()> {
let filename = archive
.file_name()
.and_then(OsStr::to_str)
.context("bobbin archive filename is not UTF-8")?;
let expected = fs::read_to_string(sums)
.context("read Bobbin checksum manifest")?
.lines()
.find_map(|line| {
let mut fields = line.split_whitespace();
let digest = fields.next()?;
let listed = fields.next()?.trim_start_matches('*');
(listed == filename).then(|| digest.to_owned())
})
.with_context(|| format!("checksum manifest does not cover {filename}"))?;
let actual = checked("sha256sum", [archive.as_os_str()], None)?;
let actual = String::from_utf8_lossy(&actual.stdout)
.split_whitespace()
.next()
.context("sha256sum returned no digest")?
.to_owned();
if actual != expected {
bail!("checksum mismatch for {filename}");
}
Ok(())
}
#[cfg(test)]
mod yupana_release_tests {
use super::*;
#[test]
fn published_platforms_resolve_and_missing_platforms_refuse() {
for (arch, os, target) in [
("x86_64", "linux", "x86_64-linux-gnu"),
("aarch64", "macos", "aarch64-apple-darwin"),
("x86_64", "macos", "x86_64-apple-darwin"),
] {
assert_eq!(yupana_release_for(arch, os).unwrap().0, target);
}
assert!(yupana_release_for("aarch64", "linux").is_err());
assert!(yupana_release_for("x86_64", "windows").is_err());
assert!(!prerequisites(ToolName::Yupana, QuipuFlavor::Release).contains(&"sha256sum"));
}
#[test]
fn checksum_refuses_corruption_without_an_external_hash_command() {
let root = tempfile::tempdir().unwrap();
let file = root.path().join("archive");
fs::write(&file, b"abc").unwrap();
let expected = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad";
verify_yupana_checksum(&file, expected).unwrap();
fs::write(&file, b"abd").unwrap();
assert!(verify_yupana_checksum(&file, expected).is_err());
}
#[test]
#[ignore = "downloads all three published archives; explicit release acceptance"]
fn real_yupana_archives_match_pins_and_unpack() {
for (arch, os) in [
("x86_64", "linux"),
("aarch64", "macos"),
("x86_64", "macos"),
] {
let (target, checksum) = yupana_release_for(arch, os).unwrap();
let root = tempfile::tempdir().unwrap();
fetch_yupana_release(target, checksum, root.path()).unwrap();
assert!(root.path().join("yupana").is_file());
assert_eq!(
fs::read_link(root.path().join("hank")).unwrap(),
Path::new("yupana")
);
eprintln!("verified published {target}: {checksum}");
}
}
}
#[cfg(test)]
mod flavor_tests {
use super::{
fetch_quipu_release, quipu_install_args, quipu_release_target, quipu_release_target_for,
require_compiled_feature,
};
use crate::model::QuipuFlavor;
use serde_json::json;
#[test]
fn install_args_add_only_the_lancedb_feature_to_the_reviewed_pin() {
let release = quipu_install_args(QuipuFlavor::Release);
let lancedb = quipu_install_args(QuipuFlavor::Lancedb);
assert_eq!(release[6], "full");
assert_eq!(lancedb[6], "full,lancedb");
assert_eq!(release[..6], lancedb[..6]);
assert_eq!(release[7..], lancedb[7..]);
assert!(release.contains(&"--locked"));
}
#[test]
fn default_quipu_release_has_a_supported_host_artifact() {
if (std::env::consts::ARCH, std::env::consts::OS) == ("x86_64", "linux") {
assert_eq!(quipu_release_target().unwrap(), "x86_64-unknown-linux-gnu");
}
}
#[test]
fn quipu_release_supports_published_linux_and_macos_architectures() {
for (arch, os, target) in [
("x86_64", "linux", "x86_64-unknown-linux-gnu"),
("aarch64", "linux", "aarch64-unknown-linux-gnu"),
("x86_64", "macos", "x86_64-apple-darwin"),
("aarch64", "macos", "aarch64-apple-darwin"),
] {
assert_eq!(quipu_release_target_for(arch, os).unwrap(), target);
}
assert!(quipu_release_target_for("x86_64", "windows").is_err());
}
#[test]
#[ignore = "downloads published artifacts; explicit release acceptance only"]
fn real_aarch64_quipu_release_resolves_and_verifies() {
for os in ["macos", "linux"] {
let target = quipu_release_target_for("aarch64", os).unwrap();
let download = tempfile::tempdir().unwrap();
let unpacked = fetch_quipu_release(target, download.path()).unwrap();
assert!(unpacked.join("quipu").is_file());
assert!(unpacked.join("quipu-server").is_file());
eprintln!("verified published Quipu checksums and both binaries for {target}");
}
}
#[test]
fn compile_map_proof_requires_a_true_lancedb_entry() {
require_compiled_feature(&json!({"features": {"lancedb": true}}), "lancedb").unwrap();
let absent = require_compiled_feature(&json!({"features": {"onnx": true}}), "lancedb")
.unwrap_err()
.to_string();
assert!(absent.contains("compiled without it"), "{absent}");
let disabled =
require_compiled_feature(&json!({"features": {"lancedb": false}}), "lancedb")
.unwrap_err()
.to_string();
assert!(disabled.contains("compiled without it"), "{disabled}");
let unmapped = require_compiled_feature(&json!({"version": "0.3.27"}), "lancedb")
.unwrap_err()
.to_string();
assert!(
unmapped.contains("no per-feature compile map"),
"{unmapped}"
);
}
}
#[cfg(all(test, unix))]
mod tests {
use super::ensure_bobbin_link;
use std::{fs, os::unix::fs::symlink};
#[test]
fn failed_commands_preserve_both_diagnostic_streams_and_status() {
for script in [
"printf 'bootstrap rejected'; exit 2",
"printf 'bootstrap rejected' >&2; exit 2",
"printf 'probe started'; printf 'bootstrap rejected' >&2; exit 2",
"exit 2",
] {
let error = super::checked("sh", ["-c", script], None)
.unwrap_err()
.to_string();
assert!(error.contains("exit status: 2"), "{error}");
assert!(error.contains("stdout:\n"), "{error}");
assert!(error.contains("stderr:\n"), "{error}");
if script.contains("bootstrap rejected") {
assert!(error.contains("bootstrap rejected"), "{error}");
}
if script.contains("probe started") {
assert!(error.contains("probe started"), "{error}");
}
}
let output = super::checked("sh", ["-c", "printf 'successful output'"], None).unwrap();
assert_eq!(output.stdout, b"successful output");
}
#[test]
fn bobbin_link_accepts_its_restored_cache_entry_but_refuses_foreign_paths() {
let root = tempfile::tempdir().unwrap();
let target = root.path().join("release/bobbin");
fs::create_dir_all(target.parent().unwrap()).unwrap();
fs::write(&target, "binary fixture").unwrap();
let link = root.path().join("bin/bobbin");
fs::create_dir_all(link.parent().unwrap()).unwrap();
symlink(&target, &link).unwrap();
ensure_bobbin_link(&link, &target).unwrap();
assert_eq!(fs::read_link(&link).unwrap(), target);
let foreign = root.path().join("foreign");
fs::write(&foreign, "do not replace").unwrap();
fs::remove_file(&link).unwrap();
symlink(&foreign, &link).unwrap();
let error = ensure_bobbin_link(&link, &target).unwrap_err().to_string();
assert!(error.contains("refusing to replace existing"));
assert_eq!(fs::read_link(&link).unwrap(), foreign);
}
#[test]
fn bobbin_link_repairs_its_own_dangling_link_but_still_refuses_a_real_file() {
let root = tempfile::tempdir().unwrap();
let target = root.path().join("release/bobbin");
fs::create_dir_all(target.parent().unwrap()).unwrap();
fs::write(&target, "binary fixture").unwrap();
let link = root.path().join("bin/bobbin");
fs::create_dir_all(link.parent().unwrap()).unwrap();
symlink(root.path().join("gone/bobbin"), &link).unwrap();
assert!(link.symlink_metadata().is_ok(), "the link itself exists");
assert!(
!link.exists(),
"but it resolves to nothing -- this is the ENOENT"
);
ensure_bobbin_link(&link, &target).unwrap();
assert_eq!(fs::read_link(&link).unwrap(), target);
fs::remove_file(&link).unwrap();
fs::write(&link, "a real bobbin somebody installed").unwrap();
let error = ensure_bobbin_link(&link, &target).unwrap_err().to_string();
assert!(error.contains("refusing to replace existing"));
assert_eq!(
fs::read_to_string(&link).unwrap(),
"a real bobbin somebody installed"
);
}
}
#[cfg(test)]
mod auth_config_tests {
use super::curl_auth_header_line;
#[test]
fn curl_auth_line_escapes_quotes_and_refuses_line_breaks() {
assert_eq!(
curl_auth_header_line("abc").unwrap(),
"header = \"Authorization: Bearer abc\"\n"
);
assert_eq!(
curl_auth_header_line(r#"a"b\c"#).unwrap(),
"header = \"Authorization: Bearer a\\\"b\\\\c\"\n"
);
assert!(curl_auth_header_line("tok\nurl = \"http://elsewhere\"").is_err());
assert!(curl_auth_header_line("tok\r").is_err());
}
}
pub fn verify_member(name: &str) -> Result<()> {
let member = crate::members::get(name).context("unknown reviewed member")?;
manifest::verify_release_member(member)
}
#[cfg(all(test, unix))]
mod quipu_alias_layout_tests {
use super::verify_quipu_alias;
use std::os::unix::fs::symlink;
use std::{fs, path::Path};
fn script(path: &Path, version: &str) {
crate::test_exec::write_executable(path, &format!("#!/bin/sh\necho '{version}'\n"));
}
#[test]
fn package_managed_layout_passes_with_qp_beside_the_resolved_quipu() {
let root = tempfile::tempdir().unwrap();
let cellar = root.path().join("Cellar/quipu/1.0/bin");
let bin = root.path().join("bin");
fs::create_dir_all(&cellar).unwrap();
fs::create_dir(&bin).unwrap();
script(&cellar.join("quipu"), "quipu 1.0");
symlink("quipu", cellar.join("qp")).unwrap();
symlink("../Cellar/quipu/1.0/bin/quipu", bin.join("quipu")).unwrap();
symlink("../Cellar/quipu/1.0/bin/qp", bin.join("qp")).unwrap();
verify_quipu_alias(&bin.join("quipu")).unwrap();
}
#[test]
fn a_qp_for_another_program_or_no_qp_still_fails() {
let root = tempfile::tempdir().unwrap();
let bin = root.path().join("bin");
fs::create_dir(&bin).unwrap();
script(&bin.join("quipu"), "quipu 1.0");
let err = verify_quipu_alias(&bin.join("quipu")).unwrap_err();
assert!(err.to_string().contains("requires relative qp -> quipu"));
let cellar = root.path().join("Cellar/bin");
fs::create_dir_all(&cellar).unwrap();
script(&cellar.join("quipu"), "quipu 1.0");
symlink("quipu", cellar.join("qp")).unwrap();
script(&root.path().join("other"), "quipu 1.0");
let pkg = root.path().join("pkgbin");
fs::create_dir(&pkg).unwrap();
symlink("../Cellar/bin/quipu", pkg.join("quipu")).unwrap();
symlink("../other", pkg.join("qp")).unwrap();
let err = verify_quipu_alias(&pkg.join("quipu")).unwrap_err();
assert!(err.to_string().contains("same executable"));
}
#[test]
fn owned_layout_with_qp_beside_quipu_still_passes() {
let root = tempfile::tempdir().unwrap();
script(&root.path().join("quipu"), "quipu 1.0");
symlink("quipu", root.path().join("qp")).unwrap();
verify_quipu_alias(&root.path().join("quipu")).unwrap();
}
}