canic-core 0.100.64

Canic — a canister orchestration and management toolkit for the Internet Computer
Documentation
//! Module: dto::fleet_subnet_root
//!
//! Responsibility: carry protected Fleet Subnet Root authority and controller lifecycle DTOs.
//! Does not own: validation, persistence, topology compilation, or lifecycle effects.
//! Boundary: lifecycle adapters pass init/command authority to workflow and return passive data.

use crate::{
    dto::fleet_registry::{FleetRegistryVersion, FleetSubnetRootStatus},
    ids::{ComponentTopologyDigest, FleetSubnetRootBinding, FleetSubnetRootReleaseSet, SubnetId},
};
use candid::{CandidType, Principal};
use serde::{Deserialize, Serialize};

///
/// FleetSubnetRootAuthority
///
/// Exact immutable root binding, initial release set, and installed module identity.
///

#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
pub struct FleetSubnetRootAuthority {
    pub binding: FleetSubnetRootBinding,
    pub initial_release_set: FleetSubnetRootReleaseSet,
    pub expected_module_hash: [u8; 32],
}

///
/// FleetSubnetRootCanisterSummary
///
/// Compact live inventory bound to one root's exact active Fleet Registry mirror.
///

#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct FleetSubnetRootCanisterSummary {
    pub fleet_registry: FleetRegistryVersion,
    pub placement_subnet: SubnetId,
    pub fleet_subnet_root: Principal,
    pub status: FleetSubnetRootStatus,
    pub infrastructure_canisters: u32,
    pub component_canisters: u32,
    pub total_canisters: u32,
}

///
/// FleetSubnetRootDrainingRequest
///
/// Controller command fencing new top-level Component allocation under exact active authority.
///

#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct FleetSubnetRootDrainingRequest {
    pub operation_id: [u8; 32],
    pub expected_registry: FleetRegistryVersion,
}

///
/// FleetSubnetRootDrainingStatusRequest
///
/// Read-only lookup key for one durable root-draining fence.
///

#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct FleetSubnetRootDrainingStatusRequest {
    pub operation_id: [u8; 32],
}

///
/// FleetSubnetRootDrainingResponse
///
/// Durable root-local admission cutoff and exact active authority frozen at that boundary.
///

#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct FleetSubnetRootDrainingResponse {
    pub operation_id: [u8; 32],
    pub fleet_subnet_root: Principal,
    pub placement_subnet: SubnetId,
    pub active_registry: FleetRegistryVersion,
    pub component_topology_digest: ComponentTopologyDigest,
    pub active_release_set: FleetSubnetRootReleaseSet,
    pub next_allocation_sequence: u64,
    pub reserved_component_instances: u32,
    pub committed_component_instances: u32,
    pub managed_descendants: u32,
    pub known_created_component_canisters: u32,
    pub root_registry_encoded_bytes: u64,
    pub started_at_ns: u64,
}

///
/// FleetSubnetRootInitArgs
///
/// Fresh-install authority plus the reinstall-local activation operation identity.
///

#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
pub struct FleetSubnetRootInitArgs {
    pub authority: FleetSubnetRootAuthority,
    pub install_id: [u8; 32],
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ids::{
        AppId, CanonicalNetworkId, FleetBinding, FleetCoordinatorBinding, FleetId, FleetKey,
        FleetRegistryAuthority,
    };

    #[test]
    fn canister_summary_and_draining_contracts_round_trip_through_candid() {
        let summary = FleetSubnetRootCanisterSummary {
            fleet_registry: FleetRegistryVersion {
                authority: FleetRegistryAuthority {
                    binding: FleetCoordinatorBinding {
                        fleet: FleetBinding {
                            fleet: FleetKey {
                                canonical_network_id: CanonicalNetworkId::ic_mainnet(),
                                fleet_id: FleetId::from_generated_bytes([1; 32]),
                            },
                            app: AppId::from("toko"),
                        },
                        coordinator_subnet: SubnetId::from_principal(Principal::from_slice(
                            &[2; 29],
                        )),
                        coordinator: Principal::from_slice(&[3; 29]),
                    },
                    epoch: 1,
                },
                revision: 4,
                content_hash: [5; 32],
            },
            placement_subnet: SubnetId::from_principal(Principal::from_slice(&[6; 29])),
            fleet_subnet_root: Principal::from_slice(&[7; 29]),
            status: FleetSubnetRootStatus::Active,
            infrastructure_canisters: 2,
            component_canisters: 3,
            total_canisters: 5,
        };
        let candid = candid::encode_one(&summary).expect("encode Canister summary");
        let decoded: FleetSubnetRootCanisterSummary =
            candid::decode_one(&candid).expect("decode Canister summary");

        assert_eq!(decoded, summary);

        let draining = FleetSubnetRootDrainingResponse {
            operation_id: [8; 32],
            fleet_subnet_root: summary.fleet_subnet_root,
            placement_subnet: summary.placement_subnet,
            active_registry: summary.fleet_registry,
            component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
            active_release_set: FleetSubnetRootReleaseSet {
                release_build_id: crate::ids::ReleaseBuildId::from_nonce(
                    crate::ids::ReleaseBuildNonce::from_random_bytes([10; 32]),
                ),
                manifest_digest: crate::ids::ReleaseSetDigest::from_bytes([11; 32]),
            },
            next_allocation_sequence: 12,
            reserved_component_instances: 13,
            committed_component_instances: 14,
            managed_descendants: 15,
            known_created_component_canisters: 16,
            root_registry_encoded_bytes: 17_000,
            started_at_ns: 18,
        };
        let request = FleetSubnetRootDrainingRequest {
            operation_id: draining.operation_id,
            expected_registry: draining.active_registry.clone(),
        };
        let status = FleetSubnetRootDrainingStatusRequest {
            operation_id: draining.operation_id,
        };
        let request_bytes = candid::encode_one(&request).expect("encode root draining request");
        let status_bytes = candid::encode_one(status).expect("encode root draining status");
        let response_bytes = candid::encode_one(&draining).expect("encode root draining response");
        assert_eq!(
            candid::decode_one::<FleetSubnetRootDrainingRequest>(&request_bytes)
                .expect("decode root draining request"),
            request
        );
        assert_eq!(
            candid::decode_one::<FleetSubnetRootDrainingStatusRequest>(&status_bytes)
                .expect("decode root draining status"),
            status
        );
        assert_eq!(
            candid::decode_one::<FleetSubnetRootDrainingResponse>(&response_bytes)
                .expect("decode root draining response"),
            draining
        );
    }
}