1use super::{AllocationRetirementError, 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
10#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
27#[serde(deny_unknown_fields)]
28pub struct AllocationLedger {
29 pub(crate) current_generation: u64,
31 pub(crate) allocation_history: AllocationHistory,
33}
34
35#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
44#[serde(deny_unknown_fields)]
45pub struct AllocationHistory {
46 pub(crate) records: Vec<AllocationRecord>,
48 pub(crate) generations: Vec<GenerationRecord>,
50}
51
52#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
61#[serde(deny_unknown_fields)]
62pub struct AllocationRecord {
63 pub(crate) stable_key: StableKey,
65 pub(crate) slot: AllocationSlotDescriptor,
67 pub(crate) state: AllocationState,
69 pub(crate) first_generation: u64,
71 pub(crate) last_seen_generation: u64,
73 pub(crate) retired_generation: Option<u64>,
75 pub(crate) schema_history: Vec<SchemaMetadataRecord>,
77}
78
79#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
87#[serde(deny_unknown_fields)]
88pub struct AllocationRetirement {
89 pub(crate) stable_key: StableKey,
91 pub(crate) slot: AllocationSlotDescriptor,
93}
94
95impl AllocationRetirement {
96 pub fn new(
98 stable_key: impl AsRef<str>,
99 slot: AllocationSlotDescriptor,
100 ) -> Result<Self, AllocationRetirementError> {
101 Ok(Self {
102 stable_key: StableKey::parse(stable_key).map_err(AllocationRetirementError::Key)?,
103 slot,
104 })
105 }
106
107 #[must_use]
109 pub const fn stable_key(&self) -> &StableKey {
110 &self.stable_key
111 }
112
113 #[must_use]
115 pub const fn slot(&self) -> &AllocationSlotDescriptor {
116 &self.slot
117 }
118}
119
120#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
125pub enum AllocationState {
126 Reserved,
128 Active,
130 Retired,
132}
133
134#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
143#[serde(deny_unknown_fields)]
144pub struct SchemaMetadataRecord {
145 pub(crate) generation: u64,
147 pub(crate) schema: SchemaMetadata,
149}
150
151#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
156#[serde(deny_unknown_fields)]
157pub struct GenerationRecord {
158 pub(crate) generation: u64,
160 pub(crate) parent_generation: u64,
162 pub(crate) runtime_fingerprint: Option<String>,
164 pub(crate) declaration_count: u32,
166 pub(crate) committed_at: Option<u64>,
168}
169
170#[derive(Clone, Debug, Eq, PartialEq)]
181pub struct RecoveredLedger {
182 ledger: AllocationLedger,
183 physical_generation: u64,
184}
185
186impl RecoveredLedger {
187 pub(crate) const fn from_trusted_parts(
188 ledger: AllocationLedger,
189 physical_generation: u64,
190 ) -> Self {
191 Self {
192 ledger,
193 physical_generation,
194 }
195 }
196
197 #[must_use]
203 pub const fn ledger(&self) -> &AllocationLedger {
204 &self.ledger
205 }
206
207 #[must_use]
209 pub const fn physical_generation(&self) -> u64 {
210 self.physical_generation
211 }
212
213 #[must_use]
215 pub const fn current_generation(&self) -> u64 {
216 self.ledger.current_generation
217 }
218
219 pub(crate) fn into_ledger(self) -> AllocationLedger {
220 self.ledger
221 }
222}
223
224impl AllocationHistory {
225 #[cfg(test)]
226 pub(crate) const fn from_parts(
227 records: Vec<AllocationRecord>,
228 generations: Vec<GenerationRecord>,
229 ) -> Self {
230 Self {
231 records,
232 generations,
233 }
234 }
235
236 #[must_use]
238 pub fn records(&self) -> &[AllocationRecord] {
239 &self.records
240 }
241
242 #[must_use]
244 pub fn generations(&self) -> &[GenerationRecord] {
245 &self.generations
246 }
247
248 #[must_use]
250 pub fn is_empty(&self) -> bool {
251 self.records.is_empty() && self.generations.is_empty()
252 }
253
254 pub(crate) const fn records_mut(&mut self) -> &mut Vec<AllocationRecord> {
255 &mut self.records
256 }
257
258 #[cfg(test)]
259 pub(crate) const fn generations_mut(&mut self) -> &mut Vec<GenerationRecord> {
260 &mut self.generations
261 }
262
263 pub(crate) fn push_record(&mut self, record: AllocationRecord) {
264 self.records.push(record);
265 }
266
267 pub(crate) fn push_generation(&mut self, generation: GenerationRecord) {
268 self.generations.push(generation);
269 }
270}
271
272impl SchemaMetadataRecord {
273 pub fn new(generation: u64, schema: SchemaMetadata) -> Result<Self, SchemaMetadataError> {
275 schema.validate()?;
276 Ok(Self { generation, schema })
277 }
278
279 #[must_use]
281 pub const fn generation(&self) -> u64 {
282 self.generation
283 }
284
285 #[must_use]
287 pub const fn schema(&self) -> &SchemaMetadata {
288 &self.schema
289 }
290}
291
292impl GenerationRecord {
293 pub fn new(
295 generation: u64,
296 parent_generation: u64,
297 runtime_fingerprint: Option<String>,
298 declaration_count: u32,
299 committed_at: Option<u64>,
300 ) -> Result<Self, DeclarationSnapshotError> {
301 validate_runtime_fingerprint(runtime_fingerprint.as_deref())?;
302 Ok(Self {
303 generation,
304 parent_generation,
305 runtime_fingerprint,
306 declaration_count,
307 committed_at,
308 })
309 }
310
311 #[must_use]
313 pub const fn generation(&self) -> u64 {
314 self.generation
315 }
316
317 #[must_use]
319 pub const fn parent_generation(&self) -> u64 {
320 self.parent_generation
321 }
322
323 #[must_use]
325 pub fn runtime_fingerprint(&self) -> Option<&str> {
326 self.runtime_fingerprint.as_deref()
327 }
328
329 #[must_use]
331 pub const fn declaration_count(&self) -> u32 {
332 self.declaration_count
333 }
334
335 #[must_use]
337 pub const fn committed_at(&self) -> Option<u64> {
338 self.committed_at
339 }
340}
341
342impl AllocationRecord {
343 #[must_use]
345 pub(crate) fn from_declaration(
346 generation: u64,
347 declaration: AllocationDeclaration,
348 state: AllocationState,
349 ) -> Self {
350 Self {
351 stable_key: declaration.stable_key,
352 slot: declaration.slot,
353 state,
354 first_generation: generation,
355 last_seen_generation: generation,
356 retired_generation: None,
357 schema_history: vec![
358 SchemaMetadataRecord::new(generation, declaration.schema)
359 .expect("declarations validate schema metadata"),
360 ],
361 }
362 }
363
364 #[must_use]
366 pub(crate) fn reserved(generation: u64, declaration: AllocationDeclaration) -> Self {
367 Self::from_declaration(generation, declaration, AllocationState::Reserved)
368 }
369
370 #[must_use]
372 pub const fn stable_key(&self) -> &StableKey {
373 &self.stable_key
374 }
375
376 #[must_use]
378 pub const fn slot(&self) -> &AllocationSlotDescriptor {
379 &self.slot
380 }
381
382 #[must_use]
384 pub const fn state(&self) -> AllocationState {
385 self.state
386 }
387
388 #[must_use]
390 pub const fn first_generation(&self) -> u64 {
391 self.first_generation
392 }
393
394 #[must_use]
396 pub const fn last_seen_generation(&self) -> u64 {
397 self.last_seen_generation
398 }
399
400 #[must_use]
402 pub const fn retired_generation(&self) -> Option<u64> {
403 self.retired_generation
404 }
405
406 #[must_use]
408 pub fn schema_history(&self) -> &[SchemaMetadataRecord] {
409 &self.schema_history
410 }
411
412 pub(crate) fn observe_declaration(
413 &mut self,
414 generation: u64,
415 declaration: &AllocationDeclaration,
416 ) {
417 self.last_seen_generation = generation;
418 if self.state == AllocationState::Reserved {
419 self.state = AllocationState::Active;
420 }
421
422 let latest_schema = self.schema_history.last().map(|record| &record.schema);
423 if latest_schema != Some(&declaration.schema) {
424 self.schema_history.push(
425 SchemaMetadataRecord::new(generation, declaration.schema.clone())
426 .expect("declarations validate schema metadata"),
427 );
428 }
429 }
430
431 pub(crate) fn observe_reservation(
432 &mut self,
433 generation: u64,
434 reservation: &AllocationDeclaration,
435 ) {
436 self.last_seen_generation = generation;
437
438 let latest_schema = self.schema_history.last().map(|record| &record.schema);
439 if latest_schema != Some(&reservation.schema) {
440 self.schema_history.push(
441 SchemaMetadataRecord::new(generation, reservation.schema.clone())
442 .expect("reservations validate schema metadata"),
443 );
444 }
445 }
446}
447
448impl AllocationLedger {
449 pub fn new(
457 current_generation: u64,
458 allocation_history: AllocationHistory,
459 ) -> Result<Self, LedgerIntegrityError> {
460 let ledger = Self {
461 current_generation,
462 allocation_history,
463 };
464 ledger.validate_integrity()?;
465 Ok(ledger)
466 }
467
468 pub fn new_committed(
474 current_generation: u64,
475 allocation_history: AllocationHistory,
476 ) -> Result<Self, LedgerIntegrityError> {
477 let ledger = Self::new(current_generation, allocation_history)?;
478 ledger.validate_committed_integrity()?;
479 Ok(ledger)
480 }
481
482 #[must_use]
484 pub const fn current_generation(&self) -> u64 {
485 self.current_generation
486 }
487
488 #[must_use]
490 pub const fn allocation_history(&self) -> &AllocationHistory {
491 &self.allocation_history
492 }
493}