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 checked physical commit counter.
124    #[must_use]
125    pub const fn physical_generation(&self) -> u64 {
126        self.ledger.current_generation
127    }
128
129    /// Return the matching logical commit counter.
130    #[must_use]
131    pub const fn current_generation(&self) -> u64 {
132        self.ledger.current_generation
133    }
134
135    pub(crate) fn into_ledger(self) -> AllocationLedger {
136        self.ledger
137    }
138}
139
140impl AllocationRecord {
141    fn from_declaration(declaration: &AllocationDeclaration, state: AllocationState) -> Self {
142        Self {
143            stable_key: declaration.stable_key.clone(),
144            slot: declaration.slot.clone(),
145            state,
146            schema: declaration.schema.clone(),
147        }
148    }
149
150    pub(crate) fn active(declaration: &AllocationDeclaration) -> Self {
151        Self::from_declaration(declaration, AllocationState::Active)
152    }
153
154    pub(crate) fn reserved(declaration: &AllocationDeclaration) -> Self {
155        Self::from_declaration(declaration, AllocationState::Reserved)
156    }
157
158    /// Return the permanent store identity.
159    #[must_use]
160    pub const fn stable_key(&self) -> &StableKey {
161        &self.stable_key
162    }
163
164    /// Return its permanently claimed memory ID.
165    #[must_use]
166    pub const fn slot(&self) -> &MemoryManagerSlot {
167        &self.slot
168    }
169
170    /// Return the current lifecycle state.
171    #[must_use]
172    pub const fn state(&self) -> AllocationState {
173        self.state
174    }
175
176    /// Borrow the latest declared schema metadata; no schema history is kept.
177    #[must_use]
178    pub const fn schema(&self) -> &SchemaMetadata {
179        &self.schema
180    }
181}
182
183impl AllocationLedger {
184    pub(crate) const fn empty_genesis() -> Self {
185        Self {
186            current_generation: 0,
187            records: Vec::new(),
188        }
189    }
190
191    /// Build a ledger DTO after validating count, ownership and genesis rules.
192    /// Recovery must still establish the persisted format and physical binding.
193    pub fn new(
194        current_generation: u64,
195        records: Vec<AllocationRecord>,
196    ) -> Result<Self, LedgerIntegrityError> {
197        let ledger = Self {
198            current_generation,
199            records,
200        };
201        ledger.validate_integrity()?;
202        Ok(ledger)
203    }
204
205    /// Return the current commit counter; this is not retained upgrade history.
206    #[must_use]
207    pub const fn current_generation(&self) -> u64 {
208        self.current_generation
209    }
210
211    /// Borrow retained ownership records, including omitted and retired stores.
212    #[must_use]
213    pub fn records(&self) -> &[AllocationRecord] {
214        &self.records
215    }
216}