Skip to main content

ic_memory/
declaration.rs

1use crate::{
2    key::{StableKey, StableKeyError},
3    schema::SchemaMetadata,
4    slot::{MemoryManagerSlot, MemoryManagerSlotError},
5};
6use serde::{Deserialize, Deserializer, Serialize, de::Error as _};
7
8///
9/// AllocationDeclaration
10///
11/// Checked runtime claim that a stable key should own an allocation slot.
12///
13/// Declarations are supplied by the current binary before opening storage.
14/// Construction and decoding check the stable key, slot and schema
15/// metadata. A declaration becomes authoritative only after validation against
16/// the recovered ledger and commitment in a generation.
17///
18
19#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
20#[serde(deny_unknown_fields)]
21pub struct AllocationDeclaration {
22    /// Durable stable key.
23    pub(crate) stable_key: StableKey,
24    /// Claimed allocation slot.
25    pub(crate) slot: MemoryManagerSlot,
26    /// Optional diagnostic schema metadata.
27    pub(crate) schema: SchemaMetadata,
28}
29
30impl AllocationDeclaration {
31    /// Build a declaration from raw parts after validating diagnostic metadata.
32    pub fn new(
33        stable_key: impl AsRef<str>,
34        slot: MemoryManagerSlot,
35        schema: SchemaMetadata,
36    ) -> Result<Self, DeclarationSnapshotError> {
37        let stable_key = StableKey::parse(stable_key).map_err(DeclarationSnapshotError::Key)?;
38        Ok(Self {
39            stable_key,
40            slot,
41            schema,
42        })
43    }
44
45    /// Build a checked `MemoryManager` declaration with default schema metadata.
46    pub fn memory_manager(
47        stable_key: impl AsRef<str>,
48        id: u8,
49    ) -> Result<Self, DeclarationSnapshotError> {
50        Self::memory_manager_with_schema(stable_key, id, SchemaMetadata::default())
51    }
52
53    /// Build a checked `MemoryManager` declaration with schema metadata.
54    pub fn memory_manager_with_schema(
55        stable_key: impl AsRef<str>,
56        id: u8,
57        schema: SchemaMetadata,
58    ) -> Result<Self, DeclarationSnapshotError> {
59        let slot =
60            MemoryManagerSlot::new(id).map_err(DeclarationSnapshotError::MemoryManagerSlot)?;
61        Self::new(stable_key, slot, schema)
62    }
63
64    /// Return the durable stable key claimed by this declaration.
65    #[must_use]
66    pub const fn stable_key(&self) -> &StableKey {
67        &self.stable_key
68    }
69
70    /// Return the allocation slot claimed by this declaration.
71    #[must_use]
72    pub const fn slot(&self) -> &MemoryManagerSlot {
73        &self.slot
74    }
75
76    /// Return the optional schema metadata.
77    #[must_use]
78    pub const fn schema(&self) -> &SchemaMetadata {
79        &self.schema
80    }
81}
82
83///
84/// DeclarationSnapshot
85///
86/// Immutable runtime declaration snapshot ready for policy and ownership validation.
87///
88/// Construction and decoding enforce the declaration count and unique keys and
89/// slots. A snapshot is not permission to open storage.
90/// [`crate::MemoryRuntime::bootstrap`] owns validation and persistence before
91/// publishing committed allocation authority.
92///
93
94#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
95pub struct DeclarationSnapshot {
96    /// Runtime declarations.
97    declarations: Vec<AllocationDeclaration>,
98}
99
100impl<'de> Deserialize<'de> for DeclarationSnapshot {
101    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
102        #[derive(Deserialize)]
103        #[serde(rename = "DeclarationSnapshot", deny_unknown_fields)]
104        struct Snapshot {
105            #[serde(deserialize_with = "crate::cbor::deserialize_records")]
106            declarations: Vec<AllocationDeclaration>,
107        }
108
109        let snapshot = Snapshot::deserialize(deserializer)?;
110        Self::new(snapshot.declarations).map_err(D::Error::custom)
111    }
112}
113
114impl DeclarationSnapshot {
115    /// Create and validate a declaration snapshot.
116    pub fn new(declarations: Vec<AllocationDeclaration>) -> Result<Self, DeclarationSnapshotError> {
117        validate_declaration_set(&declarations)?;
118        Ok(Self { declarations })
119    }
120
121    // Runtime resolution owns this order; public construction and decoding keep
122    // their original traversal and error precedence. Share identity checks below.
123    pub(crate) fn canonicalize(
124        mut declarations: Vec<AllocationDeclaration>,
125    ) -> Result<Self, DeclarationSnapshotError> {
126        if declarations.len() > crate::constants::MAX_ALLOCATIONS {
127            return Err(DeclarationSnapshotError::TooManyDeclarations);
128        }
129        crate::sort::sort_unstable_by(&mut declarations, |a, b| a.stable_key.cmp(&b.stable_key));
130        let mut duplicate_keys = [false; crate::constants::MAX_ALLOCATIONS];
131        for (index, pair) in declarations.windows(2).enumerate() {
132            duplicate_keys[index + 1] = pair[0].stable_key == pair[1].stable_key;
133        }
134        validate_declaration_identities(&declarations, &duplicate_keys)?;
135        Ok(Self { declarations })
136    }
137
138    /// Return true when the snapshot has no declarations.
139    #[must_use]
140    pub const fn is_empty(&self) -> bool {
141        self.declarations.is_empty()
142    }
143
144    /// Return the number of declarations in the snapshot.
145    #[must_use]
146    pub const fn len(&self) -> usize {
147        self.declarations.len()
148    }
149
150    /// Borrow the sealed declarations.
151    #[must_use]
152    pub fn declarations(&self) -> &[AllocationDeclaration] {
153        &self.declarations
154    }
155
156    pub(crate) fn into_declarations(self) -> Vec<AllocationDeclaration> {
157        self.declarations
158    }
159}
160
161///
162/// DeclarationSnapshotError
163///
164/// Declaration snapshot validation failure.
165#[non_exhaustive]
166#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
167pub enum DeclarationSnapshotError {
168    #[error("at most 255 allocation declarations are supported")]
169    TooManyDeclarations,
170    /// Stable-key grammar failure.
171    #[error(transparent)]
172    Key(StableKeyError),
173    /// `MemoryManager` slot validation failure.
174    #[error(transparent)]
175    MemoryManagerSlot(MemoryManagerSlotError),
176    /// A stable key appeared more than once in one snapshot.
177    #[error("stable key '{0}' is declared more than once")]
178    DuplicateStableKey(StableKey),
179    /// An allocation slot appeared more than once in one snapshot.
180    #[error("allocation slot '{0:?}' is declared more than once")]
181    DuplicateSlot(MemoryManagerSlot),
182}
183
184fn validate_declaration_set(
185    declarations: &[AllocationDeclaration],
186) -> Result<(), DeclarationSnapshotError> {
187    if declarations.len() > crate::constants::MAX_ALLOCATIONS {
188        return Err(DeclarationSnapshotError::TooManyDeclarations);
189    }
190    let duplicate_keys = crate::key::duplicate_key_positions(
191        declarations
192            .iter()
193            .enumerate()
194            .map(|(index, declaration)| (&declaration.stable_key, index))
195            .collect(),
196    );
197    validate_declaration_identities(declarations, &duplicate_keys)
198}
199
200fn validate_declaration_identities(
201    declarations: &[AllocationDeclaration],
202    duplicate_keys: &[bool; crate::constants::MAX_ALLOCATIONS],
203) -> Result<(), DeclarationSnapshotError> {
204    let mut slots = [false; crate::constants::MAX_ALLOCATIONS];
205
206    for (index, declaration) in declarations.iter().enumerate() {
207        let occupied = &mut slots[usize::from(declaration.slot.id())];
208        if *occupied {
209            return Err(DeclarationSnapshotError::DuplicateSlot(
210                declaration.slot.clone(),
211            ));
212        }
213        *occupied = true;
214        if duplicate_keys[index] {
215            return Err(DeclarationSnapshotError::DuplicateStableKey(
216                declaration.stable_key.clone(),
217            ));
218        }
219    }
220
221    Ok(())
222}
223
224#[cfg(test)]
225mod tests {
226    use super::*;
227    use crate::slot::MemoryManagerSlot;
228
229    fn declaration(key: &str, id: u8) -> AllocationDeclaration {
230        AllocationDeclaration::new(
231            key,
232            MemoryManagerSlot::new(id).expect("usable slot"),
233            SchemaMetadata::default(),
234        )
235        .expect("declaration")
236    }
237
238    #[test]
239    fn memory_manager_declaration_constructor_builds_common_declaration() {
240        let declaration =
241            AllocationDeclaration::memory_manager("app.orders.v1", 100).expect("declaration");
242
243        assert_eq!(declaration.stable_key.as_str(), "app.orders.v1");
244        assert_eq!(
245            declaration.slot,
246            MemoryManagerSlot::new(100).expect("usable slot")
247        );
248        assert_eq!(declaration.schema, SchemaMetadata::default());
249    }
250
251    #[test]
252    fn memory_manager_declaration_constructor_rejects_invalid_slot() {
253        let err = AllocationDeclaration::memory_manager("app.orders.v1", u8::MAX)
254            .expect_err("sentinel must fail");
255
256        assert!(matches!(
257            err,
258            DeclarationSnapshotError::MemoryManagerSlot(_)
259        ));
260    }
261
262    #[test]
263    fn snapshot_decode_rejects_unusable_memory_manager_slot() {
264        let snapshot = DeclarationSnapshot::new(vec![declaration("app.orders.v1", 100)]).unwrap();
265        let mut value = serde_json::to_value(snapshot).unwrap();
266        value["declarations"][0]["slot"] = serde_json::json!(255);
267        assert!(serde_json::from_value::<DeclarationSnapshot>(value).is_err());
268    }
269
270    #[test]
271    fn rejects_duplicate_keys() {
272        let err = DeclarationSnapshot::new(vec![
273            declaration("app.users.v1", 100),
274            declaration("app.users.v1", 101),
275        ])
276        .expect_err("duplicate key");
277
278        assert_eq!(
279            err,
280            DeclarationSnapshotError::DuplicateStableKey(StableKey::parse("app.users.v1").unwrap())
281        );
282    }
283
284    #[test]
285    fn rejects_duplicate_slots() {
286        for second_key in ["app.orders.v1", "app.users.v1"] {
287            let err = DeclarationSnapshot::new(vec![
288                declaration("app.users.v1", 100),
289                declaration(second_key, 100),
290            ])
291            .expect_err("duplicate slot precedes duplicate key");
292
293            assert_eq!(
294                err,
295                DeclarationSnapshotError::DuplicateSlot(MemoryManagerSlot::new(100).unwrap())
296            );
297        }
298    }
299
300    #[test]
301    fn canonical_resolution_preserves_identity_failures_and_bounds() {
302        // Compare the runtime boundary with ordinary validation in canonical
303        // traversal order, including competing duplicate-key/slot failures.
304        let choices = [
305            ("app.a.v1", 10),
306            ("app.a.v1", 11),
307            ("app.b.v1", 10),
308            ("app.b.v1", 11),
309        ];
310        for code in 0..256 {
311            let mut code = code;
312            let input: Vec<_> = (0..4)
313                .map(|_| {
314                    let (key, id) = choices[code % choices.len()];
315                    code /= choices.len();
316                    declaration(key, id)
317                })
318                .collect();
319            let mut ordered = input.clone();
320            crate::sort::sort_unstable_by(&mut ordered, |a, b| a.stable_key.cmp(&b.stable_key));
321            assert_eq!(
322                DeclarationSnapshot::canonicalize(input),
323                DeclarationSnapshot::new(ordered)
324            );
325        }
326        for count in [0, 1, 254, 255, 256] {
327            let input: Vec<_> = (0..count)
328                .rev()
329                .map(|index| {
330                    declaration(
331                        &format!("app.store{index:03}.v1"),
332                        u8::try_from(index % 255).unwrap(),
333                    )
334                })
335                .collect();
336            let result = DeclarationSnapshot::canonicalize(input);
337            if count <= 255 {
338                let snapshot = result.unwrap();
339                assert_eq!(snapshot.len(), count);
340                assert!(
341                    snapshot
342                        .declarations()
343                        .windows(2)
344                        .all(|pair| { pair[0].stable_key < pair[1].stable_key })
345                );
346            } else {
347                assert_eq!(result, Err(DeclarationSnapshotError::TooManyDeclarations));
348            }
349        }
350    }
351}