1use super::{AllocationRetirementError, LedgerCompatibilityError, LedgerIntegrityError};
2use crate::{
3 declaration::{AllocationDeclaration, DeclarationSnapshotError, validate_runtime_fingerprint},
4 key::StableKey,
5 schema::{SchemaMetadata, SchemaMetadataError},
6 slot::AllocationSlotDescriptor,
7};
8use serde::{Deserialize, Serialize};
9
10pub const CURRENT_LEDGER_SCHEMA_VERSION: u32 = 1;
12
13pub const CURRENT_PHYSICAL_FORMAT_ID: u32 = 1;
15
16#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
33pub struct AllocationLedger {
34 pub(crate) ledger_schema_version: u32,
36 pub(crate) physical_format_id: u32,
38 pub(crate) current_generation: u64,
40 pub(crate) allocation_history: AllocationHistory,
42}
43
44#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
53pub struct AllocationHistory {
54 pub(crate) records: Vec<AllocationRecord>,
56 pub(crate) generations: Vec<GenerationRecord>,
58}
59
60#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
69pub struct AllocationRecord {
70 pub(crate) stable_key: StableKey,
72 pub(crate) slot: AllocationSlotDescriptor,
74 pub(crate) state: AllocationState,
76 pub(crate) first_generation: u64,
78 pub(crate) last_seen_generation: u64,
80 pub(crate) retired_generation: Option<u64>,
82 pub(crate) schema_history: Vec<SchemaMetadataRecord>,
84}
85
86#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
94pub struct AllocationRetirement {
95 pub stable_key: StableKey,
97 pub slot: AllocationSlotDescriptor,
99}
100
101impl AllocationRetirement {
102 pub fn new(
104 stable_key: impl AsRef<str>,
105 slot: AllocationSlotDescriptor,
106 ) -> Result<Self, AllocationRetirementError> {
107 Ok(Self {
108 stable_key: StableKey::parse(stable_key).map_err(AllocationRetirementError::Key)?,
109 slot,
110 })
111 }
112}
113
114#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
119pub enum AllocationState {
120 Reserved,
122 Active,
124 Retired,
126}
127
128#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
137pub struct SchemaMetadataRecord {
138 pub(crate) generation: u64,
140 pub(crate) schema: SchemaMetadata,
142}
143
144#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
149pub struct GenerationRecord {
150 pub(crate) generation: u64,
152 pub(crate) parent_generation: Option<u64>,
154 pub(crate) runtime_fingerprint: Option<String>,
156 pub(crate) declaration_count: u32,
158 pub(crate) committed_at: Option<u64>,
160}
161
162#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
170pub struct LedgerCompatibility {
171 pub min_ledger_schema_version: u32,
173 pub max_ledger_schema_version: u32,
175 pub physical_format_id: u32,
177}
178
179impl LedgerCompatibility {
180 #[must_use]
182 pub const fn current() -> Self {
183 Self {
184 min_ledger_schema_version: CURRENT_LEDGER_SCHEMA_VERSION,
185 max_ledger_schema_version: CURRENT_LEDGER_SCHEMA_VERSION,
186 physical_format_id: CURRENT_PHYSICAL_FORMAT_ID,
187 }
188 }
189
190 pub const fn validate(
192 &self,
193 ledger: &AllocationLedger,
194 ) -> Result<(), LedgerCompatibilityError> {
195 if ledger.ledger_schema_version < self.min_ledger_schema_version {
196 return Err(LedgerCompatibilityError::UnsupportedLedgerSchemaVersion {
197 found: ledger.ledger_schema_version,
198 min_supported: self.min_ledger_schema_version,
199 max_supported: self.max_ledger_schema_version,
200 });
201 }
202 if ledger.ledger_schema_version > self.max_ledger_schema_version {
203 return Err(LedgerCompatibilityError::UnsupportedLedgerSchemaVersion {
204 found: ledger.ledger_schema_version,
205 min_supported: self.min_ledger_schema_version,
206 max_supported: self.max_ledger_schema_version,
207 });
208 }
209 if ledger.physical_format_id != self.physical_format_id {
210 return Err(LedgerCompatibilityError::UnsupportedPhysicalFormat {
211 found: ledger.physical_format_id,
212 supported: self.physical_format_id,
213 });
214 }
215 Ok(())
216 }
217}
218
219impl Default for LedgerCompatibility {
220 fn default() -> Self {
221 Self::current()
222 }
223}
224
225impl AllocationHistory {
226 #[cfg(test)]
227 pub(crate) const fn from_parts(
228 records: Vec<AllocationRecord>,
229 generations: Vec<GenerationRecord>,
230 ) -> Self {
231 Self {
232 records,
233 generations,
234 }
235 }
236
237 #[must_use]
239 pub fn records(&self) -> &[AllocationRecord] {
240 &self.records
241 }
242
243 #[must_use]
245 pub fn generations(&self) -> &[GenerationRecord] {
246 &self.generations
247 }
248
249 #[must_use]
251 pub fn is_empty(&self) -> bool {
252 self.records.is_empty() && self.generations.is_empty()
253 }
254
255 pub(crate) const fn records_mut(&mut self) -> &mut Vec<AllocationRecord> {
256 &mut self.records
257 }
258
259 #[cfg(test)]
260 pub(crate) const fn generations_mut(&mut self) -> &mut Vec<GenerationRecord> {
261 &mut self.generations
262 }
263
264 pub(crate) fn push_record(&mut self, record: AllocationRecord) {
265 self.records.push(record);
266 }
267
268 pub(crate) fn push_generation(&mut self, generation: GenerationRecord) {
269 self.generations.push(generation);
270 }
271}
272
273impl SchemaMetadataRecord {
274 pub fn new(generation: u64, schema: SchemaMetadata) -> Result<Self, SchemaMetadataError> {
276 schema.validate()?;
277 Ok(Self { generation, schema })
278 }
279
280 #[must_use]
282 pub const fn generation(&self) -> u64 {
283 self.generation
284 }
285
286 #[must_use]
288 pub const fn schema(&self) -> &SchemaMetadata {
289 &self.schema
290 }
291}
292
293impl GenerationRecord {
294 pub fn new(
296 generation: u64,
297 parent_generation: Option<u64>,
298 runtime_fingerprint: Option<String>,
299 declaration_count: u32,
300 committed_at: Option<u64>,
301 ) -> Result<Self, DeclarationSnapshotError> {
302 validate_runtime_fingerprint(runtime_fingerprint.as_deref())?;
303 Ok(Self {
304 generation,
305 parent_generation,
306 runtime_fingerprint,
307 declaration_count,
308 committed_at,
309 })
310 }
311
312 #[must_use]
314 pub const fn generation(&self) -> u64 {
315 self.generation
316 }
317
318 #[must_use]
320 pub const fn parent_generation(&self) -> Option<u64> {
321 self.parent_generation
322 }
323
324 #[must_use]
326 pub fn runtime_fingerprint(&self) -> Option<&str> {
327 self.runtime_fingerprint.as_deref()
328 }
329
330 #[must_use]
332 pub const fn declaration_count(&self) -> u32 {
333 self.declaration_count
334 }
335
336 #[must_use]
338 pub const fn committed_at(&self) -> Option<u64> {
339 self.committed_at
340 }
341}
342
343impl AllocationRecord {
344 #[must_use]
346 pub(crate) fn from_declaration(
347 generation: u64,
348 declaration: AllocationDeclaration,
349 state: AllocationState,
350 ) -> Self {
351 Self {
352 stable_key: declaration.stable_key,
353 slot: declaration.slot,
354 state,
355 first_generation: generation,
356 last_seen_generation: generation,
357 retired_generation: None,
358 schema_history: vec![
359 SchemaMetadataRecord::new(generation, declaration.schema)
360 .expect("declarations validate schema metadata"),
361 ],
362 }
363 }
364
365 #[must_use]
367 pub(crate) fn reserved(generation: u64, declaration: AllocationDeclaration) -> Self {
368 Self::from_declaration(generation, declaration, AllocationState::Reserved)
369 }
370
371 #[must_use]
373 pub const fn stable_key(&self) -> &StableKey {
374 &self.stable_key
375 }
376
377 #[must_use]
379 pub const fn slot(&self) -> &AllocationSlotDescriptor {
380 &self.slot
381 }
382
383 #[must_use]
385 pub const fn state(&self) -> AllocationState {
386 self.state
387 }
388
389 #[must_use]
391 pub const fn first_generation(&self) -> u64 {
392 self.first_generation
393 }
394
395 #[must_use]
397 pub const fn last_seen_generation(&self) -> u64 {
398 self.last_seen_generation
399 }
400
401 #[must_use]
403 pub const fn retired_generation(&self) -> Option<u64> {
404 self.retired_generation
405 }
406
407 #[must_use]
409 pub fn schema_history(&self) -> &[SchemaMetadataRecord] {
410 &self.schema_history
411 }
412
413 pub(crate) fn observe_declaration(
414 &mut self,
415 generation: u64,
416 declaration: &AllocationDeclaration,
417 ) {
418 self.last_seen_generation = generation;
419 if self.state == AllocationState::Reserved {
420 self.state = AllocationState::Active;
421 }
422
423 let latest_schema = self.schema_history.last().map(|record| &record.schema);
424 if latest_schema != Some(&declaration.schema) {
425 self.schema_history.push(
426 SchemaMetadataRecord::new(generation, declaration.schema.clone())
427 .expect("declarations validate schema metadata"),
428 );
429 }
430 }
431
432 pub(crate) fn observe_reservation(
433 &mut self,
434 generation: u64,
435 reservation: &AllocationDeclaration,
436 ) {
437 self.last_seen_generation = generation;
438
439 let latest_schema = self.schema_history.last().map(|record| &record.schema);
440 if latest_schema != Some(&reservation.schema) {
441 self.schema_history.push(
442 SchemaMetadataRecord::new(generation, reservation.schema.clone())
443 .expect("reservations validate schema metadata"),
444 );
445 }
446 }
447}
448
449impl AllocationLedger {
450 pub fn new(
458 ledger_schema_version: u32,
459 physical_format_id: u32,
460 current_generation: u64,
461 allocation_history: AllocationHistory,
462 ) -> Result<Self, LedgerIntegrityError> {
463 let ledger = Self {
464 ledger_schema_version,
465 physical_format_id,
466 current_generation,
467 allocation_history,
468 };
469 ledger.validate_integrity()?;
470 Ok(ledger)
471 }
472
473 pub fn new_committed(
479 ledger_schema_version: u32,
480 physical_format_id: u32,
481 current_generation: u64,
482 allocation_history: AllocationHistory,
483 ) -> Result<Self, LedgerIntegrityError> {
484 let ledger = Self::new(
485 ledger_schema_version,
486 physical_format_id,
487 current_generation,
488 allocation_history,
489 )?;
490 ledger.validate_committed_integrity()?;
491 Ok(ledger)
492 }
493
494 #[must_use]
496 pub const fn ledger_schema_version(&self) -> u32 {
497 self.ledger_schema_version
498 }
499
500 #[must_use]
502 pub const fn physical_format_id(&self) -> u32 {
503 self.physical_format_id
504 }
505
506 #[must_use]
508 pub const fn current_generation(&self) -> u64 {
509 self.current_generation
510 }
511
512 #[must_use]
514 pub const fn allocation_history(&self) -> &AllocationHistory {
515 &self.allocation_history
516 }
517}