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