canic_host/install_root/deployment_registration/
mod.rs1use super::clock::current_unix_secs;
2use super::root_verification::{
3 RootVerificationReceiptInput, deployment_root_verification_state, file_sha256_hex,
4 root_verification_receipt_from_report, verified_root_state_transition,
5 write_verified_root_state_if_unchanged,
6};
7use super::state::{
8 INSTALL_STATE_SCHEMA_VERSION, InstallState, RootVerificationStatus,
9 deployment_install_state_path, read_deployment_install_state, validate_network_name,
10 validate_state_name, write_install_state,
11};
12use crate::deployment_truth::{
13 DeploymentCheckV1, DeploymentRootVerificationEvidenceStatusV1,
14 DeploymentRootVerificationReceiptV1, DeploymentRootVerificationRequestV1,
15 DeploymentRootVerificationSourceV1, DeploymentRootVerificationStateV1,
16 deployment_root_verification_report_from_check, validate_deployment_root_verification_report,
17};
18use crate::release_set::{
19 icp_root, resolve_artifact_root, root_release_set_manifest_path, workspace_root,
20};
21use canic_core::cdk::types::Principal;
22use std::path::{Path, PathBuf};
23
24#[derive(Clone, Debug, Eq, PartialEq)]
29pub struct RegisterDeploymentStateOptions {
30 pub deployment_name: String,
31 pub fleet_template: String,
32 pub root_canister_id: String,
33 pub network: String,
34 pub allow_unverified: bool,
35 pub icp_root: Option<PathBuf>,
36 pub workspace_root: Option<PathBuf>,
37}
38
39#[derive(Clone, Debug, Eq, PartialEq)]
44pub struct VerifyDeploymentRootOptions {
45 pub deployment_name: String,
46 pub network: String,
47 pub deployment_check: DeploymentCheckV1,
48 pub verified_at_unix_secs: Option<u64>,
49 pub icp_root: Option<PathBuf>,
50}
51
52pub fn register_deployment_state(
58 options: RegisterDeploymentStateOptions,
59) -> Result<PathBuf, Box<dyn std::error::Error>> {
60 validate_state_name(&options.deployment_name)?;
61 validate_state_name(&options.fleet_template)?;
62 validate_network_name(&options.network)?;
63 if !options.allow_unverified {
64 return Err(
65 "deployment registration requires explicit unverified-root acknowledgement; pass --allow-unverified"
66 .into(),
67 );
68 }
69 Principal::from_text(&options.root_canister_id).map_err(|err| {
70 format!(
71 "invalid root principal for deployment {}: {err}",
72 options.deployment_name
73 )
74 })?;
75
76 let workspace_root = match options.workspace_root {
77 Some(path) => path,
78 None => workspace_root()?,
79 };
80 let icp_root = match options.icp_root {
81 Some(path) => path,
82 None => icp_root()?,
83 };
84 let release_set_manifest_path =
85 registered_deployment_release_set_manifest_path(&icp_root, &options.network);
86 let timestamp = current_unix_secs()?;
87 let state = InstallState {
88 schema_version: INSTALL_STATE_SCHEMA_VERSION,
89 deployment_name: options.deployment_name,
90 fleet_template: options.fleet_template.clone(),
91 created_at_unix_secs: timestamp,
92 updated_at_unix_secs: timestamp,
93 network: options.network.clone(),
94 root_target: options.root_canister_id.clone(),
95 root_canister_id: options.root_canister_id,
96 root_verification: RootVerificationStatus::NotVerified,
97 root_build_target: "root".to_string(),
98 workspace_root: workspace_root.display().to_string(),
99 icp_root: icp_root.display().to_string(),
100 config_path: workspace_root
101 .join("fleets")
102 .join(&options.fleet_template)
103 .join("canic.toml")
104 .display()
105 .to_string(),
106 release_set_manifest_path: release_set_manifest_path.display().to_string(),
107 };
108
109 write_install_state(&icp_root, &options.network, &state)
110}
111
112pub fn verify_registered_deployment_root(
115 options: VerifyDeploymentRootOptions,
116) -> Result<DeploymentRootVerificationReceiptV1, Box<dyn std::error::Error>> {
117 validate_state_name(&options.deployment_name)?;
118 validate_network_name(&options.network)?;
119 let verified_at_unix_secs = match options.verified_at_unix_secs {
120 Some(value) => value,
121 None => current_unix_secs()?,
122 };
123 let icp_root = match options.icp_root {
124 Some(path) => path,
125 None => icp_root()?,
126 };
127 let state_path =
128 deployment_install_state_path(&icp_root, &options.network, &options.deployment_name);
129 let state =
130 read_deployment_install_state(&icp_root, &options.network, &options.deployment_name)?
131 .ok_or_else(|| {
132 format!(
133 "no local deployment state exists for {}; run canic deploy register first",
134 options.deployment_name
135 )
136 })?;
137 let state_fleet_template = state.fleet_template.clone();
138 let state_root_canister_id = state.root_canister_id.clone();
139 let local_state_digest_before = file_sha256_hex(&state_path)?;
140 let previous_root_verification = deployment_root_verification_state(&state.root_verification);
141 let report =
142 deployment_root_verification_report_from_check(DeploymentRootVerificationRequestV1 {
143 report_id: format!(
144 "local:{}:{}:root-verification-report",
145 options.network, options.deployment_name
146 ),
147 requested_at: format!("unix:{verified_at_unix_secs}"),
148 deployment_name: options.deployment_name.clone(),
149 network: options.network.clone(),
150 expected_fleet_template: state.fleet_template.clone(),
151 expected_root_principal: state.root_canister_id.clone(),
152 current_root_verification: previous_root_verification,
153 source: DeploymentRootVerificationSourceV1::DeploymentTruthCheck,
154 deployment_check: options.deployment_check,
155 });
156 validate_deployment_root_verification_report(&report)?;
157 if report.evidence_status != DeploymentRootVerificationEvidenceStatusV1::EvidenceSatisfied {
158 return Err(format!(
159 "deployment root verification failed for {}: {} blocker(s)",
160 options.deployment_name,
161 report.blockers.len()
162 )
163 .into());
164 }
165 let state_transition = verified_root_state_transition(previous_root_verification);
166 let local_state_digest_after = match previous_root_verification {
167 DeploymentRootVerificationStateV1::NotVerified => {
168 let mut verified_state = state;
169 verified_state.root_verification = RootVerificationStatus::Verified;
170 verified_state.updated_at_unix_secs = verified_at_unix_secs;
171 write_verified_root_state_if_unchanged(
172 &icp_root,
173 &options.network,
174 &verified_state,
175 &local_state_digest_before,
176 )?
177 }
178 DeploymentRootVerificationStateV1::Verified => file_sha256_hex(&state_path)?,
179 };
180
181 root_verification_receipt_from_report(RootVerificationReceiptInput {
182 deployment_name: options.deployment_name,
183 network: options.network,
184 fleet_template: state_fleet_template,
185 root_principal: state_root_canister_id,
186 previous_root_verification,
187 state_transition,
188 report,
189 verified_at_unix_secs,
190 local_state_path: state_path.display().to_string(),
191 local_state_digest_before,
192 local_state_digest_after,
193 })
194}
195
196fn registered_deployment_release_set_manifest_path(icp_root: &Path, network: &str) -> PathBuf {
197 let artifact_root = resolve_artifact_root(icp_root, network)
198 .unwrap_or_else(|_| icp_root.join(".icp").join(network).join("canisters"));
199 root_release_set_manifest_path(&artifact_root)
200 .unwrap_or_else(|_| artifact_root.join("root").join("root.release-set.json"))
201}