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)]
28pub struct AllocationLedger {
29 pub(crate) ledger_schema_version: u32,
31 pub(crate) physical_format_id: u32,
33 pub(crate) current_generation: u64,
35 pub(crate) allocation_history: AllocationHistory,
37}
38
39#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
48pub struct AllocationHistory {
49 pub(crate) records: Vec<AllocationRecord>,
51 pub(crate) generations: Vec<GenerationRecord>,
53}
54
55#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
64pub struct AllocationRecord {
65 pub(crate) stable_key: StableKey,
67 pub(crate) slot: AllocationSlotDescriptor,
69 pub(crate) state: AllocationState,
71 pub(crate) first_generation: u64,
73 pub(crate) last_seen_generation: u64,
75 pub(crate) retired_generation: Option<u64>,
77 pub(crate) schema_history: Vec<SchemaMetadataRecord>,
79}
80
81#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
89pub struct AllocationRetirement {
90 pub stable_key: StableKey,
92 pub slot: AllocationSlotDescriptor,
94}
95
96impl AllocationRetirement {
97 pub fn new(
99 stable_key: impl AsRef<str>,
100 slot: AllocationSlotDescriptor,
101 ) -> Result<Self, AllocationRetirementError> {
102 Ok(Self {
103 stable_key: StableKey::parse(stable_key).map_err(AllocationRetirementError::Key)?,
104 slot,
105 })
106 }
107}
108
109#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
114pub enum AllocationState {
115 Reserved,
117 Active,
119 Retired,
121}
122
123#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
132pub struct SchemaMetadataRecord {
133 pub(crate) generation: u64,
135 pub(crate) schema: SchemaMetadata,
137}
138
139#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
144pub struct GenerationRecord {
145 pub(crate) generation: u64,
147 pub(crate) parent_generation: Option<u64>,
149 pub(crate) runtime_fingerprint: Option<String>,
151 pub(crate) declaration_count: u32,
153 pub(crate) committed_at: Option<u64>,
155}
156
157#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
165pub struct LedgerCompatibility {
166 pub min_ledger_schema_version: u32,
168 pub max_ledger_schema_version: u32,
170 pub physical_format_id: u32,
172}
173
174impl LedgerCompatibility {
175 #[must_use]
177 pub const fn current() -> Self {
178 Self {
179 min_ledger_schema_version: CURRENT_LEDGER_SCHEMA_VERSION,
180 max_ledger_schema_version: CURRENT_LEDGER_SCHEMA_VERSION,
181 physical_format_id: CURRENT_PHYSICAL_FORMAT_ID,
182 }
183 }
184
185 pub const fn validate(
187 &self,
188 ledger: &AllocationLedger,
189 ) -> Result<(), LedgerCompatibilityError> {
190 if ledger.ledger_schema_version < self.min_ledger_schema_version {
191 return Err(LedgerCompatibilityError::UnsupportedLedgerSchemaVersion {
192 found: ledger.ledger_schema_version,
193 min_supported: self.min_ledger_schema_version,
194 max_supported: self.max_ledger_schema_version,
195 });
196 }
197 if ledger.ledger_schema_version > self.max_ledger_schema_version {
198 return Err(LedgerCompatibilityError::UnsupportedLedgerSchemaVersion {
199 found: ledger.ledger_schema_version,
200 min_supported: self.min_ledger_schema_version,
201 max_supported: self.max_ledger_schema_version,
202 });
203 }
204 if ledger.physical_format_id != self.physical_format_id {
205 return Err(LedgerCompatibilityError::UnsupportedPhysicalFormat {
206 found: ledger.physical_format_id,
207 supported: self.physical_format_id,
208 });
209 }
210 Ok(())
211 }
212}
213
214impl Default for LedgerCompatibility {
215 fn default() -> Self {
216 Self::current()
217 }
218}
219
220impl AllocationHistory {
221 #[cfg(test)]
222 pub(crate) const fn from_parts(
223 records: Vec<AllocationRecord>,
224 generations: Vec<GenerationRecord>,
225 ) -> Self {
226 Self {
227 records,
228 generations,
229 }
230 }
231
232 #[must_use]
234 pub fn records(&self) -> &[AllocationRecord] {
235 &self.records
236 }
237
238 #[must_use]
240 pub fn generations(&self) -> &[GenerationRecord] {
241 &self.generations
242 }
243
244 #[must_use]
246 pub fn is_empty(&self) -> bool {
247 self.records.is_empty() && self.generations.is_empty()
248 }
249
250 pub(crate) const fn records_mut(&mut self) -> &mut Vec<AllocationRecord> {
251 &mut self.records
252 }
253
254 #[cfg(test)]
255 pub(crate) const fn generations_mut(&mut self) -> &mut Vec<GenerationRecord> {
256 &mut self.generations
257 }
258
259 pub(crate) fn push_record(&mut self, record: AllocationRecord) {
260 self.records.push(record);
261 }
262
263 pub(crate) fn push_generation(&mut self, generation: GenerationRecord) {
264 self.generations.push(generation);
265 }
266}
267
268impl SchemaMetadataRecord {
269 pub fn new(generation: u64, schema: SchemaMetadata) -> Result<Self, SchemaMetadataError> {
271 schema.validate()?;
272 Ok(Self { generation, schema })
273 }
274
275 #[must_use]
277 pub const fn generation(&self) -> u64 {
278 self.generation
279 }
280
281 #[must_use]
283 pub const fn schema(&self) -> &SchemaMetadata {
284 &self.schema
285 }
286}
287
288impl GenerationRecord {
289 pub fn new(
291 generation: u64,
292 parent_generation: Option<u64>,
293 runtime_fingerprint: Option<String>,
294 declaration_count: u32,
295 committed_at: Option<u64>,
296 ) -> Result<Self, DeclarationSnapshotError> {
297 validate_runtime_fingerprint(runtime_fingerprint.as_deref())?;
298 Ok(Self {
299 generation,
300 parent_generation,
301 runtime_fingerprint,
302 declaration_count,
303 committed_at,
304 })
305 }
306
307 #[must_use]
309 pub const fn generation(&self) -> u64 {
310 self.generation
311 }
312
313 #[must_use]
315 pub const fn parent_generation(&self) -> Option<u64> {
316 self.parent_generation
317 }
318
319 #[must_use]
321 pub fn runtime_fingerprint(&self) -> Option<&str> {
322 self.runtime_fingerprint.as_deref()
323 }
324
325 #[must_use]
327 pub const fn declaration_count(&self) -> u32 {
328 self.declaration_count
329 }
330
331 #[must_use]
333 pub const fn committed_at(&self) -> Option<u64> {
334 self.committed_at
335 }
336}
337
338impl AllocationRecord {
339 #[must_use]
341 pub(crate) fn from_declaration(
342 generation: u64,
343 declaration: AllocationDeclaration,
344 state: AllocationState,
345 ) -> Self {
346 Self {
347 stable_key: declaration.stable_key,
348 slot: declaration.slot,
349 state,
350 first_generation: generation,
351 last_seen_generation: generation,
352 retired_generation: None,
353 schema_history: vec![
354 SchemaMetadataRecord::new(generation, declaration.schema)
355 .expect("declarations validate schema metadata"),
356 ],
357 }
358 }
359
360 #[must_use]
362 pub(crate) fn reserved(generation: u64, declaration: AllocationDeclaration) -> Self {
363 Self::from_declaration(generation, declaration, AllocationState::Reserved)
364 }
365
366 #[must_use]
368 pub const fn stable_key(&self) -> &StableKey {
369 &self.stable_key
370 }
371
372 #[must_use]
374 pub const fn slot(&self) -> &AllocationSlotDescriptor {
375 &self.slot
376 }
377
378 #[must_use]
380 pub const fn state(&self) -> AllocationState {
381 self.state
382 }
383
384 #[must_use]
386 pub const fn first_generation(&self) -> u64 {
387 self.first_generation
388 }
389
390 #[must_use]
392 pub const fn last_seen_generation(&self) -> u64 {
393 self.last_seen_generation
394 }
395
396 #[must_use]
398 pub const fn retired_generation(&self) -> Option<u64> {
399 self.retired_generation
400 }
401
402 #[must_use]
404 pub fn schema_history(&self) -> &[SchemaMetadataRecord] {
405 &self.schema_history
406 }
407
408 pub(crate) fn observe_declaration(
409 &mut self,
410 generation: u64,
411 declaration: &AllocationDeclaration,
412 ) {
413 self.last_seen_generation = generation;
414 if self.state == AllocationState::Reserved {
415 self.state = AllocationState::Active;
416 }
417
418 let latest_schema = self.schema_history.last().map(|record| &record.schema);
419 if latest_schema != Some(&declaration.schema) {
420 self.schema_history.push(
421 SchemaMetadataRecord::new(generation, declaration.schema.clone())
422 .expect("declarations validate schema metadata"),
423 );
424 }
425 }
426
427 pub(crate) fn observe_reservation(
428 &mut self,
429 generation: u64,
430 reservation: &AllocationDeclaration,
431 ) {
432 self.last_seen_generation = generation;
433
434 let latest_schema = self.schema_history.last().map(|record| &record.schema);
435 if latest_schema != Some(&reservation.schema) {
436 self.schema_history.push(
437 SchemaMetadataRecord::new(generation, reservation.schema.clone())
438 .expect("reservations validate schema metadata"),
439 );
440 }
441 }
442}
443
444impl AllocationLedger {
445 pub fn new(
453 ledger_schema_version: u32,
454 physical_format_id: u32,
455 current_generation: u64,
456 allocation_history: AllocationHistory,
457 ) -> Result<Self, LedgerIntegrityError> {
458 let ledger = Self {
459 ledger_schema_version,
460 physical_format_id,
461 current_generation,
462 allocation_history,
463 };
464 ledger.validate_integrity()?;
465 Ok(ledger)
466 }
467
468 pub fn new_committed(
474 ledger_schema_version: u32,
475 physical_format_id: u32,
476 current_generation: u64,
477 allocation_history: AllocationHistory,
478 ) -> Result<Self, LedgerIntegrityError> {
479 let ledger = Self::new(
480 ledger_schema_version,
481 physical_format_id,
482 current_generation,
483 allocation_history,
484 )?;
485 ledger.validate_committed_integrity()?;
486 Ok(ledger)
487 }
488
489 #[must_use]
491 pub const fn ledger_schema_version(&self) -> u32 {
492 self.ledger_schema_version
493 }
494
495 #[must_use]
497 pub const fn physical_format_id(&self) -> u32 {
498 self.physical_format_id
499 }
500
501 #[must_use]
503 pub const fn current_generation(&self) -> u64 {
504 self.current_generation
505 }
506
507 #[must_use]
509 pub const fn allocation_history(&self) -> &AllocationHistory {
510 &self.allocation_history
511 }
512}