canic-core 0.110.15

Canic — a canister orchestration and management toolkit for the Internet Computer
Documentation
//! Module: workflow::runtime
//!
//! Responsibility: coordinate runtime startup services and memory initialization.
//! Does not own: lifecycle adapters, endpoint authorization, or stable schemas.
//! Boundary: lifecycle workflows call runtime startup after environment restore.

pub mod async_job;
pub mod auth;
pub mod authority_restore;
pub mod cycles;
pub mod fleet_activation;
pub mod install;
pub mod intent;
pub mod log;
mod nonroot;
pub mod observability;
mod root;
pub mod timer;

#[cfg(any(test, feature = "auth-local-application-authorization"))]
use crate::ops::storage::auth::LocalApplicationAuthorizationStateOps;
use crate::ops::storage::{
    icp_refill::IcpRefillStoreOps,
    intent::{IntentStoreOps, ReceiptBackedIntentOps},
};
use crate::{
    InternalError,
    log::Topic,
    ops::{
        ic::IcOps,
        runtime::{env::EnvOps, memory::MemoryRegistryOps},
    },
    workflow,
};

pub use nonroot::{
    init_local_nonroot_canister, init_local_nonroot_canister_with_automatic_topup,
    init_nonroot_canister, init_wasm_store_canister,
    post_upgrade_local_nonroot_canister_after_memory_init,
    post_upgrade_local_nonroot_canister_with_automatic_topup_after_memory_init,
    post_upgrade_nonroot_canister_after_memory_init,
    post_upgrade_nonroot_canister_with_automatic_topup_after_memory_init,
};
pub use root::{init_root_canister, post_upgrade_root_canister_after_memory_init};

///
/// RuntimeWorkflow
/// Coordinates periodic background services (timers) for Canic canisters.
///

pub struct RuntimeWorkflow;

impl RuntimeWorkflow {
    /// Start fixed runtime consumers shared by non-root profiles.
    pub fn start_all() -> Result<(), InternalError> {
        workflow::fixture_provisioning::timer::FixtureImportTimer::start()?;
        workflow::runtime::log::LogRetentionWorkflow::start()?;
        workflow::runtime::intent::IntentCleanupWorkflow::start()?;
        workflow::metrics::publication::timer::PublicSamplingTimer::start()?;
        Ok(())
    }

    /// Start the shared consumers plus the compile-selected automatic top-up owner.
    pub fn start_all_with_automatic_topup() -> Result<(), InternalError> {
        Self::start_all()?;
        workflow::runtime::cycles::CycleWorkflow::start()
    }

    /// Start timers that should run only on root canisters.
    pub fn start_all_root() -> Result<(), InternalError> {
        EnvOps::require_root().map_err(|_err| InternalError::invariant())?;

        // Start shared runtime owners before root-only services.
        start_root_service(
            "log_retention",
            workflow::runtime::log::LogRetentionWorkflow::start(),
        )?;
        start_root_service(
            "intent_cleanup",
            workflow::runtime::intent::IntentCleanupWorkflow::start(),
        )?;

        start_root_service(
            "public_metrics",
            workflow::metrics::publication::timer::PublicSamplingTimer::start(),
        )?;

        // root-only services
        start_root_service("cycles", workflow::runtime::cycles::CycleWorkflow::start())?;
        #[cfg(any(test, feature = "auth-root-delegation-state"))]
        start_root_service(
            "issuer_renewal",
            workflow::runtime::auth::RuntimeAuthWorkflow::reconcile_root_issuer_renewal(),
        )?;
        Ok(())
    }
}

fn start_root_service(
    service: &str,
    result: Result<(), InternalError>,
) -> Result<(), InternalError> {
    if let Err(error) = &result {
        let message =
            format!("Root runtime service startup failed service={service} error={error}");
        IcOps::println(&message);
    }
    result
}

pub(super) fn log_memory_summary() {
    crate::log!(Topic::Memory, Info, "💾 memory.registry: bootstrapped");
}

fn init_post_upgrade_memory_registry() -> Result<(), InternalError> {
    MemoryRegistryOps::bootstrap_registry().map_err(|_err| InternalError::invariant())
}

pub fn init_memory_registry_post_upgrade() -> Result<(), InternalError> {
    init_post_upgrade_memory_registry()
}

pub(super) fn rebuild_derived_storage_indexes() -> Result<(), InternalError> {
    #[cfg(feature = "auth-delegated-token-issuer-state")]
    crate::ops::storage::auth::DelegatedTokenIssuerStateOps::restore();

    #[cfg(any(test, feature = "auth-local-application-authorization"))]
    {
        let application_session_restore_start = crate::perf::perf_counter();
        LocalApplicationAuthorizationStateOps::restore_application_session_state()
            .map_err(|_| InternalError::invariant())?;
        crate::perf::record_checkpoint(
            module_path!(),
            "application_session_restore",
            crate::perf::perf_counter().saturating_sub(application_session_restore_start),
        );
    }
    IntentStoreOps::rebuild_expiry_index()?;
    ReceiptBackedIntentOps::reconcile_receipt_indexes()?;
    let _receipt_capacity = ReceiptBackedIntentOps::receipt_capacity()?;

    Ok(())
}

pub(super) fn rebuild_root_derived_storage_indexes() -> Result<(), InternalError> {
    IcpRefillStoreOps::rebuild_indexes()?;
    rebuild_derived_storage_indexes()
}

pub(super) fn require_no_resumable_refill_for_upgrade() -> Result<(), InternalError> {
    validate_refill_upgrade_admission(IcpRefillStoreOps::resumable_operation_count())
}

const fn validate_refill_upgrade_admission(count: usize) -> Result<(), InternalError> {
    if count == 0 {
        return Ok(());
    }

    Err(InternalError::invariant())
}

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

    #[test]
    fn root_upgrade_accepts_terminal_refill_state() {
        validate_refill_upgrade_admission(0).expect("terminal refill state should permit upgrade");
    }

    #[test]
    fn root_upgrade_rejects_resumable_refill_state() {
        let error = validate_refill_upgrade_admission(1)
            .expect_err("resumable refill state must block upgrade");
        assert_eq!(error.code(), crate::diagnostics::codes::STATE_INVALID);
    }
}