Skip to main content

coven_replication/sync/store/commit_verification/
merge_history.rs

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        // Only the acknowledgement this commit activated. Its predecessors are
148        // retained beside the commits that activated them, and each
149        // acknowledgement names the object of the one before it, so the chain is
150        // walkable across rows. Walking it here and storing the result made the
151        // row grow with the history in front of it, and cost a provider read per
152        // link at the moment of applying a commit.
153        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    /// Bind a history verifier to its Store root.
234    ///
235    /// Reads the founder once, to confirm it belongs to this root and to derive
236    /// the genesis device state, then keeps only its reference. The registration
237    /// itself stays where every other one does — the commit verifier's
238    /// registration cache — so asking for it later is a lookup, not a copy held
239    /// here as well.
240    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    /// Use the database's committed replay baseline as the floor of history walks.
365    ///
366    /// A writer can advance that baseline while keeping this verifier alive.
367    /// When it changes, discard conclusions derived from the previous baseline;
368    /// retained inputs are admitted and verified against the replacement next.
369    /// Independently authenticated object bytes remain reusable.
370    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        // Every acknowledgement the baseline's signed summary states, admitted
388        // before anything walks a chain. A chain walk demands contiguity from
389        // sequence one, and the acknowledgements under the coverage are exactly
390        // the rows the advance retired — so without this a device pays one
391        // provider read per acknowledgement it has ever made, every time it
392        // verifies a snapshot. The owner signed those chains into the snapshot
393        // this baseline stands on: covered positions resolve to the coverage,
394        // here as everywhere else.
395        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            // The one acknowledgement this commit activated. Across the retained
440            // rows that is every acknowledgement the device has made, which is
441            // what the chain walk used to re-read from the provider per commit —
442            // the rows hold it between them rather than each holding all of it.
443            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
590/// Every commit `tips` causally depends on, down to the installed baseline.
591///
592/// A covered reference is a member of the closure but not a step in the walk:
593/// the baseline restates what stands there, and the commits behind it are
594/// retired. Walking past one would demand history this device deliberately
595/// dropped.
596fn 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    /// Where a walk down this history stops, and what it reads there.
623    ///
624    /// Below an installed baseline there is nothing to walk to: the commits are
625    /// retired and their rows are restated by one signed image. The two ends of
626    /// a history are the same shape — `genesis` is the state before the first
627    /// commit, `baseline` is the state at the positions the image covers.
628    pub(crate) baseline: coven_database::InstalledReplayBaseline,
629    /// The commits this device still holds a retained materialization for, and
630    /// the registrations that row proved active at each of them.
631    ///
632    /// A baseline image keeps a closure of rows at or under its own coverage —
633    /// historical Circle epoch access, author-exclusion recovery — because
634    /// those paths read the rows rather than a replay. Being covered therefore
635    /// does not mean the commit is gone; holding no row for it does.
636    pub(crate) retained: BTreeMap<StoreBatchCommitRef, Vec<ActivatedStoreDeviceRegistration>>,
637    pub(crate) commits: BTreeMap<StoreBatchCommitRef, VerifiedMergeHistoryCommit>,
638}
639
640impl VerifiedMergeHistory {
641    /// Whether the installed baseline stands in for `reference` outright: it
642    /// restates the position and this device kept no row behind it. Nothing
643    /// walks past such a reference, because there is nothing left to walk to.
644    pub(crate) fn superseded(&self, reference: &StoreBatchCommitRef) -> bool {
645        self.baseline.covers(reference) && !self.retained.contains_key(reference)
646    }
647
648    /// The registrations a retained row already proved active at its commit.
649    ///
650    /// Re-deriving them reads whatever the commit's body names from the
651    /// provider — a reclaim authorization, its evidence, its receipt — on every
652    /// pull, for a commit this device verified once and wrote a row for. The
653    /// row was written by the transaction that verified and applied the commit,
654    /// and opening it re-parses and re-checks the commit against its activated
655    /// registration, so it is the answer rather than a cache of one.
656    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    /// The device state standing after `reference`, from the verified graph
666    /// when it holds the commit and from the baseline when the commit is one it
667    /// superseded.
668    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    /// Resolve an exact cut from retained commit states or from the whole
687    /// installed checkpoint and a verified suffix. The checkpoint's aggregate
688    /// state is never attributed to an individual historical commit.
689    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    /// Which registration founded this Store. Established at construction from
757    /// the founder this verifier validated against the root; the registration it
758    /// names is held by `commit_verifier`, not again here.
759    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}