canic-host 0.70.3

Host-side build, install, deployment, and fleet-template library for Canic workspaces
Documentation
use super::deployment_truth_gate::deployment_truth_finding_label;
use super::phase_receipts::receipt_with_execution_context;
use super::receipt_io::write_install_deployment_truth_receipt;
use crate::deployment_truth::{
    CurrentCliDeploymentExecutor, DeploymentCheckV1, DeploymentCommandResultV1,
    DeploymentExecutionContextV1, DeploymentExecutionPreflightV1, DeploymentExecutionStatusV1,
    ObservationStatusV1, deployment_execution_preflight_from_check,
    deployment_receipt_from_check_with_status, phase_receipt,
    validate_deployment_execution_preflight_for_check,
};
use std::path::{Path, PathBuf};

pub(super) fn write_current_install_execution_preflight_receipt(
    icp_root: &Path,
    network: &str,
    deployment_name: &str,
    check: &DeploymentCheckV1,
    execution_context: &DeploymentExecutionContextV1,
) -> Result<PathBuf, Box<dyn std::error::Error>> {
    let started_at = super::current_unix_timestamp_label()?;
    let executor = CurrentCliDeploymentExecutor::new(
        execution_context.workspace_root.clone(),
        execution_context.icp_root.clone(),
        execution_context.artifact_roots.clone(),
    );
    let preflight = deployment_execution_preflight_from_check(
        check,
        &executor,
        super::CURRENT_INSTALL_REQUIRED_CAPABILITIES,
    );
    validate_deployment_execution_preflight_for_check(check, &preflight)?;
    let blockers = preflight.blockers.clone();
    let (operation_status, command_result) = if blockers.is_empty() {
        (
            DeploymentExecutionStatusV1::Complete,
            DeploymentCommandResultV1::Succeeded,
        )
    } else {
        (
            DeploymentExecutionStatusV1::FailedBeforeMutation,
            DeploymentCommandResultV1::Failed {
                code: "execution_preflight_blocked".to_string(),
                message: "deployment execution preflight blocked current install".to_string(),
            },
        )
    };
    let finished_at = super::current_unix_timestamp_label()?;
    let receipt = receipt_with_execution_context(
        deployment_receipt_from_check_with_status(
            check,
            format!("{}:execution_preflight", check.check_id),
            operation_status,
            started_at.clone(),
            Some(finished_at.clone()),
            vec![phase_receipt(
                "execution_preflight",
                started_at,
                Some(finished_at),
                "validate deployment plan, authority, and executor capability readiness",
                ObservationStatusV1::Observed,
                current_install_execution_preflight_evidence(&preflight),
            )],
            Vec::new(),
            command_result,
        ),
        execution_context,
    );
    let path =
        write_install_deployment_truth_receipt(icp_root, network, deployment_name, &receipt)?;
    println!("Deployment truth receipt JSON: {}", path.display());
    if !blockers.is_empty() {
        let details = blockers
            .iter()
            .map(deployment_truth_finding_label)
            .collect::<Vec<_>>()
            .join("; ");
        return Err(format!("deployment execution preflight blocked install: {details}").into());
    }
    Ok(path)
}

fn current_install_execution_preflight_evidence(
    preflight: &DeploymentExecutionPreflightV1,
) -> Vec<String> {
    let mut evidence = vec![
        format!("execution_preflight_status:{:?}", preflight.status),
        format!("authority_plan:{}", preflight.authority_plan_id),
        format!("planned_phases:{}", preflight.planned_phases.len()),
        format!(
            "required_capabilities:{}",
            preflight.required_capabilities.len()
        ),
        format!(
            "missing_capabilities:{}",
            preflight.missing_capabilities.len()
        ),
        format!("blockers:{}", preflight.blockers.len()),
    ];
    evidence.extend(
        preflight
            .missing_capabilities
            .iter()
            .map(|capability| format!("missing_capability:{capability:?}")),
    );
    evidence.extend(
        preflight
            .blockers
            .iter()
            .map(|finding| format!("blocker:{}:{}", finding.code, finding.message)),
    );
    evidence
}