use crate::{
cdk::types::Cycles,
config::schema::ComponentChildKind,
dto::{
fleet_registry::{FleetDirectorySnapshot, FleetRegistryVersion},
root_store::RootStoreBootstrapRequest,
},
ids::{
CanisterRole, ComponentBinding, ComponentChildBinding, ComponentInstanceId,
ComponentSpecId, ComponentTopologyDigest, FleetSubnetRootReleaseSet,
ManagedCanisterBinding,
},
};
use candid::{CandidType, Principal};
use serde::{Deserialize, Serialize};
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentRegistryPreparationRequest {
pub store_bootstrap: RootStoreBootstrapRequest,
pub expected_fleet_registry: FleetRegistryVersion,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentInitialInventoryStatus {
pub fleet_activation_operation_id: [u8; 32],
pub component_count: u32,
pub inventory_hash: [u8; 32],
pub sealed_at_ns: u64,
pub directories_converged: bool,
pub root_runtime_activated: bool,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentRegistryStatusResponse {
pub fleet_subnet_root: Principal,
pub prepared_against_registry: FleetRegistryVersion,
pub release_set: FleetSubnetRootReleaseSet,
pub component_topology_digest: ComponentTopologyDigest,
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 encoded_bytes: u64,
pub initial_inventory: Option<RootComponentInitialInventoryStatus>,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentAllocationRequest {
pub operation_id: [u8; 32],
pub component_spec: ComponentSpecId,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentAllocationStatusRequest {
pub operation_id: [u8; 32],
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentChildAllocationRequest {
pub operation_id: [u8; 32],
pub component: ComponentInstanceId,
pub expected_registry: ComponentRegistryHead,
pub child_role: CanisterRole,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentChildAllocationStatusRequest {
pub operation_id: [u8; 32],
pub component: ComponentInstanceId,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentSubtreeRemovalRequest {
pub operation_id: [u8; 32],
pub component: ComponentInstanceId,
pub target_canister_id: Principal,
pub expected_registry: ComponentRegistryHead,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentSubtreeRemovalAdvanceRequest {
pub operation_id: [u8; 32],
pub component: ComponentInstanceId,
pub expected_traversal_steps: u32,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentSubtreeRemovalStopPreparationRequest {
pub operation_id: [u8; 32],
pub component: ComponentInstanceId,
pub expected_traversal_steps: u32,
pub expected_leaf_canister_id: Principal,
pub expected_leaf_parent_canister_id: Principal,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentSubtreeRemovalStopRequest {
pub operation_id: [u8; 32],
pub component: ComponentInstanceId,
pub expected_traversal_steps: u32,
pub expected_leaf_canister_id: Principal,
pub expected_leaf_parent_canister_id: Principal,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentSubtreeRemovalDeletePreparationRequest {
pub operation_id: [u8; 32],
pub component: ComponentInstanceId,
pub expected_traversal_steps: u32,
pub expected_leaf_canister_id: Principal,
pub expected_leaf_parent_canister_id: Principal,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentSubtreeRemovalDeleteRequest {
pub operation_id: [u8; 32],
pub component: ComponentInstanceId,
pub expected_traversal_steps: u32,
pub expected_leaf_canister_id: Principal,
pub expected_leaf_parent_canister_id: Principal,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentSubtreeRemovalMembershipRemovalRequest {
pub operation_id: [u8; 32],
pub component: ComponentInstanceId,
pub expected_traversal_steps: u32,
pub expected_leaf_canister_id: Principal,
pub expected_leaf_parent_canister_id: Principal,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentSubtreeRemovalDirectorySynchronizationRequest {
pub operation_id: [u8; 32],
pub component: ComponentInstanceId,
pub expected_traversal_steps: u32,
pub expected_leaf_canister_id: Principal,
pub expected_leaf_parent_canister_id: Principal,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentSubtreeRemovalLeafFinalizationRequest {
pub operation_id: [u8; 32],
pub component: ComponentInstanceId,
pub expected_traversal_steps: u32,
pub expected_leaf_canister_id: Principal,
pub expected_leaf_parent_canister_id: Principal,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentSubtreeRemovalStatusRequest {
pub operation_id: [u8; 32],
pub component: ComponentInstanceId,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentDrainingRequest {
pub operation_id: [u8; 32],
pub component: ComponentInstanceId,
pub expected_registry: ComponentRegistryHead,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentDrainingStatusRequest {
pub operation_id: [u8; 32],
pub component: ComponentInstanceId,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentQuiescenceRequest {
pub operation_id: [u8; 32],
pub component: ComponentInstanceId,
pub expected_registry: ComponentRegistryHead,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentQuiescenceStatusRequest {
pub operation_id: [u8; 32],
pub component: ComponentInstanceId,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentDrainingAdvanceRequest {
pub operation_id: [u8; 32],
pub component: ComponentInstanceId,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentFinalInventoryRequest {
pub operation_id: [u8; 32],
pub component: ComponentInstanceId,
pub expected_registry: ComponentRegistryHead,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentChildCreationRequest {
pub operation_id: [u8; 32],
pub component: ComponentInstanceId,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentChildInstallRequest {
pub operation_id: [u8; 32],
pub component: ComponentInstanceId,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentChildCommitRequest {
pub operation_id: [u8; 32],
pub component: ComponentInstanceId,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentChildDirectoryPreparationRequest {
pub operation_id: [u8; 32],
pub component: ComponentInstanceId,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentChildRuntimeActivationRequest {
pub operation_id: [u8; 32],
pub component: ComponentInstanceId,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentChildMembershipActivationRequest {
pub operation_id: [u8; 32],
pub component: ComponentInstanceId,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentCreationRequest {
pub operation_id: [u8; 32],
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentInstallRequest {
pub operation_id: [u8; 32],
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentCommitRequest {
pub operation_id: [u8; 32],
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentDirectoryPreparationRequest {
pub operation_id: [u8; 32],
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentRuntimeActivationRequest {
pub operation_id: [u8; 32],
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentMembershipActivationRequest {
pub operation_id: [u8; 32],
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum ComponentProvisioningOrigin {
FleetAdministrator { caller: Principal },
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum RootComponentAllocationPhase {
Reserved,
CreationIntent,
Created,
InstallIntent,
Installed,
Verified,
Committed,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[expect(
clippy::large_enum_variant,
reason = "wire phases retain complete inline receipts for deterministic Candid responses"
)]
pub enum RootComponentSubtreeRemovalPhase {
Fenced,
Traversing(RootComponentSubtreeRemovalNode),
LeafSelected(RootComponentSubtreeRemovalNode),
StopIntent(RootComponentSubtreeRemovalStopIntent),
Stopped(RootComponentSubtreeRemovalStoppedReceipt),
DeleteIntent(RootComponentSubtreeRemovalDeleteIntent),
Deleted(RootComponentSubtreeRemovalDeletedReceipt),
MembershipRemoved(RootComponentSubtreeRemovalMembershipRemovedReceipt),
DirectorySynchronized(RootComponentSubtreeRemovalDirectorySynchronizedReceipt),
Completed(RootComponentSubtreeRemovalCompletedReceipt),
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentSubtreeRemovalNode {
pub canister_id: Principal,
pub parent_canister_id: Principal,
pub role: CanisterRole,
pub kind: ComponentChildKind,
pub installed_artifact_hash: [u8; 32],
pub status: ComponentLifecycleStatus,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentSubtreeRemovalStopIntent {
pub leaf: RootComponentSubtreeRemovalNode,
pub controller: Principal,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentSubtreeRemovalStoppedReceipt {
pub stop: RootComponentSubtreeRemovalStopIntent,
pub observed_module_hash: [u8; 32],
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentSubtreeRemovalDeleteIntent {
pub stopped: RootComponentSubtreeRemovalStoppedReceipt,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentSubtreeRemovalDeletedReceipt {
pub deletion: RootComponentSubtreeRemovalDeleteIntent,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentSubtreeRemovalMembershipRemovedReceipt {
pub deleted: RootComponentSubtreeRemovalDeletedReceipt,
pub removed_from_registry: ComponentRegistryHead,
pub previous_descendant_content_hash: [u8; 32],
pub previous_committed_descendants: u32,
pub registry: ComponentRegistryHead,
pub descendant_content_hash: [u8; 32],
pub registry_encoded_bytes: u64,
pub reserved_descendants: u32,
pub committed_descendants: u32,
pub directory_synchronized_at_ns: u64,
pub directory_authority_hash: [u8; 32],
pub parent_role_instances: u32,
pub root_managed_descendants: u32,
pub root_known_created_component_canisters: u32,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
pub operation_id: [u8; 32],
pub canister_id: Principal,
pub activation: ComponentRuntimeActivationEvidence,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentSubtreeRemovalDirectorySynchronizedReceipt {
pub membership_removed: RootComponentSubtreeRemovalMembershipRemovedReceipt,
pub covered_fleet_registry_revision: u64,
pub covered_fleet_registry_content_hash: [u8; 32],
pub covered_component_registry: ComponentRegistryHead,
pub covered_authority_hash: [u8; 32],
pub owning_component: Option<RootComponentSubtreeRemovalDirectoryConvergenceEvidence>,
pub parent: Option<RootComponentSubtreeRemovalDirectoryConvergenceEvidence>,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentSubtreeRemovalCompletedReceipt {
pub registry: ComponentRegistryHead,
pub directory_authority_hash: [u8; 32],
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum ComponentLifecycleStatus {
Prepared,
Active,
Draining,
Removed,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ComponentRegistryHead {
pub component: ComponentInstanceId,
pub revision: u64,
pub content_hash: [u8; 32],
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ComponentRegistryPartitionRequest {
pub component: ComponentInstanceId,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ComponentRegistryPartitionResponse {
pub head: ComponentRegistryHead,
pub binding: ComponentBinding,
pub provisioning_origin: ComponentProvisioningOrigin,
pub release_set: FleetSubnetRootReleaseSet,
pub status: ComponentLifecycleStatus,
pub reserved_descendants: u32,
pub committed_descendants: u32,
pub encoded_bytes: u64,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ComponentDirectoryProvenance {
pub component: ComponentBinding,
pub source_fleet_subnet_root: Principal,
pub component_registry_revision: u64,
pub component_registry_content_hash: [u8; 32],
pub synchronized_at_ns: u64,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ComponentDirectoryHead {
pub provenance: ComponentDirectoryProvenance,
pub descendant_count: u32,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ComponentDirectoryHeadRequest {
pub component: ComponentInstanceId,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ComponentDirectoryPageCursor(pub Vec<u8>);
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ComponentDirectoryPageRequest {
pub directory: ComponentDirectoryHead,
pub parent_canister_id: Option<Principal>,
pub role: Option<CanisterRole>,
pub status: Option<ComponentLifecycleStatus>,
pub cursor: Option<ComponentDirectoryPageCursor>,
pub limit: u16,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ComponentDirectoryChildEntry {
pub binding: ComponentChildBinding,
pub kind: ComponentChildKind,
pub installed_artifact_hash: [u8; 32],
pub status: ComponentLifecycleStatus,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ComponentDirectoryPageResponse {
pub directory: ComponentDirectoryHead,
pub entries: Vec<ComponentDirectoryChildEntry>,
pub next_cursor: Option<ComponentDirectoryPageCursor>,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ComponentRuntimeDirectoryAuthority {
pub fleet: FleetDirectorySnapshot,
pub component: ComponentDirectoryHead,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ComponentRuntimeDirectoryPreparationRequest {
pub operation_id: [u8; 32],
pub authority: ComponentRuntimeDirectoryAuthority,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ComponentRuntimeDirectorySynchronizationRequest {
pub operation_id: [u8; 32],
pub authority: ComponentRuntimeDirectoryAuthority,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum ComponentRuntimePhase {
AwaitingDirectory,
DirectoryPrepared,
Active,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ComponentRuntimeActivationEvidence {
pub directory_authority_hash: [u8; 32],
pub activated_at_ns: u64,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ComponentRuntimeActivationRequest {
pub operation_id: [u8; 32],
pub directory_authority_hash: [u8; 32],
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ComponentRuntimeStatusResponse {
pub operation_id: [u8; 32],
pub binding: ManagedCanisterBinding,
pub phase: ComponentRuntimePhase,
pub authority: Option<ComponentRuntimeDirectoryAuthority>,
pub authority_hash: Option<[u8; 32]>,
pub activation: Option<ComponentRuntimeActivationEvidence>,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ComponentRuntimeDirectoryConvergenceEvidence {
pub operation_id: [u8; 32],
pub binding: ManagedCanisterBinding,
pub covered_authority: ComponentRuntimeDirectoryAuthority,
pub covered_authority_hash: [u8; 32],
pub activation: ComponentRuntimeActivationEvidence,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentCreationEvidence {
pub wasm_store: Principal,
pub payload_hash: [u8; 32],
pub payload_size_bytes: u64,
pub initial_cycles: Cycles,
pub controller: Principal,
pub canister: Option<Principal>,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentInstallEvidence {
pub raw_module_hash: [u8; 32],
pub chunk_hashes: Vec<Vec<u8>>,
pub binding: ComponentBinding,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentChildInstallEvidence {
pub raw_module_hash: [u8; 32],
pub chunk_hashes: Vec<Vec<u8>>,
pub binding: ComponentChildBinding,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentAllocationResponse {
pub operation_id: [u8; 32],
pub allocation_sequence: u64,
pub component: ComponentInstanceId,
pub component_spec: ComponentSpecId,
pub spec_hash: [u8; 32],
pub role: CanisterRole,
pub provisioning_origin: ComponentProvisioningOrigin,
pub release_set: FleetSubnetRootReleaseSet,
pub phase: RootComponentAllocationPhase,
pub creation: Option<RootComponentCreationEvidence>,
pub installation: Option<RootComponentInstallEvidence>,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentChildAllocationResponse {
pub operation_id: [u8; 32],
pub component: ComponentInstanceId,
pub parent_canister_id: Principal,
pub parent_role: CanisterRole,
pub child_role: CanisterRole,
pub child_kind: ComponentChildKind,
pub maximum_instances_per_parent: u32,
pub maximum_descendants: u32,
pub maximum_registry_bytes: u64,
pub reserved_against_registry: ComponentRegistryHead,
pub release_set: FleetSubnetRootReleaseSet,
pub phase: RootComponentAllocationPhase,
pub creation: Option<RootComponentCreationEvidence>,
pub installation: Option<RootComponentChildInstallEvidence>,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentSubtreeRemovalResponse {
pub operation_id: [u8; 32],
pub component: ComponentInstanceId,
pub target_canister_id: Principal,
pub target_parent_canister_id: Principal,
pub target_role: CanisterRole,
pub target_status: ComponentLifecycleStatus,
pub reserved_against_registry: ComponentRegistryHead,
pub maximum_completed_leaves: u32,
pub completed_leaves: u32,
pub traversal_steps: u32,
pub phase: RootComponentSubtreeRemovalPhase,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentDrainingResponse {
pub operation_id: [u8; 32],
pub component: ComponentInstanceId,
pub previous_registry: ComponentRegistryHead,
pub registry: ComponentRegistryHead,
pub descendant_count: u32,
pub descendant_content_hash: [u8; 32],
pub directory_authority_hash: [u8; 32],
pub started_at_ns: u64,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentQuiescenceStopIntent {
pub registry: ComponentRegistryHead,
pub descendant_count: u32,
pub descendant_content_hash: [u8; 32],
pub canister_id: Principal,
pub controller: Principal,
pub expected_module_hash: [u8; 32],
pub covered_fleet_registry_revision: u64,
pub covered_fleet_registry_content_hash: [u8; 32],
pub covered_authority_hash: [u8; 32],
pub runtime_operation_id: [u8; 32],
pub activation: ComponentRuntimeActivationEvidence,
pub prepared_at_ns: u64,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentQuiescentReceipt {
pub stop: RootComponentQuiescenceStopIntent,
pub observed_module_hash: [u8; 32],
pub quiesced_at_ns: u64,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum RootComponentQuiescencePhase {
StopIntent(RootComponentQuiescenceStopIntent),
Quiescent(RootComponentQuiescentReceipt),
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentQuiescenceResponse {
pub operation_id: [u8; 32],
pub component: ComponentInstanceId,
pub phase: RootComponentQuiescencePhase,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentDrainingDescendantsEmpty {
pub registry: ComponentRegistryHead,
pub descendant_content_hash: [u8; 32],
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[expect(
clippy::large_enum_variant,
reason = "wire result embeds the current durable subtree snapshot without a Rust-only indirection"
)]
pub enum RootComponentDrainingAdvancePhase {
DescendantRemoval(RootComponentSubtreeRemovalResponse),
DescendantsEmpty(RootComponentDrainingDescendantsEmpty),
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentDrainingAdvanceResponse {
pub operation_id: [u8; 32],
pub component: ComponentInstanceId,
pub phase: RootComponentDrainingAdvancePhase,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentFinalInventory {
pub registry: ComponentRegistryHead,
pub descendant_content_hash: [u8; 32],
pub registry_encoded_bytes: u64,
pub directory_synchronized_at_ns: u64,
pub covered_fleet_registry_revision: u64,
pub covered_fleet_registry_content_hash: [u8; 32],
pub directory_authority_hash: [u8; 32],
pub inventory_hash: [u8; 32],
pub finalized_at_ns: u64,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentFinalInventoryResponse {
pub operation_id: [u8; 32],
pub component: ComponentInstanceId,
pub inventory: RootComponentFinalInventory,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentChildCommitResponse {
pub allocation: RootComponentChildAllocationResponse,
pub registry: ComponentRegistryPartitionResponse,
pub directory: ComponentDirectoryHead,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentChildDirectoryPreparationResponse {
pub committed: RootComponentChildCommitResponse,
pub child: ComponentRuntimeStatusResponse,
pub owning_component: ComponentRuntimeDirectoryConvergenceEvidence,
pub parent: Option<ComponentRuntimeDirectoryConvergenceEvidence>,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentChildRuntimeActivationResponse {
pub committed: RootComponentChildCommitResponse,
pub child: ComponentRuntimeStatusResponse,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentChildMembershipActivationResponse {
pub committed: RootComponentChildCommitResponse,
pub registry: ComponentRegistryPartitionResponse,
pub directory: ComponentDirectoryHead,
pub child: ComponentRuntimeStatusResponse,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentCommitResponse {
pub allocation: RootComponentAllocationResponse,
pub registry: ComponentRegistryPartitionResponse,
pub directory: ComponentDirectoryHead,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentDirectoryPreparationResponse {
pub committed: RootComponentCommitResponse,
pub target: ComponentRuntimeStatusResponse,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentRuntimeActivationResponse {
pub committed: RootComponentCommitResponse,
pub target: ComponentRuntimeStatusResponse,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RootComponentMembershipActivationResponse {
pub allocation: RootComponentAllocationResponse,
pub registry: ComponentRegistryPartitionResponse,
pub directory: ComponentDirectoryHead,
pub target: ComponentRuntimeStatusResponse,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
dto::root_store::RootStoreBootstrapRequest,
ids::{
AppId, CanonicalNetworkId, FleetCoordinatorBinding, FleetId, FleetKey,
FleetRegistryAuthority, ReleaseBuildId, ReleaseBuildNonce, ReleaseSetDigest, SubnetId,
},
};
#[test]
fn component_registry_contracts_round_trip_through_candid() {
let request = RootComponentRegistryPreparationRequest {
store_bootstrap: RootStoreBootstrapRequest {
manifest_payload_size_bytes: 128,
},
expected_fleet_registry: FleetRegistryVersion {
authority: fleet_registry_authority(),
revision: 4,
content_hash: [5; 32],
},
};
let response = RootComponentRegistryStatusResponse {
fleet_subnet_root: Principal::from_slice(&[6; 29]),
prepared_against_registry: request.expected_fleet_registry.clone(),
release_set: FleetSubnetRootReleaseSet {
release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
[7; 32],
)),
manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
},
component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
next_allocation_sequence: 1,
reserved_component_instances: 0,
committed_component_instances: 0,
managed_descendants: 0,
known_created_component_canisters: 0,
encoded_bytes: 0,
initial_inventory: Some(RootComponentInitialInventoryStatus {
fleet_activation_operation_id: [10; 32],
component_count: 0,
inventory_hash: [11; 32],
sealed_at_ns: 12,
directories_converged: true,
root_runtime_activated: true,
}),
};
let allocation = RootComponentAllocationResponse {
operation_id: [10; 32],
allocation_sequence: 1,
component: ComponentInstanceId::from_generated_bytes([11; 32]),
component_spec: "projects".parse().expect("Component Spec ID"),
spec_hash: [12; 32],
role: CanisterRole::new("project_hub"),
provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
caller: Principal::from_slice(&[13; 29]),
},
release_set: response.release_set,
phase: RootComponentAllocationPhase::Reserved,
creation: None,
installation: None,
};
let created = RootComponentAllocationResponse {
phase: RootComponentAllocationPhase::Created,
creation: Some(RootComponentCreationEvidence {
wasm_store: Principal::from_slice(&[14; 29]),
payload_hash: [15; 32],
payload_size_bytes: 4_096,
initial_cycles: Cycles::new(5_000_000_000_000),
controller: Principal::from_slice(&[6; 29]),
canister: Some(Principal::from_slice(&[16; 29])),
}),
installation: None,
..allocation.clone()
};
let request_bytes = candid::encode_one(&request).expect("encode request");
let response_bytes = candid::encode_one(&response).expect("encode response");
let allocation_bytes = candid::encode_one(&allocation).expect("encode allocation");
let created_bytes = candid::encode_one(&created).expect("encode created allocation");
assert_eq!(
candid::decode_one::<RootComponentRegistryPreparationRequest>(&request_bytes)
.expect("decode request"),
request
);
assert_eq!(
candid::decode_one::<RootComponentRegistryStatusResponse>(&response_bytes)
.expect("decode response"),
response
);
assert_eq!(
candid::decode_one::<RootComponentAllocationResponse>(&allocation_bytes)
.expect("decode allocation"),
allocation
);
assert_eq!(
candid::decode_one::<RootComponentAllocationResponse>(&created_bytes)
.expect("decode created allocation"),
created
);
}
#[test]
fn component_commit_response_round_trips_through_candid() {
let root = Principal::from_slice(&[6; 29]);
let component = ComponentInstanceId::from_generated_bytes([11; 32]);
let component_spec: ComponentSpecId = "projects".parse().expect("Component Spec ID");
let release_set = FleetSubnetRootReleaseSet {
release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
[7; 32],
)),
manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
};
let provisioning_origin = ComponentProvisioningOrigin::FleetAdministrator {
caller: Principal::from_slice(&[13; 29]),
};
let binding = ComponentBinding {
authority: fleet_registry_authority(),
component,
component_spec: component_spec.clone(),
spec_hash: [12; 32],
role: CanisterRole::new("project_hub"),
placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
fleet_subnet_root: root,
canister_id: Principal::from_slice(&[16; 29]),
};
let head = ComponentRegistryHead {
component,
revision: 1,
content_hash: [18; 32],
};
let committed = RootComponentCommitResponse {
allocation: RootComponentAllocationResponse {
operation_id: [10; 32],
allocation_sequence: 1,
component,
component_spec,
spec_hash: binding.spec_hash,
role: binding.role.clone(),
provisioning_origin: provisioning_origin.clone(),
release_set,
phase: RootComponentAllocationPhase::Committed,
creation: Some(RootComponentCreationEvidence {
wasm_store: Principal::from_slice(&[14; 29]),
payload_hash: [15; 32],
payload_size_bytes: 4_096,
initial_cycles: Cycles::new(5_000_000_000_000),
controller: root,
canister: Some(binding.canister_id),
}),
installation: Some(RootComponentInstallEvidence {
raw_module_hash: [20; 32],
chunk_hashes: vec![vec![21; 32]],
binding: binding.clone(),
}),
},
registry: ComponentRegistryPartitionResponse {
head: head.clone(),
binding: binding.clone(),
provisioning_origin,
release_set,
status: ComponentLifecycleStatus::Prepared,
reserved_descendants: 0,
committed_descendants: 0,
encoded_bytes: 2_048,
},
directory: ComponentDirectoryHead {
provenance: ComponentDirectoryProvenance {
component: binding,
source_fleet_subnet_root: root,
component_registry_revision: head.revision,
component_registry_content_hash: head.content_hash,
synchronized_at_ns: 19,
},
descendant_count: 0,
},
};
let committed_bytes = candid::encode_one(&committed).expect("encode committed allocation");
assert_eq!(
candid::decode_one::<RootComponentCommitResponse>(&committed_bytes)
.expect("decode committed allocation"),
committed
);
}
#[test]
fn component_directory_page_contracts_round_trip_through_candid() {
let root = Principal::from_slice(&[6; 29]);
let component = ComponentInstanceId::from_generated_bytes([11; 32]);
let binding = ComponentBinding {
authority: fleet_registry_authority(),
component,
component_spec: "projects".parse().expect("Component Spec ID"),
spec_hash: [12; 32],
role: CanisterRole::new("project_hub"),
placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
fleet_subnet_root: root,
canister_id: Principal::from_slice(&[16; 29]),
};
let directory = ComponentDirectoryHead {
provenance: ComponentDirectoryProvenance {
component: binding.clone(),
source_fleet_subnet_root: root,
component_registry_revision: 3,
component_registry_content_hash: [18; 32],
synchronized_at_ns: 19,
},
descendant_count: 1,
};
let request = ComponentDirectoryPageRequest {
directory: directory.clone(),
parent_canister_id: Some(binding.canister_id),
role: Some(CanisterRole::new("project_instance")),
status: Some(ComponentLifecycleStatus::Active),
cursor: Some(ComponentDirectoryPageCursor(vec![20; 64])),
limit: 50,
};
let response = ComponentDirectoryPageResponse {
directory,
entries: vec![ComponentDirectoryChildEntry {
binding: ComponentChildBinding {
component: binding.clone(),
parent_canister_id: binding.canister_id,
role: CanisterRole::new("project_instance"),
canister_id: Principal::from_slice(&[21; 29]),
},
kind: ComponentChildKind::Instance,
installed_artifact_hash: [22; 32],
status: ComponentLifecycleStatus::Active,
}],
next_cursor: Some(ComponentDirectoryPageCursor(vec![23; 64])),
};
let request_bytes = candid::encode_one(&request).expect("encode Directory page request");
let response_bytes = candid::encode_one(&response).expect("encode Directory page response");
assert_eq!(
candid::decode_one::<ComponentDirectoryPageRequest>(&request_bytes)
.expect("decode Directory page request"),
request
);
assert_eq!(
candid::decode_one::<ComponentDirectoryPageResponse>(&response_bytes)
.expect("decode Directory page response"),
response
);
}
fn fleet_registry_authority() -> FleetRegistryAuthority {
FleetRegistryAuthority {
binding: FleetCoordinatorBinding {
fleet: crate::ids::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,
}
}
#[test]
fn component_creation_request_round_trips_through_candid() {
let request = RootComponentCreationRequest {
operation_id: [10; 32],
};
let bytes = candid::encode_one(request).expect("encode creation request");
assert_eq!(
candid::decode_one::<RootComponentCreationRequest>(&bytes)
.expect("decode creation request"),
request
);
}
#[test]
#[expect(
clippy::too_many_lines,
reason = "one Candid contract test covers every subtree-removal phase receipt"
)]
fn component_subtree_removal_contracts_round_trip_through_candid() {
let component = ComponentInstanceId::from_generated_bytes([41; 32]);
let registry = ComponentRegistryHead {
component,
revision: 7,
content_hash: [42; 32],
};
let request = RootComponentSubtreeRemovalRequest {
operation_id: [43; 32],
component,
target_canister_id: Principal::from_slice(&[44; 29]),
expected_registry: registry.clone(),
};
let status_request = RootComponentSubtreeRemovalStatusRequest {
operation_id: request.operation_id,
component,
};
let advance_request = RootComponentSubtreeRemovalAdvanceRequest {
operation_id: request.operation_id,
component,
expected_traversal_steps: 1,
};
let stop_request = RootComponentSubtreeRemovalStopPreparationRequest {
operation_id: request.operation_id,
component,
expected_traversal_steps: 2,
expected_leaf_canister_id: Principal::from_slice(&[46; 29]),
expected_leaf_parent_canister_id: request.target_canister_id,
};
let stopped = RootComponentSubtreeRemovalStoppedReceipt {
observed_module_hash: [49; 32],
stop: RootComponentSubtreeRemovalStopIntent {
controller: Principal::from_slice(&[48; 29]),
leaf: RootComponentSubtreeRemovalNode {
canister_id: Principal::from_slice(&[46; 29]),
parent_canister_id: request.target_canister_id,
role: CanisterRole::new("project_ledger"),
kind: ComponentChildKind::Singleton,
installed_artifact_hash: [47; 32],
status: ComponentLifecycleStatus::Active,
},
},
};
let response = RootComponentSubtreeRemovalResponse {
operation_id: request.operation_id,
component,
target_canister_id: request.target_canister_id,
target_parent_canister_id: Principal::from_slice(&[45; 29]),
target_role: CanisterRole::new("project_instance"),
target_status: ComponentLifecycleStatus::Active,
reserved_against_registry: registry,
maximum_completed_leaves: 4,
completed_leaves: 1,
traversal_steps: 2,
phase: RootComponentSubtreeRemovalPhase::DirectorySynchronized(
RootComponentSubtreeRemovalDirectorySynchronizedReceipt {
membership_removed: RootComponentSubtreeRemovalMembershipRemovedReceipt {
deleted: RootComponentSubtreeRemovalDeletedReceipt {
deletion: RootComponentSubtreeRemovalDeleteIntent { stopped },
},
removed_from_registry: ComponentRegistryHead {
component,
revision: 8,
content_hash: [50; 32],
},
previous_descendant_content_hash: [51; 32],
previous_committed_descendants: 4,
registry: ComponentRegistryHead {
component,
revision: 9,
content_hash: [52; 32],
},
descendant_content_hash: [53; 32],
registry_encoded_bytes: 4_096,
reserved_descendants: 1,
committed_descendants: 3,
directory_synchronized_at_ns: 54,
directory_authority_hash: [55; 32],
parent_role_instances: 0,
root_managed_descendants: 4,
root_known_created_component_canisters: 4,
},
covered_fleet_registry_revision: 6,
covered_fleet_registry_content_hash: [56; 32],
covered_component_registry: ComponentRegistryHead {
component,
revision: 9,
content_hash: [52; 32],
},
covered_authority_hash: [55; 32],
owning_component: Some(
RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
operation_id: [57; 32],
canister_id: Principal::from_slice(&[58; 29]),
activation: ComponentRuntimeActivationEvidence {
directory_authority_hash: [59; 32],
activated_at_ns: 60,
},
},
),
parent: Some(RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
operation_id: [61; 32],
canister_id: request.target_canister_id,
activation: ComponentRuntimeActivationEvidence {
directory_authority_hash: [62; 32],
activated_at_ns: 63,
},
}),
},
),
};
let request_bytes = candid::encode_one(&request).expect("encode subtree removal request");
let advance_bytes =
candid::encode_one(advance_request).expect("encode subtree removal advance request");
let stop_bytes =
candid::encode_one(stop_request).expect("encode subtree removal stop request");
let status_bytes =
candid::encode_one(status_request).expect("encode subtree removal status request");
let response_bytes =
candid::encode_one(&response).expect("encode subtree removal response");
assert_eq!(
candid::decode_one::<RootComponentSubtreeRemovalRequest>(&request_bytes)
.expect("decode subtree removal request"),
request
);
assert_eq!(
candid::decode_one::<RootComponentSubtreeRemovalAdvanceRequest>(&advance_bytes)
.expect("decode subtree removal advance request"),
advance_request
);
assert_eq!(
candid::decode_one::<RootComponentSubtreeRemovalStopPreparationRequest>(&stop_bytes)
.expect("decode subtree removal stop request"),
stop_request
);
assert_eq!(
candid::decode_one::<RootComponentSubtreeRemovalStatusRequest>(&status_bytes)
.expect("decode subtree removal status request"),
status_request
);
assert_eq!(
candid::decode_one::<RootComponentSubtreeRemovalResponse>(&response_bytes)
.expect("decode subtree removal response"),
response
);
let mut quiescent_owner_response = response;
let RootComponentSubtreeRemovalPhase::DirectorySynchronized(receipt) =
&mut quiescent_owner_response.phase
else {
panic!("Directory-synchronized response");
};
receipt.owning_component = None;
let quiescent_owner_bytes = candid::encode_one(&quiescent_owner_response)
.expect("encode quiescent-owner subtree response");
assert_eq!(
candid::decode_one::<RootComponentSubtreeRemovalResponse>(&quiescent_owner_bytes)
.expect("decode quiescent-owner subtree response"),
quiescent_owner_response
);
}
#[test]
fn component_draining_contracts_round_trip_through_candid() {
let component = ComponentInstanceId::from_generated_bytes([60; 32]);
let previous_registry = ComponentRegistryHead {
component,
revision: 7,
content_hash: [61; 32],
};
let request = RootComponentDrainingRequest {
operation_id: [62; 32],
component,
expected_registry: previous_registry.clone(),
};
let status_request = RootComponentDrainingStatusRequest {
operation_id: request.operation_id,
component,
};
let response = RootComponentDrainingResponse {
operation_id: request.operation_id,
component,
previous_registry,
registry: ComponentRegistryHead {
component,
revision: 8,
content_hash: [63; 32],
},
descendant_count: 20_000,
descendant_content_hash: [64; 32],
directory_authority_hash: [65; 32],
started_at_ns: 66,
};
let request_bytes =
candid::encode_one(&request).expect("encode Component draining request");
let status_bytes =
candid::encode_one(status_request).expect("encode Component draining status request");
let response_bytes =
candid::encode_one(&response).expect("encode Component draining response");
assert_eq!(
candid::decode_one::<RootComponentDrainingRequest>(&request_bytes)
.expect("decode Component draining request"),
request
);
assert_eq!(
candid::decode_one::<RootComponentDrainingStatusRequest>(&status_bytes)
.expect("decode Component draining status request"),
status_request
);
assert_eq!(
candid::decode_one::<RootComponentDrainingResponse>(&response_bytes)
.expect("decode Component draining response"),
response
);
}
#[test]
fn component_quiescence_contracts_round_trip_through_candid() {
let component = ComponentInstanceId::from_generated_bytes([67; 32]);
let registry = ComponentRegistryHead {
component,
revision: 9,
content_hash: [68; 32],
};
let request = RootComponentQuiescenceRequest {
operation_id: [69; 32],
component,
expected_registry: registry.clone(),
};
let status_request = RootComponentQuiescenceStatusRequest {
operation_id: request.operation_id,
component,
};
let stop = RootComponentQuiescenceStopIntent {
registry,
descendant_count: 20_000,
descendant_content_hash: [70; 32],
canister_id: Principal::from_slice(&[71; 29]),
controller: Principal::from_slice(&[72; 29]),
expected_module_hash: [73; 32],
covered_fleet_registry_revision: 10,
covered_fleet_registry_content_hash: [74; 32],
covered_authority_hash: [75; 32],
runtime_operation_id: [76; 32],
activation: ComponentRuntimeActivationEvidence {
directory_authority_hash: [77; 32],
activated_at_ns: 78,
},
prepared_at_ns: 79,
};
let response = RootComponentQuiescenceResponse {
operation_id: request.operation_id,
component,
phase: RootComponentQuiescencePhase::Quiescent(RootComponentQuiescentReceipt {
stop,
observed_module_hash: [73; 32],
quiesced_at_ns: 80,
}),
};
let request_bytes = candid::encode_one(&request).expect("encode quiescence request");
let status_bytes =
candid::encode_one(status_request).expect("encode quiescence status request");
let response_bytes = candid::encode_one(&response).expect("encode quiescence response");
assert_eq!(
candid::decode_one::<RootComponentQuiescenceRequest>(&request_bytes)
.expect("decode quiescence request"),
request
);
assert_eq!(
candid::decode_one::<RootComponentQuiescenceStatusRequest>(&status_bytes)
.expect("decode quiescence status request"),
status_request
);
assert_eq!(
candid::decode_one::<RootComponentQuiescenceResponse>(&response_bytes)
.expect("decode quiescence response"),
response
);
}
#[test]
fn component_draining_advance_contracts_round_trip_through_candid() {
let component = ComponentInstanceId::from_generated_bytes([81; 32]);
let registry = ComponentRegistryHead {
component,
revision: 12,
content_hash: [82; 32],
};
let request = RootComponentDrainingAdvanceRequest {
operation_id: [83; 32],
component,
};
let descendant_removal = RootComponentDrainingAdvanceResponse {
operation_id: request.operation_id,
component,
phase: RootComponentDrainingAdvancePhase::DescendantRemoval(
RootComponentSubtreeRemovalResponse {
operation_id: [84; 32],
component,
target_canister_id: Principal::from_slice(&[85; 29]),
target_parent_canister_id: Principal::from_slice(&[86; 29]),
target_role: CanisterRole::new("project_instance"),
target_status: ComponentLifecycleStatus::Active,
reserved_against_registry: registry.clone(),
maximum_completed_leaves: 20_000,
completed_leaves: 0,
traversal_steps: 0,
phase: RootComponentSubtreeRemovalPhase::Fenced,
},
),
};
let descendants_empty = RootComponentDrainingAdvanceResponse {
operation_id: request.operation_id,
component,
phase: RootComponentDrainingAdvancePhase::DescendantsEmpty(
RootComponentDrainingDescendantsEmpty {
registry,
descendant_content_hash: [87; 32],
},
),
};
let request_bytes =
candid::encode_one(request).expect("encode Component draining advance request");
let removal_bytes = candid::encode_one(&descendant_removal)
.expect("encode Component draining removal response");
let empty_bytes = candid::encode_one(&descendants_empty)
.expect("encode Component draining empty response");
assert_eq!(
candid::decode_one::<RootComponentDrainingAdvanceRequest>(&request_bytes)
.expect("decode Component draining advance request"),
request
);
assert_eq!(
candid::decode_one::<RootComponentDrainingAdvanceResponse>(&removal_bytes)
.expect("decode Component draining removal response"),
descendant_removal
);
assert_eq!(
candid::decode_one::<RootComponentDrainingAdvanceResponse>(&empty_bytes)
.expect("decode Component draining empty response"),
descendants_empty
);
}
#[test]
fn component_final_inventory_contracts_round_trip_through_candid() {
let component = ComponentInstanceId::from_generated_bytes([88; 32]);
let registry = ComponentRegistryHead {
component,
revision: 21,
content_hash: [89; 32],
};
let request = RootComponentFinalInventoryRequest {
operation_id: [90; 32],
component,
expected_registry: registry.clone(),
};
let response = RootComponentFinalInventoryResponse {
operation_id: request.operation_id,
component,
inventory: RootComponentFinalInventory {
registry,
descendant_content_hash: [91; 32],
registry_encoded_bytes: 4_096,
directory_synchronized_at_ns: 92,
covered_fleet_registry_revision: 93,
covered_fleet_registry_content_hash: [94; 32],
directory_authority_hash: [95; 32],
inventory_hash: [96; 32],
finalized_at_ns: 97,
},
};
let request_bytes =
candid::encode_one(&request).expect("encode Component final inventory request");
let response_bytes =
candid::encode_one(&response).expect("encode Component final inventory response");
assert_eq!(
candid::decode_one::<RootComponentFinalInventoryRequest>(&request_bytes)
.expect("decode Component final inventory request"),
request
);
assert_eq!(
candid::decode_one::<RootComponentFinalInventoryResponse>(&response_bytes)
.expect("decode Component final inventory response"),
response
);
}
#[test]
fn component_subtree_removal_deletion_requests_round_trip_through_candid() {
let prepare = RootComponentSubtreeRemovalDeletePreparationRequest {
operation_id: [50; 32],
component: ComponentInstanceId::from_generated_bytes([51; 32]),
expected_traversal_steps: 3,
expected_leaf_canister_id: Principal::from_slice(&[52; 29]),
expected_leaf_parent_canister_id: Principal::from_slice(&[53; 29]),
};
let request = RootComponentSubtreeRemovalDeleteRequest {
operation_id: prepare.operation_id,
component: prepare.component,
expected_traversal_steps: prepare.expected_traversal_steps,
expected_leaf_canister_id: prepare.expected_leaf_canister_id,
expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
};
let membership_request = RootComponentSubtreeRemovalMembershipRemovalRequest {
operation_id: prepare.operation_id,
component: prepare.component,
expected_traversal_steps: prepare.expected_traversal_steps,
expected_leaf_canister_id: prepare.expected_leaf_canister_id,
expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
};
let directory_request = RootComponentSubtreeRemovalDirectorySynchronizationRequest {
operation_id: prepare.operation_id,
component: prepare.component,
expected_traversal_steps: prepare.expected_traversal_steps,
expected_leaf_canister_id: prepare.expected_leaf_canister_id,
expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
};
let finalization_request = RootComponentSubtreeRemovalLeafFinalizationRequest {
operation_id: prepare.operation_id,
component: prepare.component,
expected_traversal_steps: prepare.expected_traversal_steps,
expected_leaf_canister_id: prepare.expected_leaf_canister_id,
expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
};
let prepare_bytes = candid::encode_one(prepare)
.expect("encode subtree removal deletion preparation request");
let request_bytes =
candid::encode_one(request).expect("encode subtree removal deletion request");
let membership_request_bytes = candid::encode_one(membership_request)
.expect("encode subtree removal membership-removal request");
let directory_request_bytes = candid::encode_one(directory_request)
.expect("encode subtree removal Directory synchronization request");
let finalization_request_bytes = candid::encode_one(finalization_request)
.expect("encode subtree removal leaf-finalization request");
assert_eq!(
candid::decode_one::<RootComponentSubtreeRemovalDeletePreparationRequest>(
&prepare_bytes
)
.expect("decode subtree removal deletion preparation request"),
prepare
);
assert_eq!(
candid::decode_one::<RootComponentSubtreeRemovalDeleteRequest>(&request_bytes)
.expect("decode subtree removal deletion request"),
request
);
assert_eq!(
candid::decode_one::<RootComponentSubtreeRemovalMembershipRemovalRequest>(
&membership_request_bytes
)
.expect("decode subtree removal membership-removal request"),
membership_request
);
assert_eq!(
candid::decode_one::<RootComponentSubtreeRemovalDirectorySynchronizationRequest>(
&directory_request_bytes
)
.expect("decode subtree removal Directory synchronization request"),
directory_request
);
assert_eq!(
candid::decode_one::<RootComponentSubtreeRemovalLeafFinalizationRequest>(
&finalization_request_bytes
)
.expect("decode subtree removal leaf-finalization request"),
finalization_request
);
}
#[test]
fn component_subtree_removal_stop_request_round_trips_through_candid() {
let request = RootComponentSubtreeRemovalStopRequest {
operation_id: [50; 32],
component: ComponentInstanceId::from_generated_bytes([51; 32]),
expected_traversal_steps: 3,
expected_leaf_canister_id: Principal::from_slice(&[52; 29]),
expected_leaf_parent_canister_id: Principal::from_slice(&[53; 29]),
};
let bytes =
candid::encode_one(request).expect("encode subtree removal stop execution request");
assert_eq!(
candid::decode_one::<RootComponentSubtreeRemovalStopRequest>(&bytes)
.expect("decode subtree removal stop execution request"),
request
);
}
#[test]
#[expect(
clippy::too_many_lines,
reason = "one round-trip test keeps the complete child lifecycle boundary coherent"
)]
fn component_child_lifecycle_contracts_round_trip_through_candid() {
let component = ComponentInstanceId::from_generated_bytes([11; 32]);
let registry = ComponentRegistryHead {
component,
revision: 2,
content_hash: [12; 32],
};
let request = RootComponentChildAllocationRequest {
operation_id: [13; 32],
component,
expected_registry: registry.clone(),
child_role: CanisterRole::new("project_instance"),
};
let status_request = RootComponentChildAllocationStatusRequest {
operation_id: request.operation_id,
component,
};
let creation_request = RootComponentChildCreationRequest {
operation_id: request.operation_id,
component,
};
let install_request = RootComponentChildInstallRequest {
operation_id: request.operation_id,
component,
};
let commit_request = RootComponentChildCommitRequest {
operation_id: request.operation_id,
component,
};
let directory_request = RootComponentChildDirectoryPreparationRequest {
operation_id: request.operation_id,
component,
};
let activation_request = RootComponentChildRuntimeActivationRequest {
operation_id: request.operation_id,
component,
};
let membership_request = RootComponentChildMembershipActivationRequest {
operation_id: request.operation_id,
component,
};
let root = Principal::from_slice(&[17; 29]);
let parent = Principal::from_slice(&[14; 29]);
let child = Principal::from_slice(&[18; 29]);
let child_binding = ComponentChildBinding {
component: ComponentBinding {
authority: fleet_registry_authority(),
component,
component_spec: "projects".parse().expect("Component Spec"),
spec_hash: [19; 32],
role: CanisterRole::new("project_hub"),
placement_subnet: SubnetId::from_principal(Principal::from_slice(&[20; 29])),
fleet_subnet_root: root,
canister_id: parent,
},
parent_canister_id: parent,
role: request.child_role.clone(),
canister_id: child,
};
let response = RootComponentChildAllocationResponse {
operation_id: request.operation_id,
component,
parent_canister_id: parent,
parent_role: CanisterRole::new("project_hub"),
child_role: request.child_role.clone(),
child_kind: ComponentChildKind::Instance,
maximum_instances_per_parent: 10_000,
maximum_descendants: 20_000,
maximum_registry_bytes: 16_777_216,
reserved_against_registry: registry,
release_set: FleetSubnetRootReleaseSet {
release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
[15; 32],
)),
manifest_digest: ReleaseSetDigest::from_bytes([16; 32]),
},
phase: RootComponentAllocationPhase::Verified,
creation: Some(RootComponentCreationEvidence {
wasm_store: Principal::from_slice(&[21; 29]),
payload_hash: [22; 32],
payload_size_bytes: 4_096,
initial_cycles: Cycles::new(5_000_000_000_000),
controller: root,
canister: Some(child),
}),
installation: Some(RootComponentChildInstallEvidence {
raw_module_hash: [23; 32],
chunk_hashes: vec![vec![24; 32]],
binding: child_binding.clone(),
}),
};
let commit_response = RootComponentChildCommitResponse {
allocation: response.clone(),
registry: ComponentRegistryPartitionResponse {
head: ComponentRegistryHead {
component,
revision: 3,
content_hash: [25; 32],
},
binding: child_binding.component.clone(),
provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
caller: Principal::from_slice(&[26; 29]),
},
release_set: response.release_set,
status: ComponentLifecycleStatus::Active,
reserved_descendants: 0,
committed_descendants: 1,
encoded_bytes: 8_192,
},
directory: ComponentDirectoryHead {
provenance: ComponentDirectoryProvenance {
component: child_binding.component.clone(),
source_fleet_subnet_root: root,
component_registry_revision: 3,
component_registry_content_hash: [25; 32],
synchronized_at_ns: 27,
},
descendant_count: 1,
},
};
let runtime_authority = ComponentRuntimeDirectoryAuthority {
fleet: FleetDirectorySnapshot {
provenance: crate::dto::fleet_registry::FleetDirectoryProvenance {
registry: FleetRegistryVersion {
authority: fleet_registry_authority(),
revision: 4,
content_hash: [28; 32],
},
source_fleet_subnet_root: root,
},
fleet_subnet_roots: vec![
crate::dto::fleet_registry::FleetSubnetRootDirectoryEntry {
placement_subnet: commit_response.registry.binding.placement_subnet,
fleet_subnet_root: root,
status: crate::dto::fleet_registry::FleetSubnetRootStatus::Active,
},
],
},
component: commit_response.directory.clone(),
};
let activation = ComponentRuntimeActivationEvidence {
directory_authority_hash: [29; 32],
activated_at_ns: 30,
};
let directory_response = RootComponentChildDirectoryPreparationResponse {
committed: commit_response.clone(),
child: ComponentRuntimeStatusResponse {
operation_id: request.operation_id,
binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
phase: ComponentRuntimePhase::DirectoryPrepared,
authority: Some(runtime_authority.clone()),
authority_hash: Some([31; 32]),
activation: None,
},
owning_component: ComponentRuntimeDirectoryConvergenceEvidence {
operation_id: [32; 32],
binding: ManagedCanisterBinding::Component(child_binding.component.clone()),
covered_authority: runtime_authority.clone(),
covered_authority_hash: [31; 32],
activation,
},
parent: None,
};
let activation_response = RootComponentChildRuntimeActivationResponse {
committed: commit_response.clone(),
child: ComponentRuntimeStatusResponse {
operation_id: request.operation_id,
binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
phase: ComponentRuntimePhase::Active,
authority: Some(runtime_authority.clone()),
authority_hash: Some([31; 32]),
activation: Some(ComponentRuntimeActivationEvidence {
directory_authority_hash: [31; 32],
activated_at_ns: 33,
}),
},
};
let active_directory = ComponentDirectoryHead {
provenance: ComponentDirectoryProvenance {
component: child_binding.component.clone(),
source_fleet_subnet_root: root,
component_registry_revision: 4,
component_registry_content_hash: [34; 32],
synchronized_at_ns: 35,
},
descendant_count: 1,
};
let active_authority = ComponentRuntimeDirectoryAuthority {
fleet: runtime_authority.fleet,
component: active_directory.clone(),
};
let membership_response = RootComponentChildMembershipActivationResponse {
committed: commit_response.clone(),
registry: ComponentRegistryPartitionResponse {
head: ComponentRegistryHead {
component,
revision: 4,
content_hash: [34; 32],
},
binding: child_binding.component.clone(),
provisioning_origin: commit_response.registry.provisioning_origin.clone(),
release_set: commit_response.registry.release_set,
status: ComponentLifecycleStatus::Active,
reserved_descendants: 0,
committed_descendants: 1,
encoded_bytes: 8_256,
},
directory: active_directory,
child: ComponentRuntimeStatusResponse {
operation_id: request.operation_id,
binding: ManagedCanisterBinding::ComponentChild(child_binding),
phase: ComponentRuntimePhase::Active,
authority: Some(active_authority),
authority_hash: Some([36; 32]),
activation: Some(ComponentRuntimeActivationEvidence {
directory_authority_hash: [31; 32],
activated_at_ns: 33,
}),
},
};
let request_bytes = candid::encode_one(&request).expect("encode child reservation");
let status_bytes =
candid::encode_one(status_request).expect("encode child reservation status");
let creation_bytes =
candid::encode_one(creation_request).expect("encode child creation request");
let install_bytes =
candid::encode_one(install_request).expect("encode child install request");
let response_bytes = candid::encode_one(&response).expect("encode child response");
let commit_request_bytes =
candid::encode_one(commit_request).expect("encode child commit request");
let directory_request_bytes =
candid::encode_one(directory_request).expect("encode child Directory request");
let activation_request_bytes =
candid::encode_one(activation_request).expect("encode child activation request");
let membership_request_bytes =
candid::encode_one(membership_request).expect("encode child membership request");
let commit_response_bytes =
candid::encode_one(&commit_response).expect("encode child commit response");
let directory_response_bytes =
candid::encode_one(&directory_response).expect("encode child Directory response");
let activation_response_bytes =
candid::encode_one(&activation_response).expect("encode child activation response");
let membership_response_bytes =
candid::encode_one(&membership_response).expect("encode child membership response");
assert_eq!(
candid::decode_one::<RootComponentChildAllocationRequest>(&request_bytes)
.expect("decode child reservation"),
request
);
assert_eq!(
candid::decode_one::<RootComponentChildAllocationStatusRequest>(&status_bytes)
.expect("decode child reservation status"),
status_request
);
assert_eq!(
candid::decode_one::<RootComponentChildCreationRequest>(&creation_bytes)
.expect("decode child creation request"),
creation_request
);
assert_eq!(
candid::decode_one::<RootComponentChildInstallRequest>(&install_bytes)
.expect("decode child install request"),
install_request
);
assert_eq!(
candid::decode_one::<RootComponentChildAllocationResponse>(&response_bytes)
.expect("decode child response"),
response
);
assert_eq!(
candid::decode_one::<RootComponentChildCommitRequest>(&commit_request_bytes)
.expect("decode child commit request"),
commit_request
);
assert_eq!(
candid::decode_one::<RootComponentChildDirectoryPreparationRequest>(
&directory_request_bytes
)
.expect("decode child Directory request"),
directory_request
);
assert_eq!(
candid::decode_one::<RootComponentChildCommitResponse>(&commit_response_bytes)
.expect("decode child commit response"),
commit_response
);
assert_eq!(
candid::decode_one::<RootComponentChildDirectoryPreparationResponse>(
&directory_response_bytes
)
.expect("decode child Directory response"),
directory_response
);
assert_eq!(
candid::decode_one::<RootComponentChildRuntimeActivationRequest>(
&activation_request_bytes
)
.expect("decode child activation request"),
activation_request
);
assert_eq!(
candid::decode_one::<RootComponentChildRuntimeActivationResponse>(
&activation_response_bytes
)
.expect("decode child activation response"),
activation_response
);
assert_eq!(
candid::decode_one::<RootComponentChildMembershipActivationRequest>(
&membership_request_bytes
)
.expect("decode child membership request"),
membership_request
);
assert_eq!(
candid::decode_one::<RootComponentChildMembershipActivationResponse>(
&membership_response_bytes
)
.expect("decode child membership response"),
membership_response
);
}
#[test]
fn component_install_request_round_trips_through_candid() {
let request = RootComponentInstallRequest {
operation_id: [10; 32],
};
let bytes = candid::encode_one(request).expect("encode install request");
assert_eq!(
candid::decode_one::<RootComponentInstallRequest>(&bytes)
.expect("decode install request"),
request
);
}
#[test]
fn component_commit_request_round_trips_through_candid() {
let request = RootComponentCommitRequest {
operation_id: [10; 32],
};
let bytes = candid::encode_one(request).expect("encode commit request");
assert_eq!(
candid::decode_one::<RootComponentCommitRequest>(&bytes)
.expect("decode commit request"),
request
);
}
#[test]
fn component_runtime_activation_requests_round_trip_through_candid() {
let root_request = RootComponentRuntimeActivationRequest {
operation_id: [22; 32],
};
let target_request = ComponentRuntimeActivationRequest {
operation_id: root_request.operation_id,
directory_authority_hash: [23; 32],
};
let membership_request = RootComponentMembershipActivationRequest {
operation_id: root_request.operation_id,
};
let root_bytes = candid::encode_one(root_request).expect("encode root activation request");
let target_bytes =
candid::encode_one(target_request).expect("encode target activation request");
let membership_bytes =
candid::encode_one(membership_request).expect("encode membership activation request");
assert_eq!(
candid::decode_one::<RootComponentRuntimeActivationRequest>(&root_bytes)
.expect("decode root activation request"),
root_request
);
assert_eq!(
candid::decode_one::<ComponentRuntimeActivationRequest>(&target_bytes)
.expect("decode target activation request"),
target_request
);
assert_eq!(
candid::decode_one::<RootComponentMembershipActivationRequest>(&membership_bytes)
.expect("decode membership activation request"),
membership_request
);
}
}