Skip to main content

ic_memory/ledger/
record.rs

1#[cfg(any(test, feature = "test-support"))]
2use super::LedgerIntegrityError;
3use crate::{declaration::AllocationDeclaration, key::StableKey, slot::MemoryManagerSlot};
4use serde::{Deserialize, Serialize};
5
6///
7/// AllocationLedger
8///
9/// Durable ownership, bounded by the usable memory-ID
10/// domain. Omitted identities retain their slots; no per-upgrade or
11/// application schema state is stored.
12///
13/// The counter binds logical generations to physical commits. Decoded DTOs remain
14/// untrusted until integrity validation and protected recovery succeed.
15///
16
17#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
18#[serde(deny_unknown_fields)]
19pub struct AllocationLedger {
20    pub(crate) current_generation: u64,
21    #[serde(deserialize_with = "crate::cbor::deserialize_records")]
22    pub(crate) records: Vec<AllocationRecord>,
23}
24
25///
26/// AllocationRecord
27///
28/// Durable key-to-slot ownership for one stable
29/// key. Retaining this record prevents slot reuse when its store is omitted.
30/// It is metadata, not a live memory handle or an upgrade audit log.
31///
32
33#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
34#[serde(deny_unknown_fields)]
35pub struct AllocationRecord {
36    pub(crate) stable_key: StableKey,
37    pub(crate) slot: MemoryManagerSlot,
38}
39
40///
41/// RecoveredLedger
42///
43/// Validated base for declaration checks: either a recovered ledger that passed
44/// current-format decoding, integrity checks and physical/logical counter binding,
45/// or empty logical genesis admitted only by explicit snapshot absence. Genesis
46/// is not a persisted generation. A decoded ledger alone is a passive DTO.
47///
48
49#[derive(Clone, Debug, Eq, PartialEq)]
50pub struct RecoveredLedger {
51    ledger: AllocationLedger,
52}
53
54impl RecoveredLedger {
55    // Protected recovery alone establishes absence. All present-snapshot
56    // failures remain failures for bootstrap and diagnostic validation alike.
57    pub(crate) fn genesis_for_absent_snapshot(error: &super::LedgerCommitError) -> Option<Self> {
58        matches!(
59            error,
60            super::LedgerCommitError::Recovery(crate::CommitRecoveryError::NoValidGeneration)
61        )
62        .then(|| Self::from_trusted_ledger(AllocationLedger::empty_genesis()))
63    }
64
65    pub(crate) const fn from_trusted_ledger(ledger: AllocationLedger) -> Self {
66        Self { ledger }
67    }
68
69    /// Borrow the recovered metadata, without granting open authority.
70    #[must_use]
71    pub const fn ledger(&self) -> &AllocationLedger {
72        &self.ledger
73    }
74
75    /// Return the bound recovered counter, or zero for uncommitted logical genesis.
76    #[must_use]
77    #[cfg(test)]
78    pub const fn current_generation(&self) -> u64 {
79        self.ledger.current_generation
80    }
81
82    pub(crate) fn into_ledger(self) -> AllocationLedger {
83        self.ledger
84    }
85}
86
87impl AllocationRecord {
88    pub(crate) fn from_declaration(declaration: &AllocationDeclaration) -> Self {
89        Self {
90            stable_key: declaration.stable_key.clone(),
91            slot: declaration.slot.clone(),
92        }
93    }
94
95    /// Return the permanent store identity.
96    #[must_use]
97    pub const fn stable_key(&self) -> &StableKey {
98        &self.stable_key
99    }
100
101    /// Return its permanently claimed memory ID.
102    #[must_use]
103    pub const fn slot(&self) -> &MemoryManagerSlot {
104        &self.slot
105    }
106}
107
108impl AllocationLedger {
109    pub(crate) const fn empty_genesis() -> Self {
110        Self {
111            current_generation: 0,
112            records: Vec::new(),
113        }
114    }
115
116    /// Build a ledger DTO after validating count, ownership and genesis rules.
117    /// Recovery must still establish the persisted format and physical binding.
118    #[cfg(any(test, feature = "test-support"))]
119    pub fn new(
120        current_generation: u64,
121        records: Vec<AllocationRecord>,
122    ) -> Result<Self, LedgerIntegrityError> {
123        let ledger = Self {
124            current_generation,
125            records,
126        };
127        ledger.validate_integrity()?;
128        Ok(ledger)
129    }
130
131    /// Return the current commit counter; this is not retained upgrade history.
132    #[must_use]
133    pub const fn current_generation(&self) -> u64 {
134        self.current_generation
135    }
136
137    /// Borrow permanent ownership records, including omitted stores.
138    #[must_use]
139    pub fn records(&self) -> &[AllocationRecord] {
140        &self.records
141    }
142
143    // Every retained binding owns its ID, including omitted keys.
144    pub(crate) fn occupied_slots(&self) -> [bool; crate::constants::MAX_ALLOCATIONS] {
145        let mut occupied = [false; crate::constants::MAX_ALLOCATIONS];
146        for record in &self.records {
147            occupied[usize::from(record.slot.id())] = true;
148        }
149        occupied
150    }
151}