Skip to main content

ic_memory/
declaration.rs

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