rivox 1.0.0

Universal polyglot build coordination layer for Python, Rust, and Node monorepos
Documentation
use sha2::{Digest, Sha256};

/// Derives a deterministic content-addressed cache key for a specific package node
/// based strictly on its identity, target triple, and the Merkle root of its transitive dependencies.
/// This prevents global lockfile changes from invalidating unrelated package cache keys (RFC-001).
pub fn derive_subtree_cache_key(
    ecosystem: &str,
    package_name: &str,
    resolved_version: &str,
    transitive_dep_hashes: &[String],
    target_arch_triple: &str,
) -> String {
    let mut sorted_deps = transitive_dep_hashes.to_vec();
    sorted_deps.sort();

    let mut merkle_hasher = Sha256::new();
    for dep_hash in &sorted_deps {
        merkle_hasher.update(dep_hash.as_bytes());
    }
    let merkle_root = hex::encode(merkle_hasher.finalize());

    let raw = format!(
        "{}:{}:{}:{}:{}",
        ecosystem, package_name, resolved_version, merkle_root, target_arch_triple
    );

    let mut hasher = Sha256::new();
    hasher.update(raw.as_bytes());
    format!("sha256:{}", hex::encode(hasher.finalize()))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_subtree_cache_key_determinism() {
        let deps = vec!["sha256:dep1".to_string(), "sha256:dep2".to_string()];
        let key1 = derive_subtree_cache_key(
            "python",
            "torch",
            "2.1.0",
            &deps,
            "x86_64-unknown-linux-gnu",
        );
        let key2 = derive_subtree_cache_key(
            "python",
            "torch",
            "2.1.0",
            &deps,
            "x86_64-unknown-linux-gnu",
        );
        assert_eq!(key1, key2);
        assert!(key1.starts_with("sha256:"));
    }
}