canic-host 0.107.1

Host-side App build, Fleet install, deployment, and release-set library for Canic workspaces
Documentation
use super::*;
use sha2::{Digest, Sha256};
use std::fs;

fn entry(root: &std::path::Path, candid: &[u8]) -> RegistryEntry {
    let role = CanisterRole::from("app");
    let capabilities = BTreeSet::from([RoleCapabilityKey::Runtime]);
    let profile = derive_protocol_profile_hashes("0.103.0", &role, &capabilities, candid);
    let did = root.join(".icp/local/canisters/app/app.did");
    fs::create_dir_all(did.parent().expect("DID parent")).expect("create DID parent");
    fs::write(&did, candid).expect("write DID");
    RegistryEntry {
        pid: "rrkah-fqaaa-aaaaa-aaaaq-cai".to_string(),
        role: Some("app".to_string()),
        parent_pid: None,
        module_hash: None,
        protocol_binding: Some(RegistryProtocolBinding {
            release_identity: "0.103.0".to_string(),
            role,
            capabilities,
            candid_sha256: Sha256::digest(candid).into(),
            protocol_profile_digest: profile.protocol_profile_digest,
        }),
    }
}

#[test]
fn exact_registry_binding_resolves_before_transport() {
    let root = crate::test_support::temp_dir("canic-host-protocol-binding");
    let entry = entry(&root, b"service : {}\n");
    let resolved =
        resolve_registry_protocol_binding(&root, "local", &entry).expect("resolve exact binding");
    assert_eq!(resolved.binding, entry.protocol_binding.expect("binding"));
    fs::remove_dir_all(root).expect("remove temp root");
}

#[test]
fn missing_or_drifted_binding_fails_closed() {
    let root = crate::test_support::temp_dir("canic-host-protocol-binding-drift");
    let mut missing = entry(&root, b"service : {}\n");
    missing.protocol_binding = None;
    assert!(matches!(
        resolve_registry_protocol_binding(&root, "local", &missing),
        Err(ProtocolBindingError::MissingBinding { .. })
    ));

    let mut drifted = entry(&root, b"service : {}\n");
    drifted
        .protocol_binding
        .as_mut()
        .expect("binding")
        .candid_sha256 = [9; 32];
    assert!(matches!(
        resolve_registry_protocol_binding(&root, "local", &drifted),
        Err(ProtocolBindingError::CandidHashMismatch { .. })
    ));
    fs::remove_dir_all(root).expect("remove temp root");
}

#[test]
fn immutable_infrastructure_binding_reproduces_full_profile() {
    let root = crate::test_support::temp_dir("canic-host-infrastructure-protocol-binding");
    let candid = b"service : {}\n";
    let role = CanisterRole::from("fleet_coordinator");
    let capabilities = BTreeSet::from([RoleCapabilityKey::FleetCoordinator]);
    let profile = derive_protocol_profile_hashes("0.103.0", &role, &capabilities, candid);
    let did = root.join(".icp/local/canisters/fleet_coordinator/fleet_coordinator.did");
    fs::create_dir_all(did.parent().expect("DID parent")).expect("create DID parent");
    fs::write(&did, candid).expect("write DID");
    let artifact = crate::release_set::CanicInfrastructureArtifactEntry {
        role: crate::release_set::CanicInfrastructureRole::FleetCoordinator,
        package: "canic-fleet-coordinator".to_string(),
        protocol_release_identity: "0.103.0".to_string(),
        protocol_role: role,
        protocol_capabilities: capabilities,
        release_build_id: "01".repeat(32).parse().expect("release build"),
        wasm_relative_path: "coordinator.wasm".to_string(),
        wasm_size_bytes: 1,
        wasm_sha256_hex: "01".repeat(32),
        wasm_gz_relative_path: "coordinator.wasm.gz".to_string(),
        wasm_gz_size_bytes: 1,
        wasm_gz_sha256_hex: "02".repeat(32),
        candid_sha256: profile.candid_sha256,
        protocol_profile_digest: profile.protocol_profile_digest,
    };

    let resolved = resolve_infrastructure_protocol_binding(&root, "local", &artifact)
        .expect("resolve infrastructure binding");

    assert_eq!(resolved.binding().release_identity, "0.103.0");
    assert_eq!(resolved.candid_path(), did);
    fs::remove_dir_all(root).expect("remove temp root");
}

#[test]
fn fleet_subnet_root_artifact_selects_the_root_protocol_role() {
    let root = crate::test_support::temp_dir("canic-host-root-protocol-binding");
    let candid = b"service : {}\n";
    let role = CanisterRole::from("root");
    let capabilities = BTreeSet::from([RoleCapabilityKey::Root]);
    let profile = derive_protocol_profile_hashes("0.103.0", &role, &capabilities, candid);
    let did = root.join(".icp/local/canisters/root/root.did");
    fs::create_dir_all(did.parent().expect("DID parent")).expect("create DID parent");
    fs::write(&did, candid).expect("write DID");
    let artifact = crate::release_set::CanicInfrastructureArtifactEntry {
        role: crate::release_set::CanicInfrastructureRole::FleetSubnetRoot,
        package: "root-package".to_string(),
        protocol_release_identity: "0.103.0".to_string(),
        protocol_role: role,
        protocol_capabilities: capabilities,
        release_build_id: "01".repeat(32).parse().expect("release build"),
        wasm_relative_path: "root.wasm".to_string(),
        wasm_size_bytes: 1,
        wasm_sha256_hex: "01".repeat(32),
        wasm_gz_relative_path: "root.wasm.gz".to_string(),
        wasm_gz_size_bytes: 1,
        wasm_gz_sha256_hex: "02".repeat(32),
        candid_sha256: profile.candid_sha256,
        protocol_profile_digest: profile.protocol_profile_digest,
    };

    let resolved = resolve_infrastructure_protocol_binding(&root, "local", &artifact)
        .expect("resolve root infrastructure binding");

    assert_eq!(resolved.binding().role.as_str(), "root");
    assert_eq!(resolved.candid_path(), did);
    fs::remove_dir_all(root).expect("remove temp root");
}