Skip to main content

ic_memory/ledger/
record.rs

1use super::{AllocationRetirementError, LedgerIntegrityError};
2use crate::{
3    declaration::AllocationDeclaration, key::StableKey, schema::SchemaMetadata,
4    slot::MemoryManagerSlot,
5};
6use serde::{Deserialize, Serialize};
7
8///
9/// AllocationLedger
10///
11/// Durable ownership and current metadata, bounded by the usable memory-ID
12/// domain. Omitted and retired identities retain their slots; no per-upgrade or
13/// schema audit trail is stored.
14///
15/// The counter binds validation proofs to physical commits. Decoded DTOs remain
16/// untrusted until integrity validation and protected recovery succeed.
17///
18
19#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
20#[serde(deny_unknown_fields)]
21pub struct AllocationLedger {
22    pub(crate) current_generation: u64,
23    #[serde(deserialize_with = "crate::cbor::deserialize_records")]
24    pub(crate) records: Vec<AllocationRecord>,
25}
26
27///
28/// AllocationRecord
29///
30/// Durable ownership, lifecycle state and latest schema metadata for one stable
31/// key. Retaining this record prevents slot reuse after omission or retirement.
32/// It is metadata, not a live memory handle or an upgrade audit log.
33///
34
35#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
36#[serde(deny_unknown_fields)]
37pub struct AllocationRecord {
38    pub(crate) stable_key: StableKey,
39    pub(crate) slot: MemoryManagerSlot,
40    pub(crate) state: AllocationState,
41    pub(crate) schema: SchemaMetadata,
42}
43
44///
45/// AllocationState
46///
47/// Current allocation lifecycle state. Retirement permanently retains the
48/// identity and slot rather than adding a historical event or freeing the ID.
49///
50
51#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
52#[serde(deny_unknown_fields)]
53pub enum AllocationState {
54    /// Slot is reserved for a future allocation identity.
55    Reserved,
56    /// Slot is active and may be opened after validation.
57    Active,
58    /// Identity and slot are permanently tombstoned.
59    Retired,
60}
61
62///
63/// AllocationRetirement
64///
65/// Explicit request to tombstone one retained allocation identity. Retirement
66/// prevents redeclaration and never frees the physical slot for another key.
67///
68
69#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
70#[serde(deny_unknown_fields)]
71pub struct AllocationRetirement {
72    pub(crate) stable_key: StableKey,
73    pub(crate) slot: MemoryManagerSlot,
74}
75
76impl AllocationRetirement {
77    /// Build an explicit retirement request from checked raw parts.
78    pub fn new(
79        stable_key: impl AsRef<str>,
80        slot: MemoryManagerSlot,
81    ) -> Result<Self, AllocationRetirementError> {
82        let stable_key = StableKey::parse(stable_key).map_err(AllocationRetirementError::Key)?;
83        Ok(Self { stable_key, slot })
84    }
85
86    /// Return the stable key being retired.
87    #[must_use]
88    pub const fn stable_key(&self) -> &StableKey {
89        &self.stable_key
90    }
91
92    /// Return the allocation slot named by the request.
93    #[must_use]
94    pub const fn slot(&self) -> &MemoryManagerSlot {
95        &self.slot
96    }
97}
98
99///
100/// RecoveredLedger
101///
102/// Proof that a ledger crossed physical recovery, current-format decoding,
103/// integrity validation and physical/logical commit-counter binding. Only this
104/// proof can feed declaration validation; a decoded ledger is a passive DTO.
105///
106
107#[derive(Clone, Debug, Eq, PartialEq)]
108pub struct RecoveredLedger {
109    ledger: AllocationLedger,
110}
111
112impl RecoveredLedger {
113    pub(crate) const fn from_trusted_ledger(ledger: AllocationLedger) -> Self {
114        Self { ledger }
115    }
116
117    /// Borrow the recovered metadata, without granting open authority.
118    #[must_use]
119    pub const fn ledger(&self) -> &AllocationLedger {
120        &self.ledger
121    }
122
123    /// Return the recovered commit counter, checked against both physical and logical records.
124    #[must_use]
125    pub const fn current_generation(&self) -> u64 {
126        self.ledger.current_generation
127    }
128
129    pub(crate) fn into_ledger(self) -> AllocationLedger {
130        self.ledger
131    }
132}
133
134impl AllocationRecord {
135    fn from_declaration(declaration: &AllocationDeclaration, state: AllocationState) -> Self {
136        Self {
137            stable_key: declaration.stable_key.clone(),
138            slot: declaration.slot.clone(),
139            state,
140            schema: declaration.schema.clone(),
141        }
142    }
143
144    pub(crate) fn active(declaration: &AllocationDeclaration) -> Self {
145        Self::from_declaration(declaration, AllocationState::Active)
146    }
147
148    pub(crate) fn reserved(declaration: &AllocationDeclaration) -> Self {
149        Self::from_declaration(declaration, AllocationState::Reserved)
150    }
151
152    /// Return the permanent store identity.
153    #[must_use]
154    pub const fn stable_key(&self) -> &StableKey {
155        &self.stable_key
156    }
157
158    /// Return its permanently claimed memory ID.
159    #[must_use]
160    pub const fn slot(&self) -> &MemoryManagerSlot {
161        &self.slot
162    }
163
164    /// Return the current lifecycle state.
165    #[must_use]
166    pub const fn state(&self) -> AllocationState {
167        self.state
168    }
169
170    /// Borrow the latest declared schema metadata; no schema history is kept.
171    #[must_use]
172    pub const fn schema(&self) -> &SchemaMetadata {
173        &self.schema
174    }
175}
176
177impl AllocationLedger {
178    pub(crate) const fn empty_genesis() -> Self {
179        Self {
180            current_generation: 0,
181            records: Vec::new(),
182        }
183    }
184
185    /// Build a ledger DTO after validating count, ownership and genesis rules.
186    /// Recovery must still establish the persisted format and physical binding.
187    pub fn new(
188        current_generation: u64,
189        records: Vec<AllocationRecord>,
190    ) -> Result<Self, LedgerIntegrityError> {
191        let ledger = Self {
192            current_generation,
193            records,
194        };
195        ledger.validate_integrity()?;
196        Ok(ledger)
197    }
198
199    /// Return the current commit counter; this is not retained upgrade history.
200    #[must_use]
201    pub const fn current_generation(&self) -> u64 {
202        self.current_generation
203    }
204
205    /// Borrow retained ownership records, including omitted and retired stores.
206    #[must_use]
207    pub fn records(&self) -> &[AllocationRecord] {
208        &self.records
209    }
210
211    // Every retained lifecycle state owns its ID, including omitted/retired keys.
212    pub(crate) fn occupied_slots(&self) -> [bool; crate::constants::MAX_ALLOCATIONS] {
213        let mut occupied = [false; crate::constants::MAX_ALLOCATIONS];
214        for record in &self.records {
215            occupied[usize::from(record.slot.id())] = true;
216        }
217        occupied
218    }
219}