canic-host 0.100.19

Host-side App build, Fleet install, deployment, and release-set library for Canic workspaces
Documentation
use super::*;

#[test]
fn install_truth_gate_persists_machine_readable_receipt() {
    let root = temp_dir("canic-install-truth-receipt-json");
    let config_path = root.join("apps/demo/canic.toml");
    fs::create_dir_all(config_path.parent().expect("config parent")).expect("create config dir");
    fs::write(
        &config_path,
        demo_config_source(
            r"

",
        ),
    )
    .expect("write config");
    write_wasm_gz_artifact(&root, "root", b"root-artifact");
    write_local_network_authority(&root, "local");

    let options = local_demo_install_options(&root);

    let check = current_install_deployment_truth_check_at(
        &options,
        &root,
        &root,
        &config_path,
        "demo",
        "2026-05-22T00:00:00Z".to_string(),
    )
    .expect("deployment truth check");
    let receipt = install_deployment_truth_gate_receipt(
        &check,
        "unix:1770000000".to_string(),
        vec![artifact_gate_phase_receipt(
            &check,
            "unix:1770000000",
            Some("unix:1770000001".to_string()),
        )],
        artifact_gate_role_phase_receipts(&check),
    );

    let fleet = sample_fleet_key();
    let path = write_install_deployment_truth_receipt(&root, fleet, &receipt)
        .expect("write deployment truth receipt");
    let expected_path = install_deployment_truth_receipt_path(&root, fleet, &receipt);

    assert_eq!(path, expected_path);
    assert_eq!(
        path.parent(),
        Some(sample_fleet_receipt_dir(&root).as_path())
    );
    assert!(
        path.file_name()
            .and_then(|name| name.to_str())
            .is_some_and(|name| {
                !name.contains(':')
                    && Path::new(name)
                        .extension()
                        .is_some_and(|ext| ext.eq_ignore_ascii_case("json"))
            }),
        "unexpected receipt path: {}",
        path.display()
    );
    let decoded: DeploymentReceiptV1 =
        serde_json::from_slice(&fs::read(&path).expect("read receipt")).expect("decode receipt");
    assert_eq!(decoded, receipt);

    fs::remove_dir_all(root).expect("clean temp dir");
}

#[test]
fn install_truth_phase_receipt_records_emit_manifest_evidence() {
    let root = temp_dir("canic-install-truth-emit-manifest-receipt");
    let config_path = root.join("apps/demo/canic.toml");
    fs::create_dir_all(config_path.parent().expect("config parent")).expect("create config dir");
    fs::write(
        &config_path,
        r#"
controllers = []
[app]
name = "demo"
init_mode = "enabled"


[roles.root]
kind = "root"
package = "root"

[roles.app]
kind = "canister"
package = "app"

[roles.project_registry]
kind = "canister"
package = "project_registry"

[roles.oracle_pokemon]
kind = "canister"
package = "oracle_pokemon"

[roles.user_hub]
kind = "canister"
package = "user_hub"

[roles.user_shard]
kind = "canister"
package = "user_shard"

[roles.scale_hub]
kind = "canister"
package = "scale_hub"

[roles.scale_replica]
kind = "canister"
package = "scale"

[roles.role_baseline]
kind = "canister"
package = "role_baseline"

[roles.worker]
kind = "canister"
package = "worker"
[app.whitelist]


"#,
    )
    .expect("write config");
    write_wasm_gz_artifact(&root, "root", b"root-artifact");
    write_local_network_authority(&root, "local");

    let options = InstallRootOptions {
        root_canister: "root".to_string(),
        root_build_target: "root".to_string(),
        environment: "local".to_string(),
        fleet_name: "demo".to_string(),
        icp_root: Some(root.clone()),
        build_profile: Some(CanisterBuildProfile::Fast),
        config_path: Some("apps/demo/canic.toml".to_string()),
        fleet_install_input_path: None,
        expected_app: Some("demo".to_string()),
        interactive_config_selection: false,
        deployment_plan_override: None,
    };
    let check = current_install_deployment_truth_check_at(
        &options,
        &root,
        &root,
        &config_path,
        "demo",
        "2026-05-22T00:00:00Z".to_string(),
    )
    .expect("deployment truth check");

    let receipt = install_deployment_truth_phase_receipt(
        &check,
        InstallPhaseLabel::EMIT_MANIFEST,
        "unix:1770000002".to_string(),
        Some("unix:1770000003".to_string()),
        "emit root release-set manifest",
        ObservationStatusV1::Observed,
        vec!["manifest_path:/tmp/manifest.json".to_string()],
    );

    assert_eq!(
        receipt.operation_status,
        DeploymentExecutionStatusV1::Complete
    );
    assert_eq!(receipt.operation_id, "local:local:demo:check:emit_manifest");
    assert_eq!(receipt.phase_receipts.len(), 1);
    assert_eq!(receipt.phase_receipts[0].phase, "emit_manifest");
    assert_eq!(
        receipt.phase_receipts[0].verified_postcondition.status,
        ObservationStatusV1::Observed
    );
    assert_eq!(
        receipt.phase_receipts[0].verified_postcondition.evidence,
        vec!["manifest_path:/tmp/manifest.json".to_string()]
    );

    fs::remove_dir_all(root).expect("clean temp dir");
}

#[test]
fn install_truth_completed_phase_receipt_records_pre_gate_evidence() {
    let (root, check) = demo_install_deployment_truth_check("canic-install-truth-pre-gate-phase");
    let execution_context = current_install_execution_context(&root, &root, "local");
    let scope = InstallReceiptScope {
        icp_root: &root,
        fleet: sample_fleet_key(),
        check: &check,
        execution_context: Some(&execution_context),
    };

    let path = write_completed_install_phase_receipt(
        scope,
        CompletedInstallPhase {
            phase: InstallPhaseLabel::BUILD_ARTIFACTS,
            attempted_action: "build configured install targets",
            started_at: "unix:1770000004".to_string(),
            finished_at: Some("unix:1770000005".to_string()),
            evidence: vec!["build_target:root".to_string()],
            role_names: vec!["root".to_string()],
        },
    )
    .expect("write completed phase receipt");
    let receipt: DeploymentReceiptV1 =
        serde_json::from_slice(&fs::read(path).expect("read receipt")).expect("decode receipt");

    assert_eq!(
        receipt.operation_id,
        "local:local:demo:check:build_artifacts"
    );
    assert_eq!(
        receipt.operation_status,
        DeploymentExecutionStatusV1::Complete
    );
    assert_eq!(receipt.phase_receipts[0].phase, "build_artifacts");
    assert_eq!(
        receipt.phase_receipts[0].verified_postcondition.evidence,
        vec!["build_target:root".to_string()]
    );
    assert_eq!(receipt.role_phase_receipts.len(), 1);
    assert_eq!(receipt.role_phase_receipts[0].role, "root");
    assert_eq!(receipt.role_phase_receipts[0].phase, "build_artifacts");
    assert_eq!(
        receipt.role_phase_receipts[0].result,
        crate::deployment_truth::RolePhaseResultV1::Applied
    );
    let execution_context = receipt
        .execution_context
        .expect("completed phase receipt should include execution context");
    assert_eq!(
        execution_context.backend,
        crate::deployment_truth::DeploymentExecutorBackendV1::CurrentCli
    );
    assert!(
        execution_context
            .artifact_roots
            .iter()
            .any(|root| { root.ends_with(".icp/local/canisters") })
    );

    fs::remove_dir_all(root).expect("clean temp dir");
}

#[test]
fn install_truth_latest_receipt_uses_newest_persisted_receipt() {
    let root = temp_dir("canic-install-truth-latest-receipt");
    let receipt_dir = sample_fleet_receipt_dir(&root);
    fs::create_dir_all(&receipt_dir).expect("create receipt dir");
    let older = receipt_dir.join("unix_100-local_demo_check_materialize_artifacts.json");
    let newer = receipt_dir.join("unix_200-local_demo_check_materialize_artifacts.json");
    let ignored = receipt_dir.join("unix_300-local_demo_check_materialize_artifacts.txt");
    fs::write(&older, "{}").expect("write older receipt");
    fs::write(&newer, "{}").expect("write newer receipt");
    fs::write(ignored, "{}").expect("write ignored file");

    let latest = latest_deployment_truth_receipt_path_from_root(&root, sample_fleet_key())
        .expect("latest receipt")
        .expect("receipt exists");

    assert_eq!(latest, newer);

    fs::remove_dir_all(root).expect("clean temp dir");
}