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 SchemaMetadata::default(),
121 )
122 .expect("declaration")
123 }
124
125 #[test]
126 fn validated_commit_matches_recovered_generation() {
127 let mut store = LedgerCommitStore::default();
128 let recovered = store.recover_or_initialize(&ledger()).unwrap();
129 let snapshot = DeclarationSnapshot::new(vec![declaration()]).unwrap();
130 let commit = crate::bootstrap::prepare_commit(
131 &mut store,
132 recovered,
133 snapshot,
134 &GenericAllocationPolicy,
135 )
136 .unwrap();
137 let recovered = store.recover().unwrap();
138 assert_eq!(recovered.current_generation(), 1);
139 assert_eq!(recovered.ledger().records().len(), 1);
140 assert_eq!(
141 recovered.ledger().records()[0],
142 crate::AllocationRecord::active(&declaration())
143 );
144 assert_eq!(commit.confirm_persisted().generation(), 1);
145 }
146
147 #[test]
148 fn reservation_policy_alone_does_not_activate_reserved_allocation() {
149 let mut store = LedgerCommitStore::default();
150 let reserved = ledger()
151 .stage_reservation_generation(&[declaration()])
152 .unwrap();
153 store.commit(&reserved).unwrap();
154 let before = store.clone();
155 let recovered = store.recover().unwrap();
156 let snapshot = DeclarationSnapshot::new(vec![declaration()]).unwrap();
157 let err =
158 crate::bootstrap::prepare_commit(&mut store, recovered, snapshot, &RejectActivePolicy)
159 .expect_err("active validation must run");
160 assert!(matches!(
161 err,
162 BootstrapError::Validation(AllocationValidationError::Policy("active slot rejected"))
163 ));
164 assert_eq!(store, before);
165 assert_eq!(
166 store.recover().unwrap().ledger().records()[0].state(),
167 crate::AllocationState::Reserved
168 );
169 }
170}