1use crate::{
2 capability::{CommittedAllocations, ValidatedAllocations},
3 declaration::DeclarationSnapshot,
4 ledger::{
5 AllocationStageError, LedgerCommitError, LedgerCommitStore, stage_validated_generation,
6 },
7 policy::AllocationPolicy,
8 validation::{AllocationValidationError, validate_allocations},
9};
10use std::borrow::Cow;
11
12pub fn prepare_commit<P>(
14 store: &mut LedgerCommitStore,
15 prior: crate::RecoveredLedger,
16 snapshot: DeclarationSnapshot,
17 policy: &P,
18) -> Result<PendingBootstrapCommit, BootstrapError<P::Error>>
19where
20 P: AllocationPolicy,
21{
22 let validated =
23 validate_allocations(&prior, snapshot, policy).map_err(BootstrapError::Validation)?;
24 let staged = stage_validated_generation(Cow::Owned(prior.into_ledger()), &validated)
25 .map_err(BootstrapError::Staging)?;
26 store
27 .commit_generation(&staged)
28 .map_err(BootstrapError::Ledger)?;
29
30 Ok(PendingBootstrapCommit {
31 validated,
32 generation: staged.current_generation(),
33 })
34}
35
36#[derive(Debug, Eq, PartialEq)]
48#[must_use = "persist the owning ledger record, then confirm_persisted before opening allocations"]
49pub struct PendingBootstrapCommit {
50 generation: u64,
52 validated: ValidatedAllocations,
54}
55
56impl PendingBootstrapCommit {
57 #[must_use]
63 pub fn confirm_persisted(self) -> CommittedAllocations {
64 self.validated.confirm_persisted(self.generation)
65 }
66}
67
68#[non_exhaustive]
73#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
74pub enum BootstrapError<P> {
75 #[error(transparent)]
77 Ledger(LedgerCommitError),
78 #[error(transparent)]
80 Validation(AllocationValidationError<P>),
81 #[error(transparent)]
83 Staging(AllocationStageError),
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use crate::{
90 AllocationDeclaration, AllocationLedger, GenericAllocationPolicy, MemoryManagerSlot,
91 SchemaMetadata,
92 };
93 #[derive(Debug, Eq, PartialEq)]
94 struct RejectActivePolicy;
95
96 impl AllocationPolicy for RejectActivePolicy {
97 type Error = &'static str;
98
99 fn validate_key(&self, _key: &crate::StableKey) -> Result<(), Self::Error> {
100 Ok(())
101 }
102
103 fn validate_slot(
104 &self,
105 _key: &crate::StableKey,
106 _slot: &MemoryManagerSlot,
107 ) -> Result<(), Self::Error> {
108 Err("active slot rejected")
109 }
110 }
111
112 fn ledger() -> AllocationLedger {
113 AllocationLedger::empty_genesis()
114 }
115
116 fn declaration() -> AllocationDeclaration {
117 AllocationDeclaration::new(
118 "app.users.v1",
119 MemoryManagerSlot::new(100).expect("usable slot"),
120 None,
121 SchemaMetadata::default(),
122 )
123 .expect("declaration")
124 }
125
126 #[test]
127 fn validated_commit_matches_recovered_generation() {
128 let mut store = LedgerCommitStore::default();
129 let recovered = store.recover_or_initialize(&ledger()).unwrap();
130 let snapshot = DeclarationSnapshot::new(vec![declaration()]).unwrap();
131 let commit = crate::bootstrap::prepare_commit(
132 &mut store,
133 recovered,
134 snapshot,
135 &GenericAllocationPolicy,
136 )
137 .unwrap();
138 let recovered = store.recover().unwrap();
139 assert_eq!(recovered.current_generation(), 1);
140 assert_eq!(recovered.ledger().records().len(), 1);
141 assert_eq!(
142 recovered.ledger().records()[0],
143 crate::AllocationRecord::active(&declaration())
144 );
145 assert_eq!(commit.confirm_persisted().generation(), 1);
146 }
147
148 #[test]
149 fn reservation_policy_alone_does_not_activate_reserved_allocation() {
150 let mut store = LedgerCommitStore::default();
151 let reserved = ledger()
152 .stage_reservation_generation(&[declaration()])
153 .unwrap();
154 store.commit(&reserved).unwrap();
155 let before = store.clone();
156 let recovered = store.recover().unwrap();
157 let snapshot = DeclarationSnapshot::new(vec![declaration()]).unwrap();
158 let err =
159 crate::bootstrap::prepare_commit(&mut store, recovered, snapshot, &RejectActivePolicy)
160 .expect_err("active validation must run");
161 assert!(matches!(
162 err,
163 BootstrapError::Validation(AllocationValidationError::Policy("active slot rejected"))
164 ));
165 assert_eq!(store, before);
166 assert_eq!(
167 store.recover().unwrap().ledger().records()[0].state(),
168 crate::AllocationState::Reserved
169 );
170 }
171}