Skip to main content

canic_host/protocol_binding/release/
mod.rs

1//! Module: protocol_binding::release
2//!
3//! Responsibility: select infrastructure Candid from a terminal Fleet's selected release.
4//! Does not own: Fleet discovery, live admission or release production.
5//! Boundary: finalized manifest authority and terminal protocol identity must agree.
6
7#[cfg(test)]
8mod tests;
9
10use crate::{
11    protocol_binding::{
12        ProtocolBindingError, RegistryProtocolBinding, ResolvedProtocolBinding,
13        resolve_protocol_binding,
14    },
15    registry::RegistryEntry,
16    release_build::{ReleaseBuildPlanError, validate_finalized_release_build_manifest},
17    release_set::{
18        CanicInfrastructureArtifactPersistenceError, CurrentReleaseSetManifestError,
19        load_persisted_canic_infrastructure_artifact_manifest,
20        load_persisted_current_release_set_manifest,
21    },
22};
23use canic_core::ids::ReleaseBuildId;
24use std::path::{Path, PathBuf};
25use thiserror::Error;
26
27/// Failures binding a retained infrastructure participant to its immutable release.
28#[derive(Debug, Error)]
29pub enum ReleaseProtocolBindingError {
30    #[error("Canister {canister} does not match its selected release infrastructure artifact")]
31    ArtifactBindingMismatch { canister: String },
32
33    #[error(transparent)]
34    Binding(#[from] ProtocolBindingError),
35
36    #[error(transparent)]
37    Infrastructure(#[from] CanicInfrastructureArtifactPersistenceError),
38
39    #[error("selected release infrastructure manifest digest does not match finalization")]
40    ManifestDigestMismatch,
41
42    #[error(transparent)]
43    ReleaseBuild(#[from] ReleaseBuildPlanError),
44
45    #[error(transparent)]
46    ReleaseSet(#[from] CurrentReleaseSetManifestError),
47
48    #[error("selected release Candid sidecar is not a regular contained file: {}", path.display())]
49    UnsafeCandid { path: PathBuf },
50}
51
52/// Resolve an infrastructure participant using its exact retained release, before transport.
53pub fn resolve_release_registry_protocol_binding(
54    root: &Path,
55    release_build_id: ReleaseBuildId,
56    entry: &RegistryEntry,
57) -> Result<ResolvedProtocolBinding, ReleaseProtocolBindingError> {
58    let release = load_persisted_current_release_set_manifest(root, release_build_id)?;
59    validate_finalized_release_build_manifest(root, release_build_id, &release.path)?;
60    let infrastructure =
61        load_persisted_canic_infrastructure_artifact_manifest(root, release_build_id)?;
62    if infrastructure.digest != release.manifest.infrastructure_artifact_manifest_sha256 {
63        return Err(ReleaseProtocolBindingError::ManifestDigestMismatch);
64    }
65    let mismatch = || ReleaseProtocolBindingError::ArtifactBindingMismatch {
66        canister: entry.pid.clone(),
67    };
68    let binding = entry.protocol_binding.as_ref().ok_or_else(mismatch)?;
69    let artifact = infrastructure
70        .manifest
71        .entries
72        .iter()
73        .find(|artifact| entry.role.as_deref() == Some(artifact.protocol_role.as_str()))
74        .ok_or_else(mismatch)?;
75    let expected = RegistryProtocolBinding {
76        release_identity: artifact.protocol_release_identity.clone(),
77        role: artifact.protocol_role.clone(),
78        capabilities: artifact.protocol_capabilities.clone(),
79        candid_sha256: artifact.candid_sha256,
80        protocol_profile_digest: artifact.protocol_profile_digest,
81    };
82    if binding != &expected || entry.module_hash.as_deref() != Some(&artifact.wasm_sha256_hex) {
83        return Err(mismatch());
84    }
85    let candid_path = root
86        .join(&artifact.wasm_relative_path)
87        .with_extension("did");
88    require_contained_sidecar(root, &candid_path)?;
89    Ok(resolve_protocol_binding(&entry.pid, expected, candid_path)?)
90}
91
92fn require_contained_sidecar(root: &Path, path: &Path) -> Result<(), ReleaseProtocolBindingError> {
93    let unsafe_path = || ReleaseProtocolBindingError::UnsafeCandid {
94        path: path.to_path_buf(),
95    };
96    let canonical_root = root.canonicalize().map_err(|_| unsafe_path())?;
97    let parent = path.parent().ok_or_else(unsafe_path)?;
98    let canonical_parent = parent.canonicalize().map_err(|_| unsafe_path())?;
99    if !canonical_parent.starts_with(canonical_root) {
100        return Err(unsafe_path());
101    }
102    Ok(())
103}