use crate::fleet::{EnsureOptions, FleetCommandError, progress, quote_review_argument};
use std::path::Path;
use canic_host::{
fleet_ensure::{
model::completed_handoff::preparation::CompletedPreparationReviewRecord,
ops::retained_contract, workflow::completed_preparation as workflow,
},
icp::IcpCli,
};
pub(super) fn run_if_selected(
workspace: &Path,
options: &EnsureOptions,
) -> Result<bool, FleetCommandError> {
let Some(environment) = options.environment.as_deref() else {
return Ok(false);
};
if options.operator_mint || options.observe_funding.is_some() || options.cancel_mint.is_some() {
return Ok(false);
}
let retained = workflow::review(workspace, environment, &options.fleet).map_err(failure)?;
let selected = if retained.is_some() {
options.reinstall || options.apply.is_some()
} else if options.reinstall {
retained_contract::completed_source_available(workspace, environment, &options.fleet)
.map_err(|error| {
failure(workflow::CompletedPreparationError::Source(Box::new(error)))
})?
} else {
false
};
if !selected {
return Ok(false);
}
if options.retirement_debit_block.is_some() {
return Err(FleetCommandError::Usage(
"completed-source preparation permits no external Ledger debit".into(),
));
}
let session = progress::ProgressSession::new(options.json);
session.retain_receipt(
workspace,
&progress::receipt::Invocation {
command: progress::receipt::CommandKind::CompletedPreparation,
fleet: &options.fleet,
environment,
desired_sha256: None,
applied_plan_sha256: None,
applied_review_sha256: options.apply.as_deref(),
reinstall: true,
next_review_command: "",
},
);
let request_sink = session.sink();
let icp = IcpCli::new(&options.icp, Some(environment.into()))
.with_identity(options.identity.as_deref())
.with_cwd(workspace)
.with_timing_handler(move |event| request_sink.request(event));
let result = (|| -> Result<_, FleetCommandError> {
let (review, journal) = if let Some(approval) = options.apply.as_deref() {
let review =
retained.ok_or_else(|| failure(workflow::CompletedPreparationError::Conflict))?;
let journal = workflow::apply(workspace, environment, &options.fleet, approval, &icp)
.map_err(failure)?;
(review, Some(journal))
} else {
(
workflow::plan(workspace, environment, &options.fleet, &icp).map_err(failure)?,
None,
)
};
session.sink().record(
"completed_preparation_authority",
&serde_json::json!({
"review_sha256": review.review_sha256,
"source_operation_id": review.source.operation_id,
"source_plan_sha256": review.source.plan_sha256,
"prepared": journal.as_ref().is_some_and(|journal| journal.prepared),
}),
);
Ok((review, journal))
})();
session.finish_without_report(result.is_ok());
drop(session);
let (review, journal) = result?;
render_result(options, &review, journal.as_ref())?;
Ok(true)
}
fn render_result(
options: &EnsureOptions,
review: &CompletedPreparationReviewRecord,
journal: Option<&canic_host::fleet_ensure::model::completed_handoff::preparation::CompletedPreparationJournalRecord>,
) -> Result<(), FleetCommandError> {
let command = apply_command(options, review);
if options.json {
println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
"schema_version": 1,
"stage": "completed_source_preparation",
"review": review,
"journal": journal,
"apply_command": command,
"maximum_operator_debit_cycles": "0",
"deployment_complete": false,
}))?
);
} else {
println!("{}", render(review, journal.is_some(), &command));
}
Ok(())
}
fn failure(error: workflow::CompletedPreparationError) -> FleetCommandError {
FleetCommandError::CompletedPreparation(Box::new(error))
}
fn apply_command(options: &EnsureOptions, review: &CompletedPreparationReviewRecord) -> String {
let mut command = format!(
"canic --environment {} --icp {} fleet ensure {}",
quote_review_argument(&review.environment),
quote_review_argument(&options.icp),
quote_review_argument(&review.fleet),
);
if let Some(identity) = options.identity.as_deref() {
command.push_str(" --identity ");
command.push_str("e_review_argument(identity));
}
command.push_str(" --apply ");
command.push_str(&review.review_sha256);
command
}
fn render(review: &CompletedPreparationReviewRecord, prepared: bool, command: &str) -> String {
let mut lines = vec![
format!(
"Fleet {}: completed-source authority preparation {}",
review.fleet,
if prepared { "complete" } else { "review" }
),
format!("Review: {}", review.review_sha256),
format!("Source operation: {}", review.source.operation_id),
format!(
"Source documents: plan={} journal={} state={}",
review.source.plan_document_sha256,
review.source.journal_document_sha256,
review.source.state_document_sha256
),
format!(
"Maximum operator debit: 0 cycles; maximum observed execution burn: {} cycles",
review.maximum_execution_burn_cycles
),
format!(
"Per authority: at most {} seal submissions and {} management observations",
review.maximum_attempts_per_action, review.maximum_observations_per_action
),
];
for action in &review.actions {
if let canic_host::fleet_ensure::model::EnsureAction::SealAuthority {
name,
principal,
candid_sha256,
..
} = action
{
let binding = &review.custody.canisters[name].binding;
lines.push(format!(
"Seal {name}: {principal}; interface={candid_sha256}; controllers={}",
binding
.controllers
.iter()
.map(candid::Principal::to_text)
.collect::<Vec<_>>()
.join(",")
));
}
}
for (name, root) in &review.inspection_roots {
let binding = &review.custody.canisters[name].binding;
lines.push(format!(
"Inspect {name}: {}; subnet={}; module={}; via={}; maximum attempts={}",
binding.principal,
binding.subnet,
binding.module_sha256.as_deref().unwrap_or("empty"),
root.as_deref().unwrap_or("operator management authority"),
review.maximum_attempts_per_action,
));
}
lines.push(format!(
"Original native baseline: {}; receipted funding: {}; original source burn ceiling: {} cycles. Reserved cycles and other Ledger accounts do not offset missing native funds.",
review.source_accounting.initial_native_cycles,
review.source_accounting.recorded_funding_cycles,
review.source_accounting.maximum_source_burn_cycles,
));
lines.push("Original evidence is archived unchanged. This stage seals Coordinator/Root authority; it authorizes no payments, controller handoffs, installs or wipes. Application canisters continue running. Deployment is not complete.".into());
lines.push(format!(
"{}: {command}",
if prepared {
"Effect-free preparation replay"
} else {
"Apply reviewed preparation"
}
));
lines.join("\n")
}