//! The first-party keys, and the content address of every registry package flowRS's hosts //! carry, computed from the very files cargo compiles into them. //! //! The keys: `FIRST_PARTY` is the id of each public key file in `registry/keys/`, so a host //! trusts exactly what the repository publishes. Replacing the key is replacing the file. //! //! The packages: //! //! A host admits a package it carries only when the index holds a signed record for the //! exact hash of that package's tree. The hash is taken here, at build time, from the tree //! on disk ([`archive::read_tree`], the same reading `flowrsc pkg pack` does), so it is the //! hash of the code this build compiles: edit one byte of a carried package without //! verifying it again and every host refuses it with a hash mismatch, which is the contract //! working. Each manifest is read here too and handed to the crate as JSON, so the browser //! needs no TOML reader. #[path = "src/archive.rs"] #[allow(dead_code)] mod archive; use std::path::Path; /// The packages the hosts carry, relative to the repository root. const CARRIED: &[&str] = &[ "crates/flowrs-node", "packages/dialect-opentap", "packages/dialect-teststand", "crates/flowrs-neuroviz", ]; /// The ids in `registry/keys/*.json`, in file order. A file whose id is not `v2:` and 64 hex /// digits stops the build; the signature check itself holds the key to its id at run time. fn first_party(root: &Path) -> Vec { let dir = root.join("registry/keys"); println!("cargo:rerun-if-changed={}", dir.display()); let mut files: Vec<_> = std::fs::read_dir(&dir) .unwrap_or_else(|e| panic!("{}: {e}", dir.display())) .flatten() .map(|e| e.path()) .filter(|p| p.extension().is_some_and(|x| x == "json")) .collect(); files.sort(); files .iter() .map(|p| { let v: serde_json::Value = serde_json::from_str( &std::fs::read_to_string(p).unwrap_or_else(|e| panic!("{}: {e}", p.display())), ) .unwrap_or_else(|e| panic!("{}: {e}", p.display())); let id = v["id"].as_str().unwrap_or_default().to_string(); let hex = id.strip_prefix("v2:").unwrap_or_default(); assert!( hex.len() == 64 && hex.bytes().all(|b| b.is_ascii_hexdigit()), "{}: `{id}` is not a key id", p.display() ); id }) .collect() } fn main() { println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=src/archive.rs"); let here = std::env::var("CARGO_MANIFEST_DIR").expect("cargo sets CARGO_MANIFEST_DIR"); let root = Path::new(&here) .parent() .and_then(Path::parent) .expect("the crate is at /crates/flowrs-registry"); let out_dir = std::env::var("OUT_DIR").expect("cargo sets OUT_DIR"); let ids = first_party(root); let mut keys = String::from( "/// The first-party keys: the id of each public key file in `registry/keys/`.\n\ pub const FIRST_PARTY: &[&str] = &[\n", ); for id in &ids { keys.push_str(&format!(" {id:?},\n")); } keys.push_str("];\n"); std::fs::write(Path::new(&out_dir).join("first_party.rs"), keys).expect("OUT_DIR is writable"); // Only `carried` reads the table below, and only a host that carries packages needs their // trees to exist: the public engine, which has `flowrsc pkg` but carries none, builds // without them. if std::env::var_os("CARGO_FEATURE_CARRIED").is_none() { return; } let mut out = String::from( "/// Generated by build.rs: each carried package's directory, content address and\n\ /// manifest (as JSON).\n\ pub const CARRIED: &[Carried] = &[\n", ); for rel in CARRIED.iter().copied() { let dir = root.join(rel); println!("cargo:rerun-if-changed={}", dir.display()); let entries = archive::read_tree(&dir).unwrap_or_else(|e| panic!("{rel}: {e}")); let bytes = archive::encode(&entries).unwrap_or_else(|e| panic!("{rel}: {e}")); let hash = archive::hash(&bytes); let toml_text = std::fs::read_to_string(dir.join("flowrs-package.toml")) .unwrap_or_else(|e| panic!("{rel}/flowrs-package.toml: {e}")); let value: toml::Value = toml::from_str(&toml_text).unwrap_or_else(|e| panic!("{rel}/flowrs-package.toml: {e}")); let json = serde_json::to_string(&value).expect("a TOML value is JSON"); out.push_str(&format!( " Carried {{ dir: {rel:?}, hash: {hash:?}, manifest: {json:?} }},\n" )); } out.push_str("];\n"); let dest = Path::new(&out_dir).join("carried.rs"); std::fs::write(&dest, out).expect("OUT_DIR is writable"); }