cli

Command-line interface for Radroots
git clone https://radroots.dev/git/cli.git
Log | Files | Refs | README | LICENSE

commit d64ca18d797f7528c1d3173ed5304c706e1edde4
parent 44f51cf1e3c3392550b70789eb219dbb57e93ed5
Author: triesap <tyson@radroots.org>
Date:   Tue,  8 Sep 2026 23:25:23 +0000

cli: verify immutable source lock agreement

- correct the stale digest for the unchanged cargo dependency lock
- validate exact public revisions versions and resolved shared sources
- reject malformed source locks and dependency overrides offline
- verify workspace tests strict clippy api and supply chain checks

Diffstat:
MREADME | 6+++++-
Mradroots.lib.source-lock.v1.toml | 2+-
Mscripts/verify-boundaries.sh | 1+
Ascripts/verify-source-lock.sh | 6++++++
Mtools/xtask/src/main.rs | 8+++++++-
Atools/xtask/src/source_lock.rs | 242+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 262 insertions(+), 3 deletions(-)

diff --git a/README b/README @@ -82,11 +82,15 @@ cargo test --all-targets --locked cargo clippy --all-targets --locked -- -D warnings RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --locked scripts/verify-boundaries.sh +scripts/verify-source-lock.sh scripts/verify-supply-chain.sh tools/verify-repository-boundary.sh ``` -The boundary command byte-compares the root-only public API against its +The source-lock command checks the exact Cargo lock digest, public dependency +revision, declared version and resolved shared package sources. It runs offline +and rejects local overrides or mismatched inputs. The boundary command includes +this check and byte-compares the root-only public API against its reviewed baseline and rejects forbidden repository roots or credential material. The supply-chain command validates the exact locked dependency graph, license policy, approved registries and immutable Lib source, then diff --git a/radroots.lib.source-lock.v1.toml b/radroots.lib.source-lock.v1.toml @@ -6,4 +6,4 @@ workspace_catalog_sha256 = "deca0c080deae187ff8186c0708903e42f41ea57f77c5f91581e version = "0.1.0-alpha" source_archive_sha256 = "2da62bd0112d43b634ba5e87dfc09d048f7ea1836ab89d2fd18fcfce5186ad6b" lockfile = "Cargo.lock" -lockfile_sha256 = "ef183844b032b7baf9d607d43eaecac944ad42e819e7247508130296d2543769" +lockfile_sha256 = "e0d86121841778d6ebb802bbfb807e7f74490a03b2a9593b15a7ac39020300ac" diff --git a/scripts/verify-boundaries.sh b/scripts/verify-boundaries.sh @@ -5,6 +5,7 @@ repo_root="$(git rev-parse --show-toplevel)" cd "$repo_root" tools/verify-repository-boundary.sh +scripts/verify-source-lock.sh test "$(cargo public-api --version)" = "cargo-public-api 0.52.0" temporary_api="$(mktemp)" trap 'rm -f "$temporary_api"' EXIT diff --git a/scripts/verify-source-lock.sh b/scripts/verify-source-lock.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(git rev-parse --show-toplevel)" +cd "$repo_root" +cargo run --locked -p radroots_cli_xtask -- source-lock-check diff --git a/tools/xtask/src/main.rs b/tools/xtask/src/main.rs @@ -2,10 +2,15 @@ mod rshr_202_step_302_gate; mod rshr_202_step_302_platform; +mod source_lock; fn main() { if run().is_err() { - eprintln!("step_302_gate_failed"); + if std::env::args().nth(1).as_deref() == Some("source-lock-check") { + eprintln!("source_lock_check_failed"); + } else { + eprintln!("step_302_gate_failed"); + } std::process::exit(1); } } @@ -13,6 +18,7 @@ fn main() { fn run() -> Result<(), ()> { let mut arguments = std::env::args().skip(1); match arguments.next().as_deref() { + Some("source-lock-check") if arguments.next().is_none() => source_lock::run(), Some("rshr-step-302-gate") => { let args = parse_gate_args(arguments.collect())?; rshr_202_step_302_gate::run(args).map_err(|_| ()) diff --git a/tools/xtask/src/source_lock.rs b/tools/xtask/src/source_lock.rs @@ -0,0 +1,242 @@ +use serde_json::Value; +use sha2::{Digest, Sha256}; +use std::collections::BTreeMap; +use std::io::Read; +use std::path::Path; +use std::process::{Command, Stdio}; + +const MAX_TEXT: u64 = 2 * 1024 * 1024; +const MAX_METADATA: u64 = 4 * MAX_TEXT; +const FIELDS: [&str; 9] = [ + "schema", + "repository", + "revision", + "architecture", + "workspace_catalog_sha256", + "version", + "source_archive_sha256", + "lockfile", + "lockfile_sha256", +]; + +pub fn run() -> Result<(), ()> { + let root = Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .and_then(Path::parent) + .ok_or(())?; + let lock = read(root.join("radroots.lib.source-lock.v1.toml").as_path())?; + let cargo_lock = read(root.join("Cargo.lock").as_path())?; + let metadata = metadata(root)?; + validate(&lock, &cargo_lock, &metadata)?; + println!("source lock ok: exact digest, public revision and dependency agreement"); + Ok(()) +} + +fn read(path: &Path) -> Result<Vec<u8>, ()> { + let file_type = std::fs::symlink_metadata(path).map_err(|_| ())?; + if !file_type.is_file() || file_type.len() > MAX_TEXT { + return Err(()); + } + let mut bytes = Vec::new(); + std::fs::File::open(path) + .map_err(|_| ())? + .take(MAX_TEXT + 1) + .read_to_end(&mut bytes) + .map_err(|_| ())?; + if bytes.len() as u64 > MAX_TEXT { + return Err(()); + } + Ok(bytes) +} + +fn metadata(root: &Path) -> Result<Value, ()> { + let mut child = Command::new("cargo") + .args(["metadata", "--locked", "--offline", "--format-version", "1"]) + .current_dir(root) + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .spawn() + .map_err(|_| ())?; + let mut bytes = Vec::new(); + let read = child + .stdout + .take() + .ok_or(())? + .take(MAX_METADATA + 1) + .read_to_end(&mut bytes); + if read.is_err() || bytes.len() as u64 > MAX_METADATA { + let _ = child.kill(); + let _ = child.wait(); + return Err(()); + } + if !child.wait().map_err(|_| ())?.success() { + return Err(()); + } + serde_json::from_slice(&bytes).map_err(|_| ()) +} + +fn fields(bytes: &[u8]) -> Result<BTreeMap<&str, &str>, ()> { + let text = std::str::from_utf8(bytes).map_err(|_| ())?; + let mut fields = BTreeMap::new(); + for line in text.lines() { + let (key, quoted) = line.split_once(" = ").ok_or(())?; + let value = quoted + .strip_prefix('"') + .and_then(|value| value.strip_suffix('"')) + .ok_or(())?; + if !FIELDS.contains(&key) + || value.is_empty() + || value + .bytes() + .any(|byte| !byte.is_ascii_graphic() || matches!(byte, b'"' | b'\\')) + || fields.insert(key, value).is_some() + { + return Err(()); + } + } + if fields.len() != FIELDS.len() { + return Err(()); + } + Ok(fields) +} + +fn validate(lock: &[u8], cargo_lock: &[u8], metadata: &Value) -> Result<(), ()> { + let fields = fields(lock)?; + for (key, expected) in [ + ("schema", "radroots.lib.source-lock.v1"), + ("repository", "https://github.com/radrootslabs/lib"), + ("architecture", "radroots.crates.release.v2"), + ("lockfile", "Cargo.lock"), + ] { + if fields[key] != expected { + return Err(()); + } + } + for (key, length) in [ + ("revision", 40), + ("workspace_catalog_sha256", 64), + ("source_archive_sha256", 64), + ("lockfile_sha256", 64), + ] { + if fields[key].len() != length + || !fields[key] + .bytes() + .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte)) + { + return Err(()); + } + } + if fields["lockfile_sha256"] != hex::encode(Sha256::digest(cargo_lock)) { + return Err(()); + } + let source = format!( + "git+https://github.com/radrootslabs/lib.git?rev={}", + fields["revision"] + ); + let resolved = format!("{source}#{}", fields["revision"]); + let packages = metadata["packages"].as_array().ok_or(())?; + let cli = packages + .iter() + .find(|package| package["name"] == "radroots_cli") + .ok_or(())?; + let dependencies = cli["dependencies"].as_array().ok_or(())?; + let facade = dependencies + .iter() + .find(|dependency| dependency["name"] == "radroots") + .ok_or(())?; + if facade["source"] != source + || facade["req"] != format!("={}", fields["version"]) + || !facade["rename"].is_null() + { + return Err(()); + } + let mut shared_count = 0; + for package in packages { + let name = package["name"].as_str().ok_or(())?; + if (name == "radroots" || name.starts_with("radroots_")) + && !matches!(name, "radroots_cli" | "radroots_cli_xtask") + { + if package["source"] != resolved || package["version"] != fields["version"] { + return Err(()); + } + shared_count += 1; + } + } + if shared_count == 0 { + return Err(()); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + fn fixture() -> (String, Value) { + let lock = include_str!("../../../radroots.lib.source-lock.v1.toml"); + let lock = lock.replace( + fields(lock.as_bytes()).expect("canonical lock")["lockfile_sha256"], + &hex::encode(Sha256::digest(b"synthetic cargo lock")), + ); + let revision = fields(lock.as_bytes()).expect("canonical lock")["revision"]; + let source = format!("git+https://github.com/radrootslabs/lib.git?rev={revision}"); + let metadata = json!({"packages": [ + {"name": "radroots_cli", "dependencies": [{"name": "radroots", "source": source, "req": "=0.1.0-alpha", "rename": null}]}, + {"name": "radroots", "source": format!("{source}#{revision}"), "version": "0.1.0-alpha"} + ]}); + (lock, metadata) + } + + #[test] + fn exact_lock_and_dependency_inputs_pass() { + let (lock, metadata) = fixture(); + assert!(validate(lock.as_bytes(), b"synthetic cargo lock", &metadata).is_ok()); + } + + #[test] + fn stale_lock_bytes_fail_even_when_revision_agrees() { + let (lock, metadata) = fixture(); + assert!(validate(lock.as_bytes(), b"changed cargo lock", &metadata).is_err()); + } + + #[test] + fn local_or_mismatched_resolved_shared_sources_fail() { + let (lock, metadata) = fixture(); + for source in [ + Value::Null, + json!("git+https://github.com/radrootslabs/lib.git?branch=master"), + ] { + let mut changed = metadata.clone(); + changed["packages"][1]["source"] = source; + assert!(validate(lock.as_bytes(), b"synthetic cargo lock", &changed).is_err()); + } + } + + #[test] + fn manifest_revision_or_version_drift_fails() { + let (lock, metadata) = fixture(); + for (field, value) in [ + ("source", "git+https://example.invalid/lib"), + ("req", "^0.1"), + ] { + let mut changed = metadata.clone(); + changed["packages"][0]["dependencies"][0][field] = json!(value); + assert!(validate(lock.as_bytes(), b"synthetic cargo lock", &changed).is_err()); + } + } + + #[test] + fn duplicate_unknown_missing_or_unsafe_lock_fields_fail() { + let (lock, metadata) = fixture(); + for changed in [ + format!("{lock}revision = \"{}\"\n", "a".repeat(40)), + format!("{lock}extra = \"value\"\n"), + lock.lines().skip(1).collect::<Vec<_>>().join("\n"), + lock.replace("lockfile = \"Cargo.lock\"", "lockfile = \"../Cargo.lock\""), + ] { + assert!(validate(changed.as_bytes(), b"synthetic cargo lock", &metadata).is_err()); + } + } +}