1use crate::{
2 capability::{CommittedAllocations, ValidatedAllocations},
3 declaration::DeclarationSnapshot,
4 ledger::{
5 AllocationLedger, AllocationStageError, LedgerCommitError, LedgerCommitStore,
6 stage_validated_generation,
7 },
8 policy::AllocationPolicy,
9 validation::{AllocationValidationError, validate_allocations},
10};
11use std::borrow::Cow;
12
13#[derive(Debug)]
22pub struct AllocationBootstrap<'store> {
23 store: &'store mut LedgerCommitStore,
24}
25
26impl<'store> AllocationBootstrap<'store> {
27 pub const fn new(store: &'store mut LedgerCommitStore) -> Self {
29 Self { store }
30 }
31
32 pub(crate) fn validate_against<P>(
33 &mut self,
34 prior: crate::RecoveredLedger,
35 snapshot: DeclarationSnapshot,
36 policy: &P,
37 ) -> Result<PendingBootstrapCommit, BootstrapError<P::Error>>
38 where
39 P: AllocationPolicy,
40 {
41 let validated =
42 validate_allocations(&prior, snapshot, policy).map_err(BootstrapError::Validation)?;
43 let staged = stage_validated_generation(Cow::Owned(prior.into_ledger()), &validated)
44 .map_err(BootstrapError::Staging)?;
45 self.store
46 .commit_generation(&staged)
47 .map_err(BootstrapError::Ledger)?;
48
49 Ok(PendingBootstrapCommit {
50 validated,
51 ledger: staged,
52 })
53 }
54}
55
56#[derive(Debug, Eq, PartialEq)]
68#[must_use = "persist the owning ledger record, then confirm_persisted before opening allocations"]
69pub struct PendingBootstrapCommit {
70 ledger: AllocationLedger,
72 validated: ValidatedAllocations,
74}
75
76impl PendingBootstrapCommit {
77 #[must_use]
83 #[cfg(test)]
84 pub const fn ledger(&self) -> &AllocationLedger {
85 &self.ledger
86 }
87
88 #[must_use]
94 pub fn confirm_persisted(self) -> CommittedAllocations {
95 self.validated
96 .confirm_persisted(self.ledger.current_generation())
97 }
98}
99
100#[non_exhaustive]
105#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
106pub enum BootstrapError<P> {
107 #[error(transparent)]
109 Ledger(LedgerCommitError),
110 #[error(transparent)]
112 Validation(AllocationValidationError<P>),
113 #[error(transparent)]
115 Staging(AllocationStageError),
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use crate::{
122 AllocationDeclaration, GenericAllocationPolicy, MemoryManagerSlot, SchemaMetadata,
123 };
124 #[derive(Debug, Eq, PartialEq)]
125 struct RejectActivePolicy;
126
127 impl AllocationPolicy for RejectActivePolicy {
128 type Error = &'static str;
129
130 fn validate_key(&self, _key: &crate::StableKey) -> Result<(), Self::Error> {
131 Ok(())
132 }
133
134 fn validate_slot(
135 &self,
136 _key: &crate::StableKey,
137 _slot: &MemoryManagerSlot,
138 ) -> Result<(), Self::Error> {
139 Err("active slot rejected")
140 }
141
142 fn validate_reserved_slot(
143 &self,
144 _key: &crate::StableKey,
145 _slot: &MemoryManagerSlot,
146 ) -> Result<(), Self::Error> {
147 Ok(())
148 }
149 }
150
151 fn ledger() -> AllocationLedger {
152 AllocationLedger::empty_genesis()
153 }
154
155 fn declaration() -> AllocationDeclaration {
156 AllocationDeclaration::new(
157 "app.users.v1",
158 MemoryManagerSlot::new(100).expect("usable slot"),
159 None,
160 SchemaMetadata::default(),
161 )
162 .expect("declaration")
163 }
164
165 #[test]
166 fn validated_commit_matches_recovered_generation() {
167 let mut store = LedgerCommitStore::default();
168 let recovered = store.recover_or_initialize(&ledger()).unwrap();
169 let snapshot = DeclarationSnapshot::new(vec![declaration()]).unwrap();
170 let commit = AllocationBootstrap::new(&mut store)
171 .validate_against(recovered, snapshot, &GenericAllocationPolicy)
172 .unwrap();
173 assert_eq!(commit.ledger().current_generation(), 1);
174 assert_eq!(commit.ledger().records().len(), 1);
175 assert_eq!(store.recover().unwrap().ledger(), commit.ledger());
176 assert_eq!(commit.confirm_persisted().generation(), 1);
177 }
178
179 #[test]
180 fn reservation_policy_alone_does_not_activate_reserved_allocation() {
181 let mut store = LedgerCommitStore::default();
182 let reserved = ledger()
183 .stage_reservation_generation(&[declaration()])
184 .unwrap();
185 store.commit(&reserved).unwrap();
186 let before = store.clone();
187 let recovered = store.recover().unwrap();
188 let snapshot = DeclarationSnapshot::new(vec![declaration()]).unwrap();
189 let err = AllocationBootstrap::new(&mut store)
190 .validate_against(recovered, snapshot, &RejectActivePolicy)
191 .expect_err("active validation must run");
192 assert!(matches!(
193 err,
194 BootstrapError::Validation(AllocationValidationError::Policy("active slot rejected"))
195 ));
196 assert_eq!(store, before);
197 assert_eq!(
198 store.recover().unwrap().ledger().records()[0].state(),
199 crate::AllocationState::Reserved
200 );
201 }
202}