1use super::commit::{
2 StoreCommitVerifier, StoreMembershipObjectVerifier, VerifiedMergeMembershipClosure,
3};
4use crate::sync::store::pull;
5use crate::sync::store::pull::*;
6use coven_database::VerifiedStoreSnapshotAuthority;
7use coven_database::{DeviceJoinBootstrapCommit, DeviceJoinBootstrapPlan};
8use coven_protocol::circle_activation::VerifiedCircleActivations;
9use coven_protocol::circle_control::StoreMembershipStateRef;
10use coven_protocol::membership::MembershipChain;
11use coven_protocol::objects::{ProtocolObjectContext, ProtocolObjectDomain};
12use coven_protocol::objects::{StoreObjectError, VerifiedObject};
13use coven_protocol::store_commit::{
14 ActivatedStoreDeviceRegistration, ActivatedStoreDeviceRegistrationRef, CommitFrontier,
15 DeviceJoinAttemptDecisionRef, OpenedRetainedMergeHistorySummary, OwnerRecoveryNode,
16 OwnerRecoveryNodeRef, ReferencedStoreDeviceRegistration, ResolvedStoreDeviceState,
17 RetainedVerifiedMergeHistorySummary, StoreBatchCommit, StoreBatchCommitRef, StoreCommitCoord,
18 StoreDeviceProposalState, StoreDeviceRegistration, StoreDeviceRegistrationActivation,
19 StoreDeviceRegistrationActivationRef, StoreDeviceRegistrationOrigin,
20 StoreDeviceRegistrationRef, StoreDeviceStateRef, StoreDeviceStatus, StoreHistoryCut,
21 StoreProtocolError, StoreRootRef, VerifiedStoreBatchCommit, VerifiedStoreDeviceOperations,
22};
23use coven_protocol::store_commit::{
24 SnapshotMeta, StoreAck, StoreAckRef, StoreDeviceExclusionOutcomeRef,
25 StoreDeviceExclusionProposalRef, StoreSnapshotRef, VerifiedDeviceExclusionOutcome,
26 VerifiedDeviceExclusionProposal,
27};
28use coven_protocol::{membership as protocol_membership, provider, store_commit};
29use std::collections::{BTreeMap, BTreeSet};
30
31use crate::sync::store::device_join;
32
33mod acknowledgements;
34mod device_join_verification;
35mod loaders;
36mod membership_control;
37mod nonactivation;
38mod predecessor;
39use predecessor::{
40 predecessor_verifies_provider_administrator, predecessor_verifies_provider_administrator_grant,
41};
42mod promotion;
43mod publication;
44mod rollup;
45mod snapshot_retirement;
46mod snapshots;
47mod stream;
48mod successor;
49pub use membership_control::VerifiedMergeMembershipPrefix;
50pub(crate) use membership_control::{
51 merge_membership_state_ref, verified_merge_membership_prefix,
52 verify_merge_membership_state_ref, VerifiedMergeMembershipControl,
53 VerifiedMergeMembershipHeadActivation, VerifiedMergePrefixHeadStatus,
54};
55pub(crate) use predecessor::{predecessor_verifies_owner, VerifiedMergePredecessorHistory};
56pub(crate) use promotion::VerifiedOwnerPromotionRequestActivation;
57pub(crate) use publication::{
58 AcceptedStoreSnapshot, StorePublicationReplayInstallation, VerifiedStorePublication,
59};
60pub(crate) use snapshots::SelectedStoreSnapshot;
61pub use successor::MergeHistorySuccessorEvidence;
62pub use successor::PreparedMergeHistorySuccessor;
63pub(crate) use successor::{
64 compose_merge_snapshot_history_summary, compose_verified_merge_snapshot_history_summary,
65};
66#[cfg(test)]
67pub(crate) use successor::{insert_latest_acknowledgement, merge_retained_merge_history};
68mod membership;
69pub use membership::AcceptedMembershipAuthority;
70use membership::VerifiedPrefixMembershipActivation;
71pub(crate) mod registration;
72pub(crate) use registration::RegistrationLoadError;
73use registration::*;
74
75#[derive(Clone)]
76pub(crate) struct VerifiedMergeHistoryCommit {
77 pub(crate) verified: VerifiedStoreBatchCommit,
78 pub(crate) predecessor_membership: MembershipChain,
79 pub(crate) predecessor_state: ResolvedStoreDeviceState,
80 pub(crate) state_after: ResolvedStoreDeviceState,
81 pub(crate) registrations: Vec<ActivatedStoreDeviceRegistration>,
82 pub(crate) operations: VerifiedStoreDeviceOperations,
83 pub(crate) membership_control: Option<VerifiedMergeMembershipControl>,
84 pub(crate) history_evidence: store_commit::RetainedMergeCommitEvidence,
85}
86
87pub(crate) struct VerifiedMergeHistoryAuthority {
88 pub(crate) device_state: ResolvedStoreDeviceState,
89 pub(crate) membership: MembershipChain,
90}
91
92impl<'a> MergeHistoryVerifier<'a> {
93 fn cached_verified_membership(
94 &self,
95 state: &StoreMembershipStateRef,
96 authority: &VerifiedMergeMembershipPrefix,
97 ) -> Option<MembershipChain> {
98 self.verified_memberships
99 .iter()
100 .rev()
101 .find(|verified| {
102 verified.membership.head_refs() == state.heads
103 && authority.extends(&verified.authority)
104 })
105 .map(|verified| verified.membership.clone())
106 }
107
108 fn remember_verified_membership(
109 &mut self,
110 authority: VerifiedMergeMembershipPrefix,
111 membership: MembershipChain,
112 ) {
113 if self.verified_memberships.iter().any(|verified| {
114 verified.membership.head_refs() == membership.head_refs()
115 && authority.extends(&verified.authority)
116 }) {
117 return;
118 }
119 self.verified_memberships.push(VerifiedMembershipChain {
120 authority,
121 membership,
122 });
123 }
124
125 pub(crate) fn verified_root(&self) -> &crate::sync::store::protocol_root::VerifiedStoreRoot {
126 &self.root
127 }
128
129 pub(crate) fn membership_objects(&self) -> StoreMembershipObjectVerifier<'_, 'a> {
130 self.commit_verifier.membership_objects()
131 }
132
133 pub(crate) fn retain_acknowledgement(
134 &self,
135 activating_commit: &VerifiedStoreBatchCommit,
136 reference: StoreAckRef,
137 value: StoreAck,
138 ) -> Result<store_commit::RetainedVerifiedActivatedAck, StorePullError> {
139 if activating_commit.acknowledgement() != Some(&reference)
140 || activating_commit.author_registration != reference.registration
141 || value.registration != reference.registration
142 {
143 return Err(StorePullError::InvalidState(
144 "Store acknowledgement differs from its activating commit".to_string(),
145 ));
146 }
147 let object = value.to_bytes();
154 reference
155 .object
156 .verify(&object)
157 .map_err(|error| StorePullError::context("retained acknowledgement object", error))?;
158 StoreAck::parse_at(
159 &object,
160 self.root.reference(),
161 &reference,
162 activating_commit.author(),
163 )
164 .map_err(StorePullError::Protocol)?;
165 Ok(store_commit::RetainedVerifiedActivatedAck {
166 acknowledgement: (reference, value),
167 activating_commit: activating_commit.reference().clone(),
168 predecessors: Vec::new(),
169 })
170 }
171
172 pub(crate) async fn load_local_device_operations(
173 &mut self,
174 verified_commit: &VerifiedStoreBatchCommit,
175 membership: &MembershipChain,
176 state_ref: &StoreDeviceStateRef,
177 state: ResolvedStoreDeviceState,
178 ) -> Result<VerifiedStoreDeviceOperations, StorePullError> {
179 if verified_commit.store_root_hash() != self.root.reference().store_root_hash {
180 return Err(StorePullError::InvalidState(
181 "local device-operation commit belongs to another Store root".to_string(),
182 ));
183 }
184 let commit = verified_commit.value();
185 if commit.device_exclusion_proposals().is_empty()
186 && commit.device_exclusion_outcomes().is_empty()
187 {
188 return VerifiedStoreDeviceOperations::without_exclusions(commit)
189 .map_err(StorePullError::Protocol);
190 }
191 if state_ref != &commit.device_state {
192 return Err(StorePullError::InvalidState(
193 "local exclusion commit differs from its materialized predecessor device state"
194 .to_string(),
195 ));
196 }
197 verify_merge_membership_state_ref(&commit.membership_state, membership, &state)?;
198 Box::pin(
199 self.commit_verifier
200 .load_commit_device_operations(commit, &state, membership),
201 )
202 .await
203 .map_err(StorePullError::from)
204 }
205
206 pub(crate) async fn derive_local_post_device_state(
207 &self,
208 commit: &StoreBatchCommit,
209 predecessor_state: ResolvedStoreDeviceState,
210 registrations: &[ActivatedStoreDeviceRegistration],
211 device_operations: VerifiedStoreDeviceOperations,
212 ) -> Result<ResolvedStoreDeviceState, StorePullError> {
213 let (authorized_predecessor, recovery_author) = predecessor_state
214 .preactivate_recovery_author(commit, registrations)
215 .map_err(StorePullError::Protocol)?;
216 let owner_recovery = self
217 .commit_verifier
218 .verify_owner_recovery_activation(commit)
219 .await?;
220 device_operations
221 .apply_to(authorized_predecessor)
222 .and_then(|state| {
223 state.apply_verified_lifecycle(
224 commit,
225 registrations,
226 recovery_author.as_ref(),
227 owner_recovery,
228 )
229 })
230 .map_err(StorePullError::Protocol)
231 }
232
233 pub(crate) async fn from_commit_verifier(
241 _authority: crate::sync::store::authorization::HistoryConstructionAuthority,
242 root: crate::sync::store::protocol_root::VerifiedStoreRoot,
243 commit_verifier: StoreCommitVerifier<'a>,
244 ) -> Result<Self, StorePullError> {
245 let founder = commit_verifier.load_founder_registration().await?;
246 let founder = &founder;
247 let verified_root = root.protocol();
248 let founder_ref =
249 StoreDeviceRegistrationRef::from_registration(&founder.value, founder.object.clone());
250 let founder_origin_matches = matches!(
251 founder.value.origin,
252 store_commit::StoreDeviceRegistrationOrigin::Founder { creation_id }
253 if creation_id == verified_root.descriptor.creation_id
254 );
255 if founder.value.store_root != *root.reference()
256 || founder.value.author_pubkey != verified_root.descriptor.founder_pubkey
257 || founder.value.provider != verified_root.descriptor.founder_provider_admin.provider
258 || founder.object.slot() != &verified_root.descriptor.founder_registration
259 || founder.semantic_hash != founder_ref.registration_hash
260 || !founder_origin_matches
261 {
262 return Err(StorePullError::InvalidState(
263 "verified founder registration belongs to another Store root".to_string(),
264 ));
265 }
266 let genesis = ResolvedStoreDeviceState::founder(
267 root.reference(),
268 founder_ref.clone(),
269 &verified_root.descriptor.founder_pubkey,
270 verified_root.descriptor.founder_grant.clone(),
271 &verified_root.descriptor.founder_recovery,
272 )
273 .map_err(StorePullError::Protocol)?;
274 Ok(Self {
275 root,
276 commit_verifier,
277 founder: founder_ref,
278 accepted_publications: BTreeMap::new(),
279 history: VerifiedMergeHistory {
280 genesis,
281 baseline: coven_database::InstalledReplayBaseline::default(),
282 retained: BTreeMap::new(),
283 commits: BTreeMap::new(),
284 },
285 verified_memberships: Vec::new(),
286 })
287 }
288
289 pub(crate) async fn covered_reference_status(
290 &mut self,
291 coverage: &CommitFrontier,
292 reference: &StoreBatchCommitRef,
293 ) -> MaterializedCheck {
294 let Some(covered) = coverage.commits().get(&reference.coord.stream_id) else {
295 return MaterializedCheck::Missing;
296 };
297 if reference.coord.sequence() > covered.coord.sequence() {
298 return MaterializedCheck::Missing;
299 }
300 let mut cursor = covered.clone();
301 loop {
302 if cursor == *reference {
303 return MaterializedCheck::Yes;
304 }
305 if cursor.coord.sequence() <= reference.coord.sequence() {
306 return MaterializedCheck::Held(HeldStorePositionReason::HashMismatch {
307 referenced_device_id: commit_stream_id(&reference.coord),
308 referenced_commit: reference.clone(),
309 materialized_hash: cursor.commit_hash,
310 });
311 }
312 let verified_commit = match self.load_ref(&cursor).await {
313 Ok(commit) => commit,
314 Err(error) => {
315 return MaterializedCheck::Held(
316 HeldStorePositionReason::ObjectUnreadablePull {
317 key: "exact Store commit".to_string(),
318 source: error.into(),
319 },
320 );
321 }
322 };
323 let Some(predecessor) = verified_commit.value().order.predecessor() else {
324 return MaterializedCheck::Missing;
325 };
326 cursor = predecessor.clone();
327 }
328 }
329
330 pub(crate) async fn validate_commit_acknowledgement(
331 &self,
332 commit: &StoreBatchCommit,
333 activating_author: &StoreDeviceRegistration,
334 ) -> Result<Option<(StoreAckRef, StoreAck)>, RegistrationLoadError> {
335 let Some(reference) = commit.acknowledgement() else {
336 return Ok(None);
337 };
338 let ack = self
339 .load_store_ack(reference, activating_author)
340 .await
341 .map_err(RegistrationLoadError::Object)?;
342 let predecessor_cut = commit
343 .order
344 .predecessor_cut()
345 .map_err(RegistrationLoadError::from)?;
346 if ack.registration != commit.author_registration
347 || ack.store_cut != predecessor_cut
348 || ack.device_state != commit.device_state
349 {
350 return Err(RegistrationLoadError::Invalid(
351 "Store acknowledgement differs from its activating commit predecessor".to_string(),
352 ));
353 }
354 Ok(Some((reference.clone(), ack)))
355 }
356
357 pub(crate) fn remember(
358 &mut self,
359 commit: VerifiedStoreBatchCommit,
360 ) -> Result<(), StoreProtocolError> {
361 self.commit_verifier.remember(commit)
362 }
363
364 pub(crate) fn admit_installed_baseline(
371 &mut self,
372 baseline: coven_database::InstalledReplayBaseline,
373 ) -> Result<(), StorePullError> {
374 let baseline_changed = self.history.baseline.coverage() != baseline.coverage()
375 || self
376 .history
377 .baseline
378 .snapshot()
379 .map(|snapshot| &snapshot.reference)
380 != baseline.snapshot().map(|snapshot| &snapshot.reference);
381 if baseline_changed {
382 self.history.retained.clear();
383 self.history.commits.clear();
384 self.accepted_publications.clear();
385 self.verified_memberships.clear();
386 }
387 if let Some(snapshot) = baseline.snapshot() {
396 let summary = &snapshot.meta.history_summary;
397 for proof in summary.membership_proofs.values() {
398 self.membership_objects().remember_retained_proof(proof)?;
399 }
400 for reference in summary.causal_cut.values() {
401 self.accepted_publications
402 .entry(reference.clone())
403 .or_insert(AcceptedStoreCommitEvidence::SnapshotCovered);
404 }
405 for chain in summary.acknowledgements.values() {
406 for (reference, value) in chain.chain.values() {
407 self.commit_verifier
408 .remember_acknowledgement(reference, value)
409 .map_err(StorePullError::Protocol)?;
410 }
411 }
412 }
413 self.history.baseline = baseline;
414 Ok(())
415 }
416
417 pub(crate) fn admit_retained_history(
418 &mut self,
419 retained: &[coven_database::OwnedVerifiedMergeMaterialization],
420 ) -> Result<(), StorePullError> {
421 for materialization in retained {
422 if let Some(proof) = &materialization.history_evidence().membership_proof {
423 self.membership_objects().remember_retained_proof(proof)?;
424 }
425 let commit_ref = materialization.commit_ref();
426 self.history
427 .retained
428 .insert(commit_ref.clone(), materialization.registrations().to_vec());
429 self.accepted_publications.insert(
430 commit_ref.clone(),
431 match materialization.acceptance().exact_publication() {
432 Some(publication) => AcceptedStoreCommitEvidence::Exact(publication.clone()),
433 None => AcceptedStoreCommitEvidence::SnapshotCovered,
434 },
435 );
436 self.commit_verifier
437 .remember(materialization.verified_commit().clone())
438 .map_err(StorePullError::Protocol)?;
439 if let Some(activated) = &materialization.history_evidence().acknowledgement {
444 for (reference, value) in activated.proof_objects() {
445 self.commit_verifier
446 .remember_acknowledgement(reference, value)
447 .map_err(StorePullError::Protocol)?;
448 }
449 }
450 }
451 Ok(())
452 }
453
454 pub(crate) async fn retain_local_same_principal_join_activation(
455 &mut self,
456 materialization: coven_database::OwnedVerifiedMergeMaterialization,
457 ) -> Result<(), StorePullError> {
458 let reference = materialization.commit_ref().clone();
459 self.admit_retained_history(std::slice::from_ref(&materialization))?;
460 self.verify_refs([reference]).await
461 }
462
463 pub(crate) fn verified_predecessor_state(
464 &self,
465 commit: &StoreBatchCommit,
466 ) -> Result<ResolvedStoreDeviceState, StorePullError> {
467 let frontier = commit.order.predecessor_cut()?.frontier();
468 let state = self.history.state_at_frontier(&frontier)?;
469 if commit.device_state != StoreDeviceStateRef::from_resolved(frontier, &state)? {
470 return Err(StorePullError::InvalidState(
471 "Merge commit names another predecessor device state".into(),
472 ));
473 }
474 Ok(state)
475 }
476
477 pub(crate) fn verified_membership_prefix(
478 &self,
479 predecessors: impl IntoIterator<Item = StoreBatchCommitRef>,
480 ) -> Result<VerifiedMergeMembershipPrefix, StorePullError> {
481 verified_merge_membership_prefix(&self.history, predecessors)
482 }
483
484 pub(crate) fn verified_pull_candidate(
485 &self,
486 reference: &StoreBatchCommitRef,
487 ) -> Option<pull::VerifiedPullCandidate> {
488 self.history
489 .commits
490 .get(reference)
491 .map(|commit| pull::VerifiedPullCandidate {
492 verified: commit.verified.clone(),
493 predecessor_membership: commit.predecessor_membership.clone(),
494 registrations: commit.registrations.clone(),
495 operations: commit.operations.clone(),
496 membership_control: commit
497 .membership_control
498 .as_ref()
499 .map(|control| control.activations.clone()),
500 })
501 }
502
503 pub(crate) fn verify_commit_acceptance(
504 &self,
505 reference: &StoreBatchCommitRef,
506 ) -> Result<(), StorePullError> {
507 if !self.accepted_publications.contains_key(reference) {
508 return Err(StorePullError::InvalidState(
509 "Store commit has no verified acceptance evidence".to_string(),
510 ));
511 }
512 Ok(())
513 }
514
515 pub(crate) fn accepted_publication(
516 &self,
517 reference: &StoreBatchCommitRef,
518 ) -> Option<&coven_database::AcceptedStoreCommitPublication> {
519 match self.accepted_publications.get(reference) {
520 Some(AcceptedStoreCommitEvidence::Exact(publication)) => Some(publication),
521 Some(AcceptedStoreCommitEvidence::SnapshotCovered) | None => None,
522 }
523 }
524
525 pub(super) async fn verify_membership_head_activation(
526 &mut self,
527 reference: &protocol_membership::MembershipHeadRef,
528 head: &protocol_membership::AuthorHead,
529 activation: &StoreBatchCommitRef,
530 ) -> Result<bool, StorePullError> {
531 if !self.accepted_publications.contains_key(activation) {
532 return Ok(false);
533 }
534 self.verify_refs([activation.clone()]).await?;
535 let prefix = verified_merge_membership_prefix(&self.history, [activation.clone()])?;
536 if !prefix
537 .head_activation(activation)
538 .is_some_and(|proof| proof.verifies(reference, head, activation))
539 {
540 return Err(StorePullError::InvalidState(
541 "membership head activation differs from its verified Merge membership control"
542 .to_string(),
543 ));
544 }
545 Ok(true)
546 }
547
548 pub(crate) async fn verify_merge_history_authority(
549 &mut self,
550 frontier: &BTreeMap<protocol_membership::AuthorStreamId, StoreBatchCommitRef>,
551 membership_state: &StoreMembershipStateRef,
552 ) -> Result<VerifiedMergeHistoryAuthority, StorePullError> {
553 self.verify_refs(frontier.values().cloned()).await?;
554 let (device_state, verified_membership_activations) =
555 self.verified_merge_history_authority_parts(frontier)?;
556 let membership = match self
557 .cached_verified_membership(membership_state, &verified_membership_activations)
558 {
559 Some(membership) => membership,
560 None => self
561 .load_membership_at_verified_prefix(
562 &membership_state.heads,
563 &verified_membership_activations,
564 )
565 .await
566 .map_err(StorePullError::MembershipChain)?,
567 };
568 verified_membership_activations.validate_complete_membership(&membership)?;
569 verify_merge_membership_state_ref(membership_state, &membership, &device_state)?;
570 self.remember_verified_membership(verified_membership_activations, membership.clone());
571 Ok(VerifiedMergeHistoryAuthority {
572 device_state,
573 membership,
574 })
575 }
576
577 fn verified_merge_history_authority_parts(
578 &self,
579 frontier: &BTreeMap<protocol_membership::AuthorStreamId, StoreBatchCommitRef>,
580 ) -> Result<(ResolvedStoreDeviceState, VerifiedMergeMembershipPrefix), StorePullError> {
581 let device_state = self
582 .history
583 .state_at_frontier(&CommitFrontier(frontier.clone()))?;
584 let membership =
585 verified_merge_membership_prefix(&self.history, frontier.values().cloned())?;
586 Ok((device_state, membership))
587 }
588}
589
590fn verified_merge_commit_closure(
597 history: &VerifiedMergeHistory,
598 tips: impl IntoIterator<Item = StoreBatchCommitRef>,
599) -> Result<BTreeSet<StoreBatchCommitRef>, StorePullError> {
600 let mut pending = tips.into_iter().collect::<Vec<_>>();
601 let mut closure = BTreeSet::new();
602 while let Some(reference) = pending.pop() {
603 if !closure.insert(reference.clone()) {
604 continue;
605 }
606 if history.superseded(&reference) {
607 continue;
608 }
609 let verified = history.commits.get(&reference).ok_or_else(|| {
610 StorePullError::InvalidState(
611 "verified Merge predecessor closure is absent from its history".to_string(),
612 )
613 })?;
614 pending.extend(commit_predecessor_references(verified.verified.value()));
615 }
616 Ok(closure)
617}
618
619#[derive(Clone)]
620pub(crate) struct VerifiedMergeHistory {
621 pub(crate) genesis: ResolvedStoreDeviceState,
622 pub(crate) baseline: coven_database::InstalledReplayBaseline,
629 pub(crate) retained: BTreeMap<StoreBatchCommitRef, Vec<ActivatedStoreDeviceRegistration>>,
637 pub(crate) commits: BTreeMap<StoreBatchCommitRef, VerifiedMergeHistoryCommit>,
638}
639
640impl VerifiedMergeHistory {
641 pub(crate) fn superseded(&self, reference: &StoreBatchCommitRef) -> bool {
645 self.baseline.covers(reference) && !self.retained.contains_key(reference)
646 }
647
648 pub(crate) fn retained_registrations(
657 &self,
658 reference: &StoreBatchCommitRef,
659 ) -> Option<&[ActivatedStoreDeviceRegistration]> {
660 self.retained
661 .get(reference)
662 .map(|registrations| registrations.as_slice())
663 }
664
665 pub(crate) fn state_after(
669 &self,
670 reference: &StoreBatchCommitRef,
671 ) -> Option<&ResolvedStoreDeviceState> {
672 self.commits
673 .get(reference)
674 .map(|commit| &commit.state_after)
675 .or_else(|| self.baseline.covered_state(reference))
676 }
677
678 fn is_baseline_tip(&self, reference: &StoreBatchCommitRef) -> bool {
679 self.baseline
680 .coverage()
681 .commits()
682 .get(&reference.coord.stream_id)
683 == Some(reference)
684 }
685
686 fn state_at_frontier(
690 &self,
691 frontier: &CommitFrontier,
692 ) -> Result<ResolvedStoreDeviceState, StorePullError> {
693 if frontier.commits().is_empty() {
694 return Ok(self.genesis.clone());
695 }
696 if frontier
697 .commits()
698 .values()
699 .all(|reference| self.state_after(reference).is_some())
700 {
701 return ResolvedStoreDeviceState::merge(frontier.commits().values().map(|reference| {
702 self.state_after(reference)
703 .expect("every exact frontier state was checked")
704 .clone()
705 }))
706 .map_err(StorePullError::Protocol);
707 }
708 let baseline = self.baseline.snapshot().ok_or_else(|| {
709 StorePullError::InvalidState("Merge history has an unresolved predecessor state".into())
710 })?;
711 let mut pending = frontier.commits().values().cloned().collect::<Vec<_>>();
712 let mut reached = BTreeSet::new();
713 while let Some(reference) = pending.pop() {
714 if !reached.insert(reference.clone()) || self.is_baseline_tip(&reference) {
715 continue;
716 }
717 let commit = self.commits.get(&reference).ok_or_else(|| {
718 StorePullError::InvalidState(
719 "Merge device-state cut lacks exact checkpoint ancestry".into(),
720 )
721 })?;
722 pending.extend(commit_predecessor_references(commit.verified.value()));
723 }
724 if self
725 .baseline
726 .coverage()
727 .commits()
728 .values()
729 .any(|reference| !reached.contains(reference))
730 {
731 return Err(StorePullError::InvalidState(
732 "Merge device-state cut does not include its complete checkpoint".into(),
733 ));
734 }
735 ResolvedStoreDeviceState::merge(
736 std::iter::once(baseline.meta.state.devices.clone()).chain(
737 frontier
738 .commits()
739 .values()
740 .filter_map(|reference| self.state_after(reference).cloned()),
741 ),
742 )
743 .map_err(StorePullError::Protocol)
744 }
745}
746
747#[derive(Clone)]
748struct VerifiedMembershipChain {
749 authority: VerifiedMergeMembershipPrefix,
750 membership: MembershipChain,
751}
752
753pub struct MergeHistoryVerifier<'a> {
754 root: crate::sync::store::protocol_root::VerifiedStoreRoot,
755 commit_verifier: StoreCommitVerifier<'a>,
756 founder: StoreDeviceRegistrationRef,
760 accepted_publications: BTreeMap<StoreBatchCommitRef, AcceptedStoreCommitEvidence>,
761 history: VerifiedMergeHistory,
762 verified_memberships: Vec<VerifiedMembershipChain>,
763}
764
765#[derive(Clone)]
766enum AcceptedStoreCommitEvidence {
767 Exact(coven_database::AcceptedStoreCommitPublication),
768 SnapshotCovered,
769}
770
771type PredecessorCommitPredicate<'a> = Box<dyn FnMut(&VerifiedStoreBatchCommit) -> bool + Send + 'a>;
772
773pub struct MergeOutboundAuthorization {
774 pub(crate) membership: MembershipChain,
775 pub(crate) membership_state: StoreMembershipStateRef,
776 pub(crate) device_state_ref: StoreDeviceStateRef,
777 pub(crate) device_state: ResolvedStoreDeviceState,
778}