//! The registry's custody, on the command line: verification runs on one machine and writes an //! unsigned record, signing is a separate light step on another, and nothing signs without being //! given the key by its path. A throwaway key throughout: no test needs the production key. use flowrs_registry::package::Package; use flowrs_registry::record::{ Condition, ConditionId, Method, PackageRef, Record, SCHEMA, SignedRecord, Status, Toolchain, }; use serde_json::Value; use std::path::{Path, PathBuf}; use std::process::{Command, Output}; use tempfile::TempDir; fn flowrsc(dir: &Path, args: &[&str], keys: Option<&str>) -> Output { let mut c = Command::new(env!("CARGO_BIN_EXE_flowrsc")); c.args(args).current_dir(dir); match keys { Some(k) => c.env("FLOWRS_REGISTRY_KEYS", k), None => c.env_remove("FLOWRS_REGISTRY_KEYS"), }; c.output().expect("flowrsc runs") } fn text(o: &Output) -> String { format!( "{}{}", String::from_utf8_lossy(&o.stdout), String::from_utf8_lossy(&o.stderr) ) } const MANIFEST: &str = r#"schema = "flowrs.package/1" name = "tiny" version = "0.1.0" kind = "widget" licence = "Apache-2.0" description = "A package for the custody tests." provides = ["tiny.node"] tests = ["tests::it_runs"] [needs] network = [] filesystem = [] [[ports]] of = "tiny.node" port = "in" unit = "1" "#; /// A package tree, its archive, and a record of it with all five conditions as given. fn package(dir: &Path) -> Package { let p = dir.join("tiny"); std::fs::create_dir_all(p.join("src")).unwrap(); std::fs::write( p.join("Cargo.toml"), "[package]\nname = \"tiny\"\nversion = \"0.1.0\"\nedition = \"2024\"\n", ) .unwrap(); std::fs::write( p.join("src/lib.rs"), "#[cfg(test)]\nmod tests {\n #[test]\n fn it_runs() {}\n}\n", ) .unwrap(); std::fs::write(p.join("flowrs-package.toml"), MANIFEST).unwrap(); Package::from_dir(&p).unwrap() } fn record(pkg: &Package, status: Status) -> Record { let manifest = pkg.manifest().unwrap(); Record { schema: SCHEMA.into(), package: PackageRef { name: manifest.name.clone(), version: manifest.version.clone(), kind: manifest.kind, }, hash: pkg.hash.clone(), manifest, conditions: ConditionId::ALL .iter() .map(|&id| Condition { id, status, how: vec![Method::Observed], detail: "checked on another machine".into(), evidence: vec![], }) .collect(), toolchain: Toolchain { rustc: "rustc".into(), cargo: "cargo".into(), host: "aarch64-apple-darwin".into(), sandbox: "seatbelt".into(), observation: "kernel reports (canary seen)".into(), }, flowrs_commit: "0000000".into(), verified_at: "2026-11-02T00:00:00Z".into(), verifier: "flowrs-registry test".into(), } } /// Stage `pkg` as `pkg submit` does on the verifying machine. fn stage(reg: &Path, pkg: &Package, r: Record) -> PathBuf { let hex = flowrs_registry::archive::hex_of(&pkg.hash).unwrap(); let incoming = reg.join("incoming"); std::fs::create_dir_all(&incoming).unwrap(); std::fs::write(incoming.join(format!("{hex}.fpkg")), &pkg.archive).unwrap(); let file = incoming.join(format!("{hex}.json")); std::fs::write( &file, serde_json::to_string_pretty(&SignedRecord { record: r, signature: None, }) .unwrap(), ) .unwrap(); file } /// A key made by the command, and its id. fn key(dir: &Path) -> (PathBuf, String) { let f = dir.join("key.json"); let o = flowrsc(dir, &["pkg", "keygen", f.to_str().unwrap()], None); assert!(o.status.success(), "{}", text(&o)); let public: Value = serde_json::from_str(&std::fs::read_to_string(dir.join("key.json.pub")).unwrap()).unwrap(); (f, public["id"].as_str().unwrap().to_string()) } fn signature(file: &Path) -> Option { let v: Value = serde_json::from_str(&std::fs::read_to_string(file).unwrap()).unwrap(); v.get("signature").cloned() } /// `keygen` writes a key only its owner can read, and never over one that exists. #[test] fn keygen_writes_an_owners_key_and_never_over_another() { let d = TempDir::new().unwrap(); let (f, id) = key(d.path()); assert!(id.starts_with("v2:")); #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; let mode = std::fs::metadata(&f).unwrap().permissions().mode(); assert_eq!(mode & 0o777, 0o600, "{mode:o}"); } let before = std::fs::read(&f).unwrap(); let o = flowrsc(d.path(), &["pkg", "keygen", f.to_str().unwrap()], None); assert!(!o.status.success()); assert!(text(&o).contains("never overwritten"), "{}", text(&o)); assert_eq!(std::fs::read(&f).unwrap(), before); // The public half holds no secret. let public = std::fs::read_to_string(d.path().join("key.json.pub")).unwrap(); assert!( !public.contains("ml_dsa_65") && !public.contains("ed25519\""), "{public}" ); } /// Verifying and staging take no key at all: there is no way to sign on the machine that builds. #[test] fn verify_and_submit_take_no_key() { let d = TempDir::new().unwrap(); for args in [ &["pkg", "verify", "x", "--key", "k.json"][..], &["pkg", "submit", "x", "--registry", "r", "--key", "k.json"][..], ] { let o = flowrsc(d.path(), args, None); assert_eq!(o.status.code(), Some(2), "{args:?}"); assert!(text(&o).contains("--key"), "{}", text(&o)); } } /// `sign` needs a key named on its command line; a mistyped path signs nothing and falls back to /// nothing. #[test] fn a_mistyped_key_path_signs_nothing() { let d = TempDir::new().unwrap(); let (_, id) = key(d.path()); let pkg = package(d.path()); let reg = d.path().join("reg"); let staged = stage(®, &pkg, record(&pkg, Status::Passed)); let reg_s = reg.to_str().unwrap(); // With no --key at all. let o = flowrsc(d.path(), &["pkg", "sign", "--registry", reg_s], Some(&id)); assert_eq!(o.status.code(), Some(2)); assert!(text(&o).contains("--key"), "{}", text(&o)); assert_eq!(signature(&staged), None); // With a path that is not there. let o = flowrsc( d.path(), &["pkg", "sign", "--key", "kye.json", "--registry", reg_s], Some(&id), ); assert_eq!(o.status.code(), Some(2)); assert!(text(&o).contains("kye.json"), "{}", text(&o)); assert_eq!(signature(&staged), None); } /// A real key this host does not trust signs nothing: it would be a record no host admits. #[test] fn a_key_this_host_does_not_trust_signs_nothing() { let d = TempDir::new().unwrap(); let (f, _) = key(d.path()); let pkg = package(d.path()); let reg = d.path().join("reg"); let staged = stage(®, &pkg, record(&pkg, Status::Passed)); let o = flowrsc( d.path(), &[ "pkg", "sign", "--key", f.to_str().unwrap(), "--registry", reg.to_str().unwrap(), ], None, ); assert_eq!(o.status.code(), Some(2)); assert!(text(&o).contains("does not trust"), "{}", text(&o)); assert_eq!(signature(&staged), None); } /// The whole split: a record verified elsewhere, signed here by a command that compiles nothing, /// published, and then admitted for this exact tree. #[test] fn verified_elsewhere_signed_here_published_and_admitted() { let d = TempDir::new().unwrap(); let (f, id) = key(d.path()); let pkg = package(d.path()); let reg = d.path().join("reg"); let staged = stage(®, &pkg, record(&pkg, Status::Passed)); let (reg_s, key_s) = (reg.to_str().unwrap(), f.to_str().unwrap()); let tree = d.path().join("tiny"); let tree_s = tree.to_str().unwrap(); // Not signed: publishing holds it back. let o = flowrsc( d.path(), &["pkg", "publish", "--registry", reg_s], Some(&id), ); assert!(o.status.success(), "{}", text(&o)); assert!(text(&o).contains("left out"), "{}", text(&o)); let o = flowrsc( d.path(), &["pkg", "check", tree_s, "--registry", reg_s], Some(&id), ); assert_eq!(o.status.code(), Some(1), "{}", text(&o)); let o = flowrsc( d.path(), &["pkg", "sign", "--key", key_s, "--registry", reg_s], Some(&id), ); assert!(o.status.success(), "{}", text(&o)); assert!(text(&o).contains("signed tiny"), "{}", text(&o)); assert_eq!( signature(&staged).unwrap()["key_id"].as_str(), Some(id.as_str()) ); let o = flowrsc( d.path(), &["pkg", "publish", "--registry", reg_s], Some(&id), ); assert!(o.status.success(), "{}", text(&o)); let o = flowrsc( d.path(), &["pkg", "check", tree_s, "--registry", reg_s], Some(&id), ); assert!(o.status.success(), "{}", text(&o)); assert!(text(&o).contains("is admitted"), "{}", text(&o)); // One byte of the tree changed: the signed record no longer covers it. std::fs::write(tree.join("src/lib.rs"), "// changed\n").unwrap(); let o = flowrsc( d.path(), &["pkg", "check", tree_s, "--registry", reg_s], Some(&id), ); assert_eq!(o.status.code(), Some(1), "{}", text(&o)); } /// Signing one record file, for a record carried by hand, holds to the same rule: a record that /// did not pass is not signed, and the file stays as it was. #[test] fn one_record_file_is_signed_only_if_it_passed() { let d = TempDir::new().unwrap(); let (f, id) = key(d.path()); let pkg = package(d.path()); let key_s = f.to_str().unwrap(); let write = |name: &str, status| { let file = d.path().join(name); std::fs::write( &file, serde_json::to_string_pretty(&SignedRecord { record: record(&pkg, status), signature: None, }) .unwrap(), ) .unwrap(); file }; let bad = write("failed.json", Status::Failed); let o = flowrsc( d.path(), &["pkg", "sign", bad.to_str().unwrap(), "--key", key_s], Some(&id), ); assert_eq!(o.status.code(), Some(2), "{}", text(&o)); assert_eq!(signature(&bad), None); let good = write("passed.json", Status::Passed); let out = d.path().join("signed.json"); let o = flowrsc( d.path(), &[ "pkg", "sign", good.to_str().unwrap(), "--key", key_s, "-o", out.to_str().unwrap(), ], Some(&id), ); assert!(o.status.success(), "{}", text(&o)); assert_eq!(signature(&good), None, "the input stays as it was with -o"); assert_eq!( signature(&out).unwrap()["key_id"].as_str(), Some(id.as_str()) ); } /// `check` with no registry is what a contributor runs: no key, and a package whose manifest does /// not read stops there with the reason. #[test] fn a_contributors_check_stops_at_a_manifest_that_does_not_read() { let d = TempDir::new().unwrap(); let p = d.path().join("broken"); std::fs::create_dir_all(&p).unwrap(); std::fs::write(p.join("Cargo.toml"), "[package]\nname = \"broken\"\n").unwrap(); std::fs::write( p.join("flowrs-package.toml"), "schema = \"flowrs.package/9\"\n", ) .unwrap(); let o = flowrsc(d.path(), &["pkg", "check", p.to_str().unwrap()], None); assert_eq!(o.status.code(), Some(2), "{}", text(&o)); assert!(text(&o).contains("error"), "{}", text(&o)); } /// The whole local check, with no key on the machine: a package whose test fails is told so, with /// a non-zero status, and nothing is written anywhere. It builds flowrs-core confined, once, in the /// verifier's shared target. #[test] fn a_contributors_check_runs_with_no_key_and_says_what_failed() { let d = TempDir::new().unwrap(); let p = d.path().join("failing"); std::fs::create_dir_all(p.join("src")).unwrap(); std::fs::write( p.join("Cargo.toml"), "[package]\nname = \"failing\"\nversion = \"0.1.0\"\nedition = \"2024\"\npublish = false\n\n[dependencies]\nflowrs-core = { path = \"../../crates/flowrs-core\" }\n", ) .unwrap(); std::fs::write( p.join("src/lib.rs"), "pub fn install() -> Result<(), String> {\n Ok(())\n}\n#[cfg(test)]\nmod tests {\n #[test]\n fn it_runs() {\n panic!(\"this package is broken\");\n }\n}\n", ) .unwrap(); std::fs::write( p.join("flowrs-package.toml"), MANIFEST.replace("\"tiny\"", "\"failing\""), ) .unwrap(); let work = std::env::temp_dir().join("flowrs-registry-verify"); let o = flowrsc( d.path(), &[ "pkg", "check", p.to_str().unwrap(), "--work", work.to_str().unwrap(), "--commit", "custody-test", ], None, ); let t = text(&o); assert_eq!(o.status.code(), Some(1), "{t}"); assert!(t.contains("the manifest reads"), "{t}"); assert!(t.contains("FAILED tests"), "{t}"); assert!(t.contains("fix it before opening a pull request"), "{t}"); assert!(!p.join("record.json").exists()); }