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        let RuntimeLifecycle::Bootstrapped { binding, .. } = &self.lifecycle else {
58            return Err(RuntimeOpenError::NotBootstrapped.into());
59        };
60        let committed = &binding.declarations;
61        let mut found = false;
62        for registration in requirements.registered_declarations() {
63            if registration.authority() == authority {
64                found = true;
65                let expected = registration.declaration();
66                verify_requirement(
67                    committed,
68                    authority,
69                    expected.stable_key(),
70                    expected.schema(),
71                    Some(expected),
72                )?;
73            }
74        }
75        for request in requirements.requests() {
76            if request.authority() == authority {
77                found = true;
78                verify_requirement(
79                    committed,
80                    authority,
81                    request.stable_key(),
82                    request.schema(),
83                    None,
84                )?;
85            }
86        }
87        if !found {
88            return Err(RuntimeAdoptionError::UnknownAuthority {
89                authority: authority.to_string(),
90            });
91        }
92        Ok(())
93    }
94}
95
96fn verify_requirement(
97    committed: &SealedDeclarationSnapshot,
98    authority: &str,
99    key: &StableKey,
100    schema: &SchemaMetadata,
101    fixed: Option<&AllocationDeclaration>,
102) -> Result<(), RuntimeAdoptionError> {
103    let registration = committed
104        .registered_declaration(key)
105        .ok_or_else(|| RuntimeOpenError::StableKeyNotCommitted(key.to_string()))?;
106    if registration.authority() != authority {
107        return Err(RuntimeAdoptionError::AuthorityMismatch {
108            stable_key: key.to_string(),
109            committed_authority: registration.authority().to_string(),
110            requested_authority: authority.to_string(),
111        });
112    }
113    let committed = registration.declaration();
114    if let Some(expected) = fixed
115        && expected.slot() != committed.slot()
116    {
117        return Err(RuntimeOpenError::MemoryIdMismatch {
118            stable_key: key.to_string(),
119            committed_id: committed.slot().id(),
120            requested_id: expected.slot().id(),
121        }
122        .into());
123    }
124    if schema != committed.schema()
125        || fixed.is_some_and(|expected| expected.label() != committed.label())
126    {
127        return Err(RuntimeAdoptionError::DeclarationMetadataMismatch {
128            stable_key: key.to_string(),
129        });
130    }
131    Ok(())
132}