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)]
33#[serde(deny_unknown_fields)]
34pub struct AllocationLedger {
35 pub(crate) ledger_schema_version: u32,
37 pub(crate) physical_format_id: u32,
39 pub(crate) current_generation: u64,
41 pub(crate) allocation_history: AllocationHistory,
43}
44
45#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
54#[serde(deny_unknown_fields)]
55pub struct AllocationHistory {
56 pub(crate) records: Vec<AllocationRecord>,
58 pub(crate) generations: Vec<GenerationRecord>,
60}
61
62#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
71#[serde(deny_unknown_fields)]
72pub struct AllocationRecord {
73 pub(crate) stable_key: StableKey,
75 pub(crate) slot: AllocationSlotDescriptor,
77 pub(crate) state: AllocationState,
79 pub(crate) first_generation: u64,
81 pub(crate) last_seen_generation: u64,
83 pub(crate) retired_generation: Option<u64>,
85 pub(crate) schema_history: Vec<SchemaMetadataRecord>,
87}
88
89#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
97#[serde(deny_unknown_fields)]
98pub struct AllocationRetirement {
99 pub stable_key: StableKey,
101 pub slot: AllocationSlotDescriptor,
103}
104
105impl AllocationRetirement {
106 pub fn new(
108 stable_key: impl AsRef<str>,
109 slot: AllocationSlotDescriptor,
110 ) -> Result<Self, AllocationRetirementError> {
111 Ok(Self {
112 stable_key: StableKey::parse(stable_key).map_err(AllocationRetirementError::Key)?,
113 slot,
114 })
115 }
116}
117
118#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
123pub enum AllocationState {
124 Reserved,
126 Active,
128 Retired,
130}
131
132#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
141#[serde(deny_unknown_fields)]
142pub struct SchemaMetadataRecord {
143 pub(crate) generation: u64,
145 pub(crate) schema: SchemaMetadata,
147}
148
149#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
154#[serde(deny_unknown_fields)]
155pub struct GenerationRecord {
156 pub(crate) generation: u64,
158 pub(crate) parent_generation: Option<u64>,
160 pub(crate) runtime_fingerprint: Option<String>,
162 pub(crate) declaration_count: u32,
164 pub(crate) committed_at: Option<u64>,
166}
167
168#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
176#[serde(deny_unknown_fields)]
177pub struct LedgerCompatibility {
178 pub min_ledger_schema_version: u32,
180 pub max_ledger_schema_version: u32,
182 pub physical_format_id: u32,
184}
185
186impl LedgerCompatibility {
187 #[must_use]
189 pub const fn current() -> Self {
190 Self {
191 min_ledger_schema_version: CURRENT_LEDGER_SCHEMA_VERSION,
192 max_ledger_schema_version: CURRENT_LEDGER_SCHEMA_VERSION,
193 physical_format_id: CURRENT_PHYSICAL_FORMAT_ID,
194 }
195 }
196
197 pub const fn validate(
199 &self,
200 ledger: &AllocationLedger,
201 ) -> Result<(), LedgerCompatibilityError> {
202 self.validate_versions(ledger.ledger_schema_version, ledger.physical_format_id)
203 }
204
205 pub(crate) const fn validate_versions(
206 &self,
207 ledger_schema_version: u32,
208 physical_format_id: u32,
209 ) -> Result<(), LedgerCompatibilityError> {
210 if ledger_schema_version < self.min_ledger_schema_version {
211 return Err(LedgerCompatibilityError::UnsupportedLedgerSchemaVersion {
212 found: ledger_schema_version,
213 min_supported: self.min_ledger_schema_version,
214 max_supported: self.max_ledger_schema_version,
215 });
216 }
217 if ledger_schema_version > self.max_ledger_schema_version {
218 return Err(LedgerCompatibilityError::UnsupportedLedgerSchemaVersion {
219 found: ledger_schema_version,
220 min_supported: self.min_ledger_schema_version,
221 max_supported: self.max_ledger_schema_version,
222 });
223 }
224 if physical_format_id != self.physical_format_id {
225 return Err(LedgerCompatibilityError::UnsupportedPhysicalFormat {
226 found: physical_format_id,
227 supported: self.physical_format_id,
228 });
229 }
230 Ok(())
231 }
232}
233
234impl Default for LedgerCompatibility {
235 fn default() -> Self {
236 Self::current()
237 }
238}
239
240#[derive(Clone, Debug, Eq, PartialEq)]
251pub struct RecoveredLedger {
252 ledger: AllocationLedger,
253 physical_generation: u64,
254 ledger_schema_version: u32,
255 envelope_version: u16,
256}
257
258impl RecoveredLedger {
259 pub(crate) const fn from_trusted_parts(
260 ledger: AllocationLedger,
261 physical_generation: u64,
262 envelope_version: u16,
263 ) -> Self {
264 let ledger_schema_version = ledger.ledger_schema_version;
265 Self {
266 ledger,
267 physical_generation,
268 ledger_schema_version,
269 envelope_version,
270 }
271 }
272
273 #[must_use]
279 pub const fn ledger(&self) -> &AllocationLedger {
280 &self.ledger
281 }
282
283 #[must_use]
285 pub const fn physical_generation(&self) -> u64 {
286 self.physical_generation
287 }
288
289 #[must_use]
291 pub const fn current_generation(&self) -> u64 {
292 self.ledger.current_generation
293 }
294
295 #[must_use]
297 pub const fn ledger_schema_version(&self) -> u32 {
298 self.ledger_schema_version
299 }
300
301 #[must_use]
303 pub const fn envelope_version(&self) -> u16 {
304 self.envelope_version
305 }
306
307 pub(crate) fn into_ledger(self) -> AllocationLedger {
308 self.ledger
309 }
310}
311
312impl AllocationHistory {
313 #[cfg(test)]
314 pub(crate) const fn from_parts(
315 records: Vec<AllocationRecord>,
316 generations: Vec<GenerationRecord>,
317 ) -> Self {
318 Self {
319 records,
320 generations,
321 }
322 }
323
324 #[must_use]
326 pub fn records(&self) -> &[AllocationRecord] {
327 &self.records
328 }
329
330 #[must_use]
332 pub fn generations(&self) -> &[GenerationRecord] {
333 &self.generations
334 }
335
336 #[must_use]
338 pub fn is_empty(&self) -> bool {
339 self.records.is_empty() && self.generations.is_empty()
340 }
341
342 pub(crate) const fn records_mut(&mut self) -> &mut Vec<AllocationRecord> {
343 &mut self.records
344 }
345
346 #[cfg(test)]
347 pub(crate) const fn generations_mut(&mut self) -> &mut Vec<GenerationRecord> {
348 &mut self.generations
349 }
350
351 pub(crate) fn push_record(&mut self, record: AllocationRecord) {
352 self.records.push(record);
353 }
354
355 pub(crate) fn push_generation(&mut self, generation: GenerationRecord) {
356 self.generations.push(generation);
357 }
358}
359
360impl SchemaMetadataRecord {
361 pub fn new(generation: u64, schema: SchemaMetadata) -> Result<Self, SchemaMetadataError> {
363 schema.validate()?;
364 Ok(Self { generation, schema })
365 }
366
367 #[must_use]
369 pub const fn generation(&self) -> u64 {
370 self.generation
371 }
372
373 #[must_use]
375 pub const fn schema(&self) -> &SchemaMetadata {
376 &self.schema
377 }
378}
379
380impl GenerationRecord {
381 pub fn new(
383 generation: u64,
384 parent_generation: Option<u64>,
385 runtime_fingerprint: Option<String>,
386 declaration_count: u32,
387 committed_at: Option<u64>,
388 ) -> Result<Self, DeclarationSnapshotError> {
389 validate_runtime_fingerprint(runtime_fingerprint.as_deref())?;
390 Ok(Self {
391 generation,
392 parent_generation,
393 runtime_fingerprint,
394 declaration_count,
395 committed_at,
396 })
397 }
398
399 #[must_use]
401 pub const fn generation(&self) -> u64 {
402 self.generation
403 }
404
405 #[must_use]
407 pub const fn parent_generation(&self) -> Option<u64> {
408 self.parent_generation
409 }
410
411 #[must_use]
413 pub fn runtime_fingerprint(&self) -> Option<&str> {
414 self.runtime_fingerprint.as_deref()
415 }
416
417 #[must_use]
419 pub const fn declaration_count(&self) -> u32 {
420 self.declaration_count
421 }
422
423 #[must_use]
425 pub const fn committed_at(&self) -> Option<u64> {
426 self.committed_at
427 }
428}
429
430impl AllocationRecord {
431 #[must_use]
433 pub(crate) fn from_declaration(
434 generation: u64,
435 declaration: AllocationDeclaration,
436 state: AllocationState,
437 ) -> Self {
438 Self {
439 stable_key: declaration.stable_key,
440 slot: declaration.slot,
441 state,
442 first_generation: generation,
443 last_seen_generation: generation,
444 retired_generation: None,
445 schema_history: vec![
446 SchemaMetadataRecord::new(generation, declaration.schema)
447 .expect("declarations validate schema metadata"),
448 ],
449 }
450 }
451
452 #[must_use]
454 pub(crate) fn reserved(generation: u64, declaration: AllocationDeclaration) -> Self {
455 Self::from_declaration(generation, declaration, AllocationState::Reserved)
456 }
457
458 #[must_use]
460 pub const fn stable_key(&self) -> &StableKey {
461 &self.stable_key
462 }
463
464 #[must_use]
466 pub const fn slot(&self) -> &AllocationSlotDescriptor {
467 &self.slot
468 }
469
470 #[must_use]
472 pub const fn state(&self) -> AllocationState {
473 self.state
474 }
475
476 #[must_use]
478 pub const fn first_generation(&self) -> u64 {
479 self.first_generation
480 }
481
482 #[must_use]
484 pub const fn last_seen_generation(&self) -> u64 {
485 self.last_seen_generation
486 }
487
488 #[must_use]
490 pub const fn retired_generation(&self) -> Option<u64> {
491 self.retired_generation
492 }
493
494 #[must_use]
496 pub fn schema_history(&self) -> &[SchemaMetadataRecord] {
497 &self.schema_history
498 }
499
500 pub(crate) fn observe_declaration(
501 &mut self,
502 generation: u64,
503 declaration: &AllocationDeclaration,
504 ) {
505 self.last_seen_generation = generation;
506 if self.state == AllocationState::Reserved {
507 self.state = AllocationState::Active;
508 }
509
510 let latest_schema = self.schema_history.last().map(|record| &record.schema);
511 if latest_schema != Some(&declaration.schema) {
512 self.schema_history.push(
513 SchemaMetadataRecord::new(generation, declaration.schema.clone())
514 .expect("declarations validate schema metadata"),
515 );
516 }
517 }
518
519 pub(crate) fn observe_reservation(
520 &mut self,
521 generation: u64,
522 reservation: &AllocationDeclaration,
523 ) {
524 self.last_seen_generation = generation;
525
526 let latest_schema = self.schema_history.last().map(|record| &record.schema);
527 if latest_schema != Some(&reservation.schema) {
528 self.schema_history.push(
529 SchemaMetadataRecord::new(generation, reservation.schema.clone())
530 .expect("reservations validate schema metadata"),
531 );
532 }
533 }
534}
535
536impl AllocationLedger {
537 pub fn new(
545 ledger_schema_version: u32,
546 physical_format_id: u32,
547 current_generation: u64,
548 allocation_history: AllocationHistory,
549 ) -> Result<Self, LedgerIntegrityError> {
550 let ledger = Self {
551 ledger_schema_version,
552 physical_format_id,
553 current_generation,
554 allocation_history,
555 };
556 ledger.validate_integrity()?;
557 Ok(ledger)
558 }
559
560 pub fn new_committed(
566 ledger_schema_version: u32,
567 physical_format_id: u32,
568 current_generation: u64,
569 allocation_history: AllocationHistory,
570 ) -> Result<Self, LedgerIntegrityError> {
571 let ledger = Self::new(
572 ledger_schema_version,
573 physical_format_id,
574 current_generation,
575 allocation_history,
576 )?;
577 ledger.validate_committed_integrity()?;
578 Ok(ledger)
579 }
580
581 #[must_use]
583 pub const fn ledger_schema_version(&self) -> u32 {
584 self.ledger_schema_version
585 }
586
587 #[must_use]
589 pub const fn physical_format_id(&self) -> u32 {
590 self.physical_format_id
591 }
592
593 #[must_use]
595 pub const fn current_generation(&self) -> u64 {
596 self.current_generation
597 }
598
599 #[must_use]
601 pub const fn allocation_history(&self) -> &AllocationHistory {
602 &self.allocation_history
603 }
604}