Skip to main content

ic_memory/runtime/
adoption.rs

1use super::{MemoryRuntime, RuntimeLifecycle, RuntimeOpenError};
2use crate::{AllocationDeclaration, SchemaMetadata, SealedDeclarationSnapshot, StableKey};
3use ic_stable_structures::Memory;
4
5///
6/// RuntimeAdoptionError
7///
8/// A consumer's declared requirements do not match the existing committed
9/// runtime. Verification never bootstraps, grants authority, opens memory,
10/// replays admission, or changes the host's policy or bucket configuration.
11///
12
13#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
14#[non_exhaustive]
15pub enum RuntimeAdoptionError {
16    /// Runtime readiness, key resolution or fixed-ID verification failed.
17    #[error(transparent)]
18    Open(#[from] RuntimeOpenError),
19    /// The supplied snapshot contains no fixed declarations or requests for this authority.
20    #[error("no allocation requirements declared by authority '{authority}'")]
21    UnknownAuthority { authority: String },
22    /// This key was committed under a different current declaration authority.
23    #[error(
24        "stable key '{stable_key}' is committed under '{committed_authority}', not '{requested_authority}'"
25    )]
26    AuthorityMismatch {
27        stable_key: String,
28        committed_authority: String,
29        requested_authority: String,
30    },
31    /// Declared diagnostic schema or fixed-declaration label differs from the commitment.
32    #[error("declaration metadata for stable key '{stable_key}' differs from the commitment")]
33    DeclarationMetadataMismatch { stable_key: String },
34}
35
36impl From<super::RuntimeStateError> for RuntimeAdoptionError {
37    fn from(error: super::RuntimeStateError) -> Self {
38        Self::Open(RuntimeOpenError::State(error))
39    }
40}
41
42impl<M: Memory> MemoryRuntime<M> {
43    /// Verify every fixed declaration and logical request for one authority in
44    /// the supplied requirements against this runtime's current commitment.
45    ///
46    /// Fixed declarations must match key, ID, label and diagnostic schema;
47    /// logical requests must match key, authority and diagnostic schema while
48    /// retaining the host's assigned ID. Other authorities and additional
49    /// committed keys are ignored. An authority with no requirements rejects.
50    /// Grants and application schema semantics are not revalidated. Success
51    /// neither grants new access nor proves application lifecycle readiness.
52    pub fn verify_authority(
53        &self,
54        requirements: &SealedDeclarationSnapshot,
55        authority: &str,
56    ) -> Result<(), RuntimeAdoptionError> {
57        self.committed_allocations()?;
58        let mut found = false;
59        for registration in requirements.registered_declarations() {
60            if registration.authority() == authority {
61                found = true;
62                let expected = registration.declaration();
63                self.verify_requirement(
64                    authority,
65                    expected.stable_key(),
66                    expected.schema(),
67                    Some(expected),
68                )?;
69            }
70        }
71        for request in requirements.requests() {
72            if request.authority() == authority {
73                found = true;
74                self.verify_requirement(authority, request.stable_key(), request.schema(), None)?;
75            }
76        }
77        if !found {
78            return Err(RuntimeAdoptionError::UnknownAuthority {
79                authority: authority.to_string(),
80            });
81        }
82        Ok(())
83    }
84
85    fn verify_requirement(
86        &self,
87        authority: &str,
88        key: &StableKey,
89        schema: &SchemaMetadata,
90        fixed: Option<&AllocationDeclaration>,
91    ) -> Result<(), RuntimeAdoptionError> {
92        let RuntimeLifecycle::Bootstrapped { binding, .. } = &self.lifecycle else {
93            return Err(RuntimeOpenError::NotBootstrapped.into());
94        };
95        let registration = binding
96            .declarations
97            .registered_declarations()
98            .iter()
99            .find(|registration| registration.declaration().stable_key() == key)
100            .ok_or_else(|| RuntimeOpenError::StableKeyNotCommitted(key.to_string()))?;
101        if registration.authority() != authority {
102            return Err(RuntimeAdoptionError::AuthorityMismatch {
103                stable_key: key.to_string(),
104                committed_authority: registration.authority().to_string(),
105                requested_authority: authority.to_string(),
106            });
107        }
108        let committed = registration.declaration();
109        if let Some(expected) = fixed
110            && expected.slot() != committed.slot()
111        {
112            return Err(RuntimeOpenError::MemoryIdMismatch {
113                stable_key: key.to_string(),
114                committed_id: committed
115                    .slot()
116                    .memory_manager_id()
117                    .map_err(RuntimeOpenError::from)?,
118                requested_id: expected
119                    .slot()
120                    .memory_manager_id()
121                    .map_err(RuntimeOpenError::from)?,
122            }
123            .into());
124        }
125        if schema != committed.schema()
126            || fixed.is_some_and(|expected| expected.label() != committed.label())
127        {
128            return Err(RuntimeAdoptionError::DeclarationMetadataMismatch {
129                stable_key: key.to_string(),
130            });
131        }
132        Ok(())
133    }
134}