canic-host 0.70.12

Host-side build, install, deployment, and fleet-template library for Canic workspaces
Documentation
use super::super::super::*;
use std::collections::BTreeSet;

pub(super) fn required_lifecycle_controllers(
    control_class: CanisterControlClassV1,
    expected_controllers: &[String],
) -> Vec<String> {
    match control_class {
        CanisterControlClassV1::DeploymentControlled
        | CanisterControlClassV1::JointlyControlled => sorted_unique(expected_controllers.to_vec()),
        CanisterControlClassV1::CanicManagedPool
        | CanisterControlClassV1::ExternallyImported
        | CanisterControlClassV1::UserControlled
        | CanisterControlClassV1::UnknownUnsafe => Vec::new(),
    }
}

pub(super) fn external_lifecycle_controllers(
    control_class: CanisterControlClassV1,
    observed_controllers: &[String],
    required_controllers: &[String],
) -> Vec<String> {
    match control_class {
        CanisterControlClassV1::DeploymentControlled | CanisterControlClassV1::UnknownUnsafe => {
            Vec::new()
        }
        CanisterControlClassV1::JointlyControlled => {
            let required = required_controllers.iter().collect::<BTreeSet<_>>();
            sorted_unique(
                observed_controllers
                    .iter()
                    .filter(|controller| !required.contains(controller))
                    .cloned()
                    .collect(),
            )
        }
        CanisterControlClassV1::CanicManagedPool
        | CanisterControlClassV1::ExternallyImported
        | CanisterControlClassV1::UserControlled => sorted_unique(observed_controllers.to_vec()),
    }
}

pub(super) fn lifecycle_consent_requirements(
    control_class: CanisterControlClassV1,
    external_controllers: &[String],
) -> Vec<ConsentRequirementV1> {
    if !lifecycle_external_action_required(control_class) {
        return Vec::new();
    }
    vec![ConsentRequirementV1 {
        consent_subject_kind: consent_subject_kind(control_class),
        required_principals: sorted_unique(external_controllers.to_vec()),
        required_controller_set_digest: Some(stable_json_sha256_hex(&external_controllers)),
        consent_channel_kind: consent_channel_kind(control_class),
        required_action: required_consent_action(control_class),
    }]
}

pub(super) const fn lifecycle_mode(control_class: CanisterControlClassV1) -> LifecycleModeV1 {
    match control_class {
        CanisterControlClassV1::DeploymentControlled => LifecycleModeV1::DirectDeploymentAuthority,
        CanisterControlClassV1::CanicManagedPool => LifecycleModeV1::DelegatedInstallRequired,
        CanisterControlClassV1::ExternallyImported | CanisterControlClassV1::UserControlled => {
            LifecycleModeV1::ExternalCompletionOnly
        }
        CanisterControlClassV1::JointlyControlled => LifecycleModeV1::ProposalRequired,
        CanisterControlClassV1::UnknownUnsafe => LifecycleModeV1::UnknownUnsafeBlocked,
    }
}

pub(super) fn lifecycle_blockers(control_class: CanisterControlClassV1) -> Vec<String> {
    if control_class == CanisterControlClassV1::UnknownUnsafe {
        vec!["unknown unsafe controller state blocks lifecycle action".to_string()]
    } else {
        Vec::new()
    }
}

pub(super) fn lifecycle_warnings(control_class: CanisterControlClassV1) -> Vec<String> {
    match control_class {
        CanisterControlClassV1::CanicManagedPool => {
            vec!["pool-aware lifecycle policy is required before mutation".to_string()]
        }
        CanisterControlClassV1::ExternallyImported => {
            vec!["external controller action or verification is required".to_string()]
        }
        CanisterControlClassV1::JointlyControlled => {
            vec!["joint controller consent or delegation is required".to_string()]
        }
        CanisterControlClassV1::UserControlled => {
            vec!["user or delegated lifecycle action is required".to_string()]
        }
        CanisterControlClassV1::DeploymentControlled | CanisterControlClassV1::UnknownUnsafe => {
            Vec::new()
        }
    }
}

pub(super) fn lifecycle_upgrade_modes(
    control_class: CanisterControlClassV1,
) -> Vec<LifecycleUpgradeModeV1> {
    match control_class {
        CanisterControlClassV1::DeploymentControlled => vec![
            LifecycleUpgradeModeV1::DirectByDeploymentAuthority,
            LifecycleUpgradeModeV1::VerifyExternalCompletion,
        ],
        CanisterControlClassV1::CanicManagedPool
        | CanisterControlClassV1::ExternallyImported
        | CanisterControlClassV1::JointlyControlled
        | CanisterControlClassV1::UserControlled => vec![
            LifecycleUpgradeModeV1::ExternalProposal,
            LifecycleUpgradeModeV1::ExternalExecution,
            LifecycleUpgradeModeV1::VerifyExternalCompletion,
            LifecycleUpgradeModeV1::ObserveOnly,
        ],
        CanisterControlClassV1::UnknownUnsafe => vec![LifecycleUpgradeModeV1::Blocked],
    }
}

pub(super) fn lifecycle_verification_requirements(
    verifier_required: bool,
) -> Vec<LifecycleVerificationRequirementV1> {
    let mut requirements = vec![
        LifecycleVerificationRequirementV1::LiveInventory,
        LifecycleVerificationRequirementV1::ControllerObservation,
        LifecycleVerificationRequirementV1::ModuleHash,
        LifecycleVerificationRequirementV1::CanonicalEmbeddedConfig,
    ];
    if verifier_required {
        requirements.push(LifecycleVerificationRequirementV1::ProtectedCallReadiness);
    }
    requirements
}

pub(super) const fn lifecycle_external_action_required(
    control_class: CanisterControlClassV1,
) -> bool {
    matches!(
        control_class,
        CanisterControlClassV1::CanicManagedPool
            | CanisterControlClassV1::ExternallyImported
            | CanisterControlClassV1::JointlyControlled
            | CanisterControlClassV1::UserControlled
    )
}

pub(super) fn lifecycle_reason(control_class: CanisterControlClassV1) -> String {
    match control_class {
        CanisterControlClassV1::DeploymentControlled => {
            "deployment authority can execute lifecycle directly".to_string()
        }
        CanisterControlClassV1::CanicManagedPool => {
            "Canic-managed pool lifecycle requires pool-aware external action".to_string()
        }
        CanisterControlClassV1::ExternallyImported => {
            "externally imported canister requires external controller action".to_string()
        }
        CanisterControlClassV1::JointlyControlled => {
            "jointly controlled canister requires non-deployment-controller consent".to_string()
        }
        CanisterControlClassV1::UserControlled => {
            "user-controlled canister requires user or delegated lifecycle action".to_string()
        }
        CanisterControlClassV1::UnknownUnsafe => {
            "unknown or unsafe controller state blocks lifecycle action".to_string()
        }
    }
}

pub(super) const fn required_external_action(lifecycle_mode: LifecycleModeV1) -> &'static str {
    match lifecycle_mode {
        LifecycleModeV1::DirectDeploymentAuthority => "none",
        LifecycleModeV1::ProposalRequired => "proposal_and_consent",
        LifecycleModeV1::DelegatedInstallRequired => "delegated_install_or_pool_policy",
        LifecycleModeV1::ExternalCompletionOnly => "external_controller_execution",
        LifecycleModeV1::VerifyOnly => "verify_external_completion",
        LifecycleModeV1::MustNotTouch | LifecycleModeV1::UnknownUnsafeBlocked => "blocked",
    }
}

pub(super) fn sorted_unique(values: Vec<String>) -> Vec<String> {
    values
        .into_iter()
        .collect::<BTreeSet<_>>()
        .into_iter()
        .collect()
}

const fn consent_subject_kind(control_class: CanisterControlClassV1) -> ConsentSubjectKindV1 {
    match control_class {
        CanisterControlClassV1::CanicManagedPool => ConsentSubjectKindV1::ProjectHub,
        CanisterControlClassV1::ExternallyImported | CanisterControlClassV1::JointlyControlled => {
            ConsentSubjectKindV1::CustomerController
        }
        CanisterControlClassV1::UserControlled => ConsentSubjectKindV1::UserPrincipal,
        CanisterControlClassV1::DeploymentControlled | CanisterControlClassV1::UnknownUnsafe => {
            ConsentSubjectKindV1::UnknownExternalController
        }
    }
}

const fn consent_channel_kind(control_class: CanisterControlClassV1) -> ConsentChannelKindV1 {
    match control_class {
        CanisterControlClassV1::CanicManagedPool => ConsentChannelKindV1::DelegatedInstall,
        CanisterControlClassV1::ExternallyImported
        | CanisterControlClassV1::JointlyControlled
        | CanisterControlClassV1::UserControlled => ConsentChannelKindV1::GeneratedCommand,
        CanisterControlClassV1::DeploymentControlled | CanisterControlClassV1::UnknownUnsafe => {
            ConsentChannelKindV1::OutOfBand
        }
    }
}

const fn required_consent_action(
    control_class: CanisterControlClassV1,
) -> ExternalUpgradeAuthorizationModeV1 {
    match control_class {
        CanisterControlClassV1::JointlyControlled => {
            ExternalUpgradeAuthorizationModeV1::ConsentForDirectInstall
        }
        CanisterControlClassV1::CanicManagedPool => {
            ExternalUpgradeAuthorizationModeV1::DelegatedInstallAuthority
        }
        CanisterControlClassV1::ExternallyImported | CanisterControlClassV1::UserControlled => {
            ExternalUpgradeAuthorizationModeV1::ExternalControllerExecution
        }
        CanisterControlClassV1::DeploymentControlled | CanisterControlClassV1::UnknownUnsafe => {
            ExternalUpgradeAuthorizationModeV1::ObserveAndVerifyOnly
        }
    }
}