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 stable_key: StableKey,
91 pub 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
108#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
113pub enum AllocationState {
114 Reserved,
116 Active,
118 Retired,
120}
121
122#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
131#[serde(deny_unknown_fields)]
132pub struct SchemaMetadataRecord {
133 pub(crate) generation: u64,
135 pub(crate) schema: SchemaMetadata,
137}
138
139#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
144#[serde(deny_unknown_fields)]
145pub struct GenerationRecord {
146 pub(crate) generation: u64,
148 pub(crate) parent_generation: u64,
150 pub(crate) runtime_fingerprint: Option<String>,
152 pub(crate) declaration_count: u32,
154 pub(crate) committed_at: Option<u64>,
156}
157
158#[derive(Clone, Debug, Eq, PartialEq)]
169pub struct RecoveredLedger {
170 ledger: AllocationLedger,
171 physical_generation: u64,
172}
173
174impl RecoveredLedger {
175 pub(crate) const fn from_trusted_parts(
176 ledger: AllocationLedger,
177 physical_generation: u64,
178 ) -> Self {
179 Self {
180 ledger,
181 physical_generation,
182 }
183 }
184
185 #[must_use]
191 pub const fn ledger(&self) -> &AllocationLedger {
192 &self.ledger
193 }
194
195 #[must_use]
197 pub const fn physical_generation(&self) -> u64 {
198 self.physical_generation
199 }
200
201 #[must_use]
203 pub const fn current_generation(&self) -> u64 {
204 self.ledger.current_generation
205 }
206
207 pub(crate) fn into_ledger(self) -> AllocationLedger {
208 self.ledger
209 }
210}
211
212impl AllocationHistory {
213 #[cfg(test)]
214 pub(crate) const fn from_parts(
215 records: Vec<AllocationRecord>,
216 generations: Vec<GenerationRecord>,
217 ) -> Self {
218 Self {
219 records,
220 generations,
221 }
222 }
223
224 #[must_use]
226 pub fn records(&self) -> &[AllocationRecord] {
227 &self.records
228 }
229
230 #[must_use]
232 pub fn generations(&self) -> &[GenerationRecord] {
233 &self.generations
234 }
235
236 #[must_use]
238 pub fn is_empty(&self) -> bool {
239 self.records.is_empty() && self.generations.is_empty()
240 }
241
242 pub(crate) const fn records_mut(&mut self) -> &mut Vec<AllocationRecord> {
243 &mut self.records
244 }
245
246 #[cfg(test)]
247 pub(crate) const fn generations_mut(&mut self) -> &mut Vec<GenerationRecord> {
248 &mut self.generations
249 }
250
251 pub(crate) fn push_record(&mut self, record: AllocationRecord) {
252 self.records.push(record);
253 }
254
255 pub(crate) fn push_generation(&mut self, generation: GenerationRecord) {
256 self.generations.push(generation);
257 }
258}
259
260impl SchemaMetadataRecord {
261 pub fn new(generation: u64, schema: SchemaMetadata) -> Result<Self, SchemaMetadataError> {
263 schema.validate()?;
264 Ok(Self { generation, schema })
265 }
266
267 #[must_use]
269 pub const fn generation(&self) -> u64 {
270 self.generation
271 }
272
273 #[must_use]
275 pub const fn schema(&self) -> &SchemaMetadata {
276 &self.schema
277 }
278}
279
280impl GenerationRecord {
281 pub fn new(
283 generation: u64,
284 parent_generation: u64,
285 runtime_fingerprint: Option<String>,
286 declaration_count: u32,
287 committed_at: Option<u64>,
288 ) -> Result<Self, DeclarationSnapshotError> {
289 validate_runtime_fingerprint(runtime_fingerprint.as_deref())?;
290 Ok(Self {
291 generation,
292 parent_generation,
293 runtime_fingerprint,
294 declaration_count,
295 committed_at,
296 })
297 }
298
299 #[must_use]
301 pub const fn generation(&self) -> u64 {
302 self.generation
303 }
304
305 #[must_use]
307 pub const fn parent_generation(&self) -> u64 {
308 self.parent_generation
309 }
310
311 #[must_use]
313 pub fn runtime_fingerprint(&self) -> Option<&str> {
314 self.runtime_fingerprint.as_deref()
315 }
316
317 #[must_use]
319 pub const fn declaration_count(&self) -> u32 {
320 self.declaration_count
321 }
322
323 #[must_use]
325 pub const fn committed_at(&self) -> Option<u64> {
326 self.committed_at
327 }
328}
329
330impl AllocationRecord {
331 #[must_use]
333 pub(crate) fn from_declaration(
334 generation: u64,
335 declaration: AllocationDeclaration,
336 state: AllocationState,
337 ) -> Self {
338 Self {
339 stable_key: declaration.stable_key,
340 slot: declaration.slot,
341 state,
342 first_generation: generation,
343 last_seen_generation: generation,
344 retired_generation: None,
345 schema_history: vec![
346 SchemaMetadataRecord::new(generation, declaration.schema)
347 .expect("declarations validate schema metadata"),
348 ],
349 }
350 }
351
352 #[must_use]
354 pub(crate) fn reserved(generation: u64, declaration: AllocationDeclaration) -> Self {
355 Self::from_declaration(generation, declaration, AllocationState::Reserved)
356 }
357
358 #[must_use]
360 pub const fn stable_key(&self) -> &StableKey {
361 &self.stable_key
362 }
363
364 #[must_use]
366 pub const fn slot(&self) -> &AllocationSlotDescriptor {
367 &self.slot
368 }
369
370 #[must_use]
372 pub const fn state(&self) -> AllocationState {
373 self.state
374 }
375
376 #[must_use]
378 pub const fn first_generation(&self) -> u64 {
379 self.first_generation
380 }
381
382 #[must_use]
384 pub const fn last_seen_generation(&self) -> u64 {
385 self.last_seen_generation
386 }
387
388 #[must_use]
390 pub const fn retired_generation(&self) -> Option<u64> {
391 self.retired_generation
392 }
393
394 #[must_use]
396 pub fn schema_history(&self) -> &[SchemaMetadataRecord] {
397 &self.schema_history
398 }
399
400 pub(crate) fn observe_declaration(
401 &mut self,
402 generation: u64,
403 declaration: &AllocationDeclaration,
404 ) {
405 self.last_seen_generation = generation;
406 if self.state == AllocationState::Reserved {
407 self.state = AllocationState::Active;
408 }
409
410 let latest_schema = self.schema_history.last().map(|record| &record.schema);
411 if latest_schema != Some(&declaration.schema) {
412 self.schema_history.push(
413 SchemaMetadataRecord::new(generation, declaration.schema.clone())
414 .expect("declarations validate schema metadata"),
415 );
416 }
417 }
418
419 pub(crate) fn observe_reservation(
420 &mut self,
421 generation: u64,
422 reservation: &AllocationDeclaration,
423 ) {
424 self.last_seen_generation = generation;
425
426 let latest_schema = self.schema_history.last().map(|record| &record.schema);
427 if latest_schema != Some(&reservation.schema) {
428 self.schema_history.push(
429 SchemaMetadataRecord::new(generation, reservation.schema.clone())
430 .expect("reservations validate schema metadata"),
431 );
432 }
433 }
434}
435
436impl AllocationLedger {
437 pub fn new(
445 current_generation: u64,
446 allocation_history: AllocationHistory,
447 ) -> Result<Self, LedgerIntegrityError> {
448 let ledger = Self {
449 current_generation,
450 allocation_history,
451 };
452 ledger.validate_integrity()?;
453 Ok(ledger)
454 }
455
456 pub fn new_committed(
462 current_generation: u64,
463 allocation_history: AllocationHistory,
464 ) -> Result<Self, LedgerIntegrityError> {
465 let ledger = Self::new(current_generation, allocation_history)?;
466 ledger.validate_committed_integrity()?;
467 Ok(ledger)
468 }
469
470 #[must_use]
472 pub const fn current_generation(&self) -> u64 {
473 self.current_generation
474 }
475
476 #[must_use]
478 pub const fn allocation_history(&self) -> &AllocationHistory {
479 &self.allocation_history
480 }
481}