use crate::support::path_stamp::utc_timestamp_ns;
use canic_core::dto::component_provisioning::FleetComponentProvisioningPhase;
use canic_host::fleet_ensure::dto::{
FleetComponentProgress, FleetComponentProgressState, FleetEnsureActionKind, FleetEnsurePhase,
FleetEnsureProgress, FleetEnsureProgressState, FleetProvisioningProgress,
};
use std::{fmt::Write as _, time::Duration};
pub(in crate::fleet) fn plain(progress: &FleetEnsureProgress) -> String {
let mut line = format!(
"Fleet deployment: {}; reviewed effects {}/{}",
job(progress),
progress.applied_effects,
progress.reviewed_effects
);
if let Some(action) = &progress.next_action {
let _ = write!(
line,
"; next reviewed work: {} ({})",
action_label(action.kind),
safe_text(&action.target)
);
}
if let FleetEnsureProgressState::AwaitingProgress {
elapsed_seconds,
provisioning,
} = &progress.state
{
let _ = write!(line, "; {elapsed_seconds}s awaiting this effect here");
if let Some(detail) = provisioning {
let _ = write!(
line,
"; prepared Roots {}/{}; registered Roots {}/{}; active Roots {}/{}; Components in scope {}",
detail.provisioned_root_count,
detail.root_batch_count,
detail.directory_confirmed_root_count,
detail.directory_confirmation_root_count,
detail.runtime_activated_root_count,
detail.root_batch_count,
detail.component_count
);
if let Some(retry) = detail.pending_root_failure {
let _ = write!(line, "; {}", pending_retry(retry).join("; "));
}
for component in &detail.components {
let _ = write!(
line,
"; {}; Root {}",
component_label(component),
component.root
);
}
} else {
line.push_str("; readiness detail unavailable");
}
}
if let FleetEnsureProgressState::ReviewRequired {
review: Some(review),
..
} = &progress.state
{
let _ = write!(line, "; {review}");
}
line
}
pub(super) fn milestone(
progress: &FleetEnsureProgress,
age: Duration,
unchanged: Duration,
) -> String {
let mut line = plain(progress);
if age.as_secs() >= 30 {
let _ = write!(
line,
"; observation: {}s old (stale); last change: {}s ago",
age.as_secs(),
unchanged.as_secs()
);
}
line
}
pub(super) fn panel(
progress: &FleetEnsureProgress,
age: Duration,
unchanged: Duration,
) -> Vec<String> {
let stale = age.as_secs() >= 30;
let marker = if stale {
"!"
} else if matches!(
progress.state,
FleetEnsureProgressState::AwaitingProgress { .. }
) {
["|", "/", "-", "\\"][(age.as_millis() / 250 % 4) as usize]
} else {
" "
};
let mut lines = vec![format!("{marker} {}", job(progress))];
if let Some(action) = &progress.next_action
&& !matches!(
progress.state,
FleetEnsureProgressState::AwaitingProgress {
provisioning: Some(_),
..
}
)
{
lines.push(format!(
"Current work: {} ({})",
action_label(action.kind),
safe_text(&action.target)
));
}
if let FleetEnsureProgressState::AwaitingProgress {
elapsed_seconds,
provisioning,
} = &progress.state
{
lines.push(String::new());
if let Some(detail) = provisioning {
lines.push(format!("{:<32} State Roots", "Stage"));
lines.extend(stages(detail));
lines.push("Final checks Pending".into());
if let Some(retry) = detail.pending_root_failure {
lines.extend(pending_retry(retry));
}
lines.push(format!(
"Waiting: {elapsed_seconds}s; Components in scope: {}",
detail.component_count
));
let mut components = detail.components.iter().collect::<Vec<_>>();
components.sort_by_key(|component| !component.current);
for component in components.iter().take(4) {
lines.push(component_label(component));
lines.push(format!(" Root {}", component.root));
}
if components.len() > 4 {
lines.push(format!(
"{} more components in --json output and timing receipt",
components.len() - 4
));
}
} else {
lines.push("Readiness detail unavailable".into());
lines.push(format!("Waiting: {elapsed_seconds}s (last reported)"));
}
}
lines.push(format!(
"Reviewed work: {}/{} effects",
progress.applied_effects, progress.reviewed_effects
));
lines.push(format!(
"Last change: {}s ago; observation: {}s old{}",
unchanged.as_secs(),
age.as_secs(),
if stale { " (stale)" } else { "" }
));
lines
}
fn component_label(component: &FleetComponentProgress) -> String {
let state = match component.state {
FleetComponentProgressState::Unknown => "unknown",
FleetComponentProgressState::Reserved => "reserved",
FleetComponentProgressState::Claimed => "claimed",
FleetComponentProgressState::Installed => "installed",
FleetComponentProgressState::Registered => "registered",
FleetComponentProgressState::Published => "directory published",
FleetComponentProgressState::RuntimePending => "awaiting activation",
FleetComponentProgressState::Active => "active",
};
format!(
"{}[{}]/{} ({}): {state}{}",
safe_text(&component.deployment),
component.placement,
safe_text(&component.member_path.join("/")),
safe_text(&component.component_spec),
if component.current { " [current]" } else { "" }
)
}
fn stages(detail: &FleetProvisioningProgress) -> [String; 3] {
[
stage(
"Prepare application canisters",
detail.provisioned_root_count,
detail.root_batch_count,
matches!(
detail.phase,
FleetComponentProvisioningPhase::RootsAccepted
| FleetComponentProvisioningPhase::ProvisioningRoots
),
),
stage(
"Register services",
detail.directory_confirmed_root_count,
detail.directory_confirmation_root_count,
matches!(
detail.phase,
FleetComponentProvisioningPhase::ComponentsProvisioned
| FleetComponentProvisioningPhase::ServiceTopologyPublished
| FleetComponentProvisioningPhase::ConfirmingDirectories
),
),
stage(
"Start application services",
detail.runtime_activated_root_count,
detail.root_batch_count,
matches!(
detail.phase,
FleetComponentProvisioningPhase::DirectoriesConfirmed
| FleetComponentProvisioningPhase::ActivatingRuntimes
),
),
]
}
fn stage(label: &str, done: u32, total: u32, waiting: bool) -> String {
let state = if total == 0 || done > total {
"Unknown"
} else if done == total {
"Done"
} else if waiting {
"Waiting"
} else {
"Pending"
};
format!("{label:<32} {state:<7} {done}/{total}")
}
const fn job(progress: &FleetEnsureProgress) -> &'static str {
match &progress.state {
FleetEnsureProgressState::Complete => "Deployment verified",
FleetEnsureProgressState::FundingRequired => "Operator funding required",
FleetEnsureProgressState::ReviewRequired { .. } => "Operator review required",
FleetEnsureProgressState::PrerequisiteComplete => {
"Prerequisites complete; deployment verification pending"
}
FleetEnsureProgressState::AwaitingProgress {
provisioning: Some(detail),
..
} => provisioning_job(detail.phase),
FleetEnsureProgressState::AwaitingProgress { .. } => match progress.phase {
FleetEnsurePhase::TerminalVerification => "Waiting for final verification",
FleetEnsurePhase::PoolReadiness => "Waiting for pool readiness",
_ => "Waiting for observed progress",
},
FleetEnsureProgressState::Advancing => match progress.phase {
FleetEnsurePhase::Infrastructure => "Preparing infrastructure",
FleetEnsurePhase::ImportReconciliation => "Reconciling pool canisters",
FleetEnsurePhase::ControlPlane => "Preparing Fleet services and artifacts",
FleetEnsurePhase::WorkloadProvisioning => "Preparing application services",
FleetEnsurePhase::PoolReadiness => "Checking pool readiness",
FleetEnsurePhase::TerminalVerification | FleetEnsurePhase::Complete => {
"Verifying deployment"
}
},
}
}
const fn provisioning_job(phase: FleetComponentProvisioningPhase) -> &'static str {
match phase {
FleetComponentProvisioningPhase::Planned
| FleetComponentProvisioningPhase::AcceptingRoots => "Waiting for Roots to accept work",
FleetComponentProvisioningPhase::RootsAccepted
| FleetComponentProvisioningPhase::ProvisioningRoots => "Preparing application canisters",
FleetComponentProvisioningPhase::ComponentsProvisioned
| FleetComponentProvisioningPhase::ServiceTopologyPublished
| FleetComponentProvisioningPhase::ConfirmingDirectories => "Registering services",
FleetComponentProvisioningPhase::DirectoriesConfirmed
| FleetComponentProvisioningPhase::ActivatingRuntimes => {
"Waiting for application services to start"
}
FleetComponentProvisioningPhase::RuntimesActivated => {
"Application services started; verification pending"
}
}
}
const fn action_label(kind: FleetEnsureActionKind) -> &'static str {
match kind {
FleetEnsureActionKind::ActivateRegistry => "Activate Fleet registry",
FleetEnsureActionKind::ActivateRegistryMirror => "Activate registry mirror",
FleetEnsureActionKind::AdoptStore => "Connect artifact Store",
FleetEnsureActionKind::BootstrapStore => "Prepare artifact Store",
FleetEnsureActionKind::Create => "Create canister",
FleetEnsureActionKind::Delete => "Delete canister",
FleetEnsureActionKind::DeleteSnapshot => "Clear reviewed snapshot",
FleetEnsureActionKind::Fund => "Fund canister",
FleetEnsureActionKind::FundEstate => "Fund Root estate",
FleetEnsureActionKind::Install => "Install canister",
FleetEnsureActionKind::JoinRoot => "Register Root",
FleetEnsureActionKind::MaintainPoolReadiness => "Prepare ready pool canisters",
FleetEnsureActionKind::ObservePoolReadiness => "Verify pool readiness",
FleetEnsureActionKind::PrepareComponentRegistry => "Prepare service registry",
FleetEnsureActionKind::PrepareStoreFixture => "Prepare artifact upload",
FleetEnsureActionKind::Protocol => "Apply reviewed protocol action",
FleetEnsureActionKind::ProvisionComponents => "Provision application services",
FleetEnsureActionKind::PublishStoreChunk
| FleetEnsureActionKind::PublishStoreFixtureChunk => "Upload artifact chunk",
FleetEnsureActionKind::ReconcilePoolAsset => "Reconcile pool canister",
FleetEnsureActionKind::SetControllers => "Set canister controllers",
FleetEnsureActionKind::Start => "Start canister",
FleetEnsureActionKind::Stop => "Stop canister",
FleetEnsureActionKind::SynchronizeRegistry => "Synchronize registry",
FleetEnsureActionKind::Transfer => "Transfer cycles",
FleetEnsureActionKind::Uninstall => "Clear code and state",
}
}
fn safe_text(value: &str) -> String {
value
.chars()
.map(|ch| if ch.is_control() { '?' } else { ch })
.collect()
}
fn pending_retry(
retry: canic_core::dto::component_provisioning::FleetComponentProvisioningRootFailure,
) -> [String; 2] {
if let Some(origin) = retry.origin {
let deadline = origin.retry_at_ns.map_or_else(
|| "retry deadline unavailable".to_owned(),
|value| format!("observed retry deadline: {}", utc_timestamp_ns(value)),
);
[
format!(
"Pending owner {}: {:?}, code {}, {:?}",
origin.target, origin.stage, origin.diagnostic_code, origin.retry_category,
),
deadline,
]
} else {
[
format!(
"Root {}: {:?}, code {}; originating owner unavailable",
retry.fleet_subnet_root, retry.stage, retry.diagnostic_code
),
"retry deadline unavailable".to_owned(),
]
}
}