1use super::{AllocationRetirementError, LedgerCompatibilityError, LedgerIntegrityError};
2use crate::{
3 declaration::AllocationDeclaration, key::StableKey, schema::SchemaMetadata,
4 slot::AllocationSlotDescriptor,
5};
6use serde::{Deserialize, Serialize};
7
8pub const CURRENT_LEDGER_SCHEMA_VERSION: u32 = 1;
10
11pub const CURRENT_PHYSICAL_FORMAT_ID: u32 = 1;
13
14#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
24pub struct AllocationLedger {
25 pub(crate) ledger_schema_version: u32,
27 pub(crate) physical_format_id: u32,
29 pub(crate) current_generation: u64,
31 pub(crate) allocation_history: AllocationHistory,
33}
34
35#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
44pub struct AllocationHistory {
45 pub records: Vec<AllocationRecord>,
47 pub generations: Vec<GenerationRecord>,
49}
50
51#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
60pub struct AllocationRecord {
61 pub(crate) stable_key: StableKey,
63 pub(crate) slot: AllocationSlotDescriptor,
65 pub(crate) state: AllocationState,
67 pub(crate) first_generation: u64,
69 pub(crate) last_seen_generation: u64,
71 pub(crate) retired_generation: Option<u64>,
73 pub(crate) schema_history: Vec<SchemaMetadataRecord>,
75}
76
77#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
85pub struct AllocationRetirement {
86 pub stable_key: StableKey,
88 pub slot: AllocationSlotDescriptor,
90}
91
92impl AllocationRetirement {
93 pub fn new(
95 stable_key: impl AsRef<str>,
96 slot: AllocationSlotDescriptor,
97 ) -> Result<Self, AllocationRetirementError> {
98 Ok(Self {
99 stable_key: StableKey::parse(stable_key).map_err(AllocationRetirementError::Key)?,
100 slot,
101 })
102 }
103}
104
105#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
110pub enum AllocationState {
111 Reserved,
113 Active,
115 Retired,
117}
118
119#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
128pub struct SchemaMetadataRecord {
129 pub generation: u64,
131 pub schema: SchemaMetadata,
133}
134
135#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
140pub struct GenerationRecord {
141 pub generation: u64,
143 pub parent_generation: Option<u64>,
145 pub runtime_fingerprint: Option<String>,
147 pub declaration_count: u32,
149 pub committed_at: Option<u64>,
151}
152
153#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
161pub struct LedgerCompatibility {
162 pub min_ledger_schema_version: u32,
164 pub max_ledger_schema_version: u32,
166 pub physical_format_id: u32,
168}
169
170impl LedgerCompatibility {
171 #[must_use]
173 pub const fn current() -> Self {
174 Self {
175 min_ledger_schema_version: CURRENT_LEDGER_SCHEMA_VERSION,
176 max_ledger_schema_version: CURRENT_LEDGER_SCHEMA_VERSION,
177 physical_format_id: CURRENT_PHYSICAL_FORMAT_ID,
178 }
179 }
180
181 pub const fn validate(
183 &self,
184 ledger: &AllocationLedger,
185 ) -> Result<(), LedgerCompatibilityError> {
186 if ledger.ledger_schema_version < self.min_ledger_schema_version {
187 return Err(LedgerCompatibilityError::UnsupportedLedgerSchemaVersion {
188 found: ledger.ledger_schema_version,
189 min_supported: self.min_ledger_schema_version,
190 max_supported: self.max_ledger_schema_version,
191 });
192 }
193 if ledger.ledger_schema_version > self.max_ledger_schema_version {
194 return Err(LedgerCompatibilityError::UnsupportedLedgerSchemaVersion {
195 found: ledger.ledger_schema_version,
196 min_supported: self.min_ledger_schema_version,
197 max_supported: self.max_ledger_schema_version,
198 });
199 }
200 if ledger.physical_format_id != self.physical_format_id {
201 return Err(LedgerCompatibilityError::UnsupportedPhysicalFormat {
202 found: ledger.physical_format_id,
203 supported: self.physical_format_id,
204 });
205 }
206 Ok(())
207 }
208}
209
210impl Default for LedgerCompatibility {
211 fn default() -> Self {
212 Self::current()
213 }
214}
215
216impl AllocationRecord {
217 #[must_use]
219 pub(crate) fn from_declaration(
220 generation: u64,
221 declaration: AllocationDeclaration,
222 state: AllocationState,
223 ) -> Self {
224 Self {
225 stable_key: declaration.stable_key,
226 slot: declaration.slot,
227 state,
228 first_generation: generation,
229 last_seen_generation: generation,
230 retired_generation: None,
231 schema_history: vec![SchemaMetadataRecord {
232 generation,
233 schema: declaration.schema,
234 }],
235 }
236 }
237
238 #[must_use]
240 pub(crate) fn reserved(generation: u64, declaration: AllocationDeclaration) -> Self {
241 Self::from_declaration(generation, declaration, AllocationState::Reserved)
242 }
243
244 #[must_use]
246 pub const fn stable_key(&self) -> &StableKey {
247 &self.stable_key
248 }
249
250 #[must_use]
252 pub const fn slot(&self) -> &AllocationSlotDescriptor {
253 &self.slot
254 }
255
256 #[must_use]
258 pub const fn state(&self) -> AllocationState {
259 self.state
260 }
261
262 #[must_use]
264 pub const fn first_generation(&self) -> u64 {
265 self.first_generation
266 }
267
268 #[must_use]
270 pub const fn last_seen_generation(&self) -> u64 {
271 self.last_seen_generation
272 }
273
274 #[must_use]
276 pub const fn retired_generation(&self) -> Option<u64> {
277 self.retired_generation
278 }
279
280 #[must_use]
282 pub fn schema_history(&self) -> &[SchemaMetadataRecord] {
283 &self.schema_history
284 }
285
286 pub(crate) fn observe_declaration(
287 &mut self,
288 generation: u64,
289 declaration: &AllocationDeclaration,
290 ) {
291 self.last_seen_generation = generation;
292 if self.state == AllocationState::Reserved {
293 self.state = AllocationState::Active;
294 }
295
296 let latest_schema = self.schema_history.last().map(|record| &record.schema);
297 if latest_schema != Some(&declaration.schema) {
298 self.schema_history.push(SchemaMetadataRecord {
299 generation,
300 schema: declaration.schema.clone(),
301 });
302 }
303 }
304
305 pub(crate) fn observe_reservation(
306 &mut self,
307 generation: u64,
308 reservation: &AllocationDeclaration,
309 ) {
310 self.last_seen_generation = generation;
311
312 let latest_schema = self.schema_history.last().map(|record| &record.schema);
313 if latest_schema != Some(&reservation.schema) {
314 self.schema_history.push(SchemaMetadataRecord {
315 generation,
316 schema: reservation.schema.clone(),
317 });
318 }
319 }
320}
321
322impl AllocationLedger {
323 pub fn new(
329 ledger_schema_version: u32,
330 physical_format_id: u32,
331 current_generation: u64,
332 allocation_history: AllocationHistory,
333 ) -> Result<Self, LedgerIntegrityError> {
334 let ledger = Self {
335 ledger_schema_version,
336 physical_format_id,
337 current_generation,
338 allocation_history,
339 };
340 ledger.validate_integrity()?;
341 Ok(ledger)
342 }
343
344 #[must_use]
346 pub const fn ledger_schema_version(&self) -> u32 {
347 self.ledger_schema_version
348 }
349
350 #[must_use]
352 pub const fn physical_format_id(&self) -> u32 {
353 self.physical_format_id
354 }
355
356 #[must_use]
358 pub const fn current_generation(&self) -> u64 {
359 self.current_generation
360 }
361
362 #[must_use]
364 pub const fn allocation_history(&self) -> &AllocationHistory {
365 &self.allocation_history
366 }
367}