Skip to main content

coven_database/store/
materialization_models.rs

1use crate::*;
2use coven_protocol::audience_package::AudiencePackage;
3use coven_protocol::circle_activation::VerifiedCircleActivations;
4use coven_protocol::membership::{
5    AuthorHead, MembershipEntry, MembershipEntryRef, MembershipHeadRef,
6    StoreMembershipConflictResolutionRef,
7};
8use coven_protocol::objects::{ExactObjectRef, PreparedExactObject};
9use coven_protocol::remote_object::{remote_object_id, SharedLiveSetObjectDomain};
10use coven_protocol::store_commit::{
11    ActivatedStoreDeviceRegistration, CirclePackageRef, ObjectHash, RetainedStoreDeviceOperations,
12    RetainedStoreDeviceRegistrationActivations, StoreBatchCommit, StoreBatchCommitRef,
13    StoreDeviceHead, StorePackageRef, VerifiedStoreDeviceOperations,
14};
15use coven_protocol::store_commit::{
16    RetainedMergeCommitEvidence, RetainedReplaySnapshotAuthority, StoreRootRef,
17    VerifiedStoreBatchCommit,
18};
19
20#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
21#[serde(deny_unknown_fields)]
22pub struct RetainedMergeMaterializationInput {
23    pub commit: PreparedExactObject,
24    pub activation_head: PreparedExactObject,
25    pub history_evidence: RetainedMergeCommitEvidence,
26    pub membership_objects: Option<VerifiedMergeMembershipObjects>,
27    pub packages: Vec<RetainedAudiencePackage>,
28    pub activation: RetainedCommitActivationInput,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
32#[serde(deny_unknown_fields)]
33pub struct VerifiedMergeMembershipObjects {
34    entry: MembershipEntryRef,
35    head: MembershipHeadRef,
36    resolution: Option<StoreMembershipConflictResolutionRef>,
37}
38
39impl VerifiedMergeMembershipObjects {
40    pub fn entry(&self) -> &MembershipEntryRef {
41        &self.entry
42    }
43
44    pub fn head(&self) -> &MembershipHeadRef {
45        &self.head
46    }
47
48    pub fn resolution(&self) -> Option<&StoreMembershipConflictResolutionRef> {
49        self.resolution.as_ref()
50    }
51
52    pub fn verify(
53        commit: &StoreBatchCommit,
54        commit_ref: &StoreBatchCommitRef,
55        entry: &MembershipEntry,
56        head_value: &AuthorHead,
57        head: MembershipHeadRef,
58    ) -> Result<Self, DbError> {
59        let Some(coven_protocol::store_commit::StoreControl { transition }) = commit.control()
60        else {
61            return Err(DbError::Message(
62                "Merge membership object closure accompanies another Store control".to_string(),
63            ));
64        };
65        if transition.body.entry.coord != entry.coord()
66            || !transition.matches_head(head_value, &head)
67            || !matches!(
68                &head_value.activation,
69                coven_protocol::membership::MembershipHeadActivation::StoreCommit { commit }
70                    if commit == commit_ref
71            )
72        {
73            return Err(DbError::Message(
74                "Merge membership object closure differs from its exact Store transition"
75                    .to_string(),
76            ));
77        }
78        let resolution = match &entry.change {
79            coven_protocol::membership::MembershipChange::ResolutionActivation { resolution } => {
80                Some(resolution.clone())
81            }
82            _ => None,
83        };
84        Ok(Self {
85            entry: transition.body.entry.clone(),
86            head,
87            resolution,
88        })
89    }
90
91    pub fn object_ids(&self) -> impl Iterator<Item = ObjectHash> + '_ {
92        [
93            Some(remote_object_id(&self.entry.object)),
94            Some(remote_object_id(&self.head.object)),
95            self.resolution
96                .as_ref()
97                .map(|resolution| remote_object_id(&resolution.object)),
98        ]
99        .into_iter()
100        .flatten()
101    }
102}
103
104#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
105#[serde(deny_unknown_fields)]
106pub struct MergeRetractionCleanupInput {
107    pub commit: PreparedExactObject,
108    pub activation_head: PreparedExactObject,
109}
110
111#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
112#[serde(rename_all = "snake_case", deny_unknown_fields)]
113pub enum RetainedAudiencePackage {
114    Store {
115        reference: StorePackageRef,
116        package: AudiencePackage,
117    },
118    Circle {
119        reference: CirclePackageRef,
120        package: AudiencePackage,
121    },
122}
123
124impl RetainedAudiencePackage {
125    pub fn verify(
126        commit: &StoreBatchCommit,
127        commit_ref: &StoreBatchCommitRef,
128        package: AudiencePackage,
129    ) -> Result<Self, DbError> {
130        if package.store_root_hash() != commit.store_root_hash
131            || package.write_id() != &commit.write_id
132            || package.commit_coord() != &commit_ref.coord
133            || package.candidate_family() != commit.candidate_family()
134        {
135            return Err(DbError::Message(
136                "retained audience package differs from its exact Store commit".to_string(),
137            ));
138        }
139        package
140            .validate_blob_uploader(&commit.author_registration)
141            .map_err(DbError::from)?;
142        match package.audience() {
143            coven_protocol::audience_package::PackageAudience::Store => {
144                let reference = commit.store_package().ok_or_else(|| {
145                    DbError::Message(
146                        "retained Store package is absent from its exact commit".to_string(),
147                    )
148                })?;
149                if package.schema_version() != reference.schema_version {
150                    return Err(DbError::Message(
151                        "retained Store package schema version differs from its exact commit"
152                            .to_string(),
153                    ));
154                }
155                commit
156                    .verify_store_package(&package.to_bytes())
157                    .map_err(DbError::from)?;
158                Ok(Self::Store {
159                    reference: reference.clone(),
160                    package,
161                })
162            }
163            coven_protocol::audience_package::PackageAudience::Circle {
164                circle_id,
165                control,
166                key_fingerprint,
167            } => {
168                let reference = commit
169                    .circle_packages()
170                    .iter()
171                    .find(|reference| reference.circle_id == *circle_id)
172                    .ok_or_else(|| {
173                        DbError::Message(format!(
174                            "retained Circle package {circle_id} is absent from its exact commit"
175                        ))
176                    })?;
177                if reference.control != *control
178                    || reference.key_fingerprint != *key_fingerprint
179                    || package.schema_version() != reference.package.schema_version
180                {
181                    return Err(DbError::Message(format!(
182                        "retained Circle package {circle_id} differs from its exact commit"
183                    )));
184                }
185                commit
186                    .verify_circle_package(*circle_id, &package.to_bytes())
187                    .map_err(DbError::from)?;
188                Ok(Self::Circle {
189                    reference: reference.clone(),
190                    package,
191                })
192            }
193        }
194    }
195
196    pub fn package(&self) -> &AudiencePackage {
197        match self {
198            Self::Store { package, .. } | Self::Circle { package, .. } => package,
199        }
200    }
201
202    pub fn domain(&self) -> SharedLiveSetObjectDomain {
203        match self {
204            Self::Store { reference, .. } => SharedLiveSetObjectDomain::StorePackage {
205                reference: reference.clone(),
206            },
207            Self::Circle { reference, .. } => SharedLiveSetObjectDomain::CirclePackage {
208                reference: reference.clone(),
209            },
210        }
211    }
212
213    pub fn object(&self) -> &ExactObjectRef {
214        match self {
215            Self::Store { reference, .. } => &reference.object,
216            Self::Circle { reference, .. } => &reference.package.object,
217        }
218    }
219}
220
221#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
222#[serde(deny_unknown_fields)]
223pub struct RetainedCommitActivationInput {
224    pub registrations: RetainedStoreDeviceRegistrationActivations,
225    pub device_operations: RetainedStoreDeviceOperations,
226    pub circle_activations: Vec<u8>,
227    #[serde(skip_serializing_if = "Option::is_none")]
228    pub package_application: Option<RetainedPackageApplication>,
229}
230
231#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
232#[serde(rename_all = "snake_case", deny_unknown_fields)]
233pub enum RetainedPackageApplication {
234    LocallyAuthored,
235    Received { receiver_wall_ms: u64 },
236}
237
238pub struct RetainedMergeMaterializationKey {
239    pub commit_ref: String,
240    pub input_hash: ObjectHash,
241}
242
243pub struct VerifiedMergeMaterialization<'a> {
244    root: &'a coven_protocol::store_commit::StoreRootRef,
245    verified_commit: &'a coven_protocol::store_commit::VerifiedStoreBatchCommit,
246    device_operations: &'a VerifiedStoreDeviceOperations,
247    circle_activations: &'a VerifiedCircleActivations,
248    activation_head: &'a StoreDeviceHead,
249    activation_head_object: &'a ExactObjectRef,
250    history_evidence: &'a RetainedMergeCommitEvidence,
251    membership_objects: Option<&'a VerifiedMergeMembershipObjects>,
252    packages: &'a [AudiencePackage],
253    package_application: Option<RetainedPackageApplication>,
254    registrations: &'a [ActivatedStoreDeviceRegistration],
255}
256
257#[derive(Clone)]
258pub struct OwnedVerifiedMergeMaterialization {
259    root: coven_protocol::store_commit::StoreRootRef,
260    verified_commit: coven_protocol::store_commit::VerifiedStoreBatchCommit,
261    registrations: Vec<ActivatedStoreDeviceRegistration>,
262    device_operations: VerifiedStoreDeviceOperations,
263    circle_activations: VerifiedCircleActivations,
264    activation_head: StoreDeviceHead,
265    activation_head_object: ExactObjectRef,
266    history_evidence: RetainedMergeCommitEvidence,
267    membership_objects: Option<VerifiedMergeMembershipObjects>,
268    packages: Vec<AudiencePackage>,
269    package_application: Option<RetainedPackageApplication>,
270    input_hash: ObjectHash,
271}
272
273/// The replay baseline a device stands on, as the history verifier needs it.
274///
275/// A device that installed or advanced a baseline holds one signed image in
276/// place of the commits under `coverage`, and the rows those commits produced
277/// are gone. So a walk that reaches a covered position has nothing left to walk
278/// to — and nothing to prove, because the baseline already restates the result.
279/// It stops there instead, and reads the position's device state out of
280/// `covered_states`, which is what `retain_snapshot_device_states` keeps alive
281/// for exactly the positions commits above the coverage still name.
282///
283/// A device on a genesis baseline has an empty coverage, so every walk runs to
284/// genesis as it always did.
285#[derive(Debug, Clone)]
286pub struct InstalledReplayBaseline {
287    coverage: coven_protocol::store_commit::CommitFrontier,
288    covered_states: std::collections::BTreeMap<
289        StoreBatchCommitRef,
290        std::sync::Arc<coven_protocol::store_commit::ResolvedStoreDeviceState>,
291    >,
292    summary: Option<coven_protocol::store_commit::OpenedRetainedMergeHistorySummary>,
293    /// The snapshot this baseline was installed or advanced from, when it came
294    /// from one. Naming it is what lets a device answer "am I already standing
295    /// on that?" without reading the snapshot back to compare coverages.
296    snapshot: Option<coven_protocol::store_commit::StoreSnapshotRef>,
297}
298
299impl Default for InstalledReplayBaseline {
300    /// The genesis baseline: nothing is covered, so every walk runs to the
301    /// bottom of the history exactly as it does on a device that never
302    /// installed a snapshot.
303    fn default() -> Self {
304        Self {
305            coverage: coven_protocol::store_commit::CommitFrontier(
306                std::collections::BTreeMap::new(),
307            ),
308            covered_states: std::collections::BTreeMap::new(),
309            summary: None,
310            snapshot: None,
311        }
312    }
313}
314
315impl InstalledReplayBaseline {
316    pub fn new(
317        coverage: coven_protocol::store_commit::CommitFrontier,
318        covered_states: std::collections::BTreeMap<
319            StoreBatchCommitRef,
320            std::sync::Arc<coven_protocol::store_commit::ResolvedStoreDeviceState>,
321        >,
322        summary: Option<coven_protocol::store_commit::OpenedRetainedMergeHistorySummary>,
323        snapshot: Option<coven_protocol::store_commit::StoreSnapshotRef>,
324    ) -> Self {
325        Self {
326            coverage,
327            covered_states,
328            summary,
329            snapshot,
330        }
331    }
332
333    /// Whether this baseline was installed from `snapshot` itself.
334    pub fn stands_on(&self, snapshot: &coven_protocol::store_commit::StoreSnapshotRef) -> bool {
335        self.snapshot.as_ref() == Some(snapshot)
336    }
337
338    /// The signed history summary standing for everything under the coverage.
339    ///
340    /// A composition that walked to genesis produced this from the commits
341    /// themselves; one that stops at the baseline starts from it instead. Both
342    /// arrive at the same summary, which is what makes a summary a summary.
343    pub fn history_summary(
344        &self,
345    ) -> Option<&coven_protocol::store_commit::OpenedRetainedMergeHistorySummary> {
346        self.summary.as_ref()
347    }
348
349    pub fn coverage(&self) -> &coven_protocol::store_commit::CommitFrontier {
350        &self.coverage
351    }
352
353    /// Whether the baseline restates `reference`, so no walk need pass it.
354    pub fn covers(&self, reference: &StoreBatchCommitRef) -> bool {
355        self.coverage.covers_commit(reference)
356    }
357
358    /// The device state that stood at a covered position, or `None` when this
359    /// device never recorded one there — which is a commit naming a position
360    /// outside its own history, not a baseline that lost something.
361    pub fn covered_state(
362        &self,
363        reference: &StoreBatchCommitRef,
364    ) -> Option<&coven_protocol::store_commit::ResolvedStoreDeviceState> {
365        self.covered_states.get(reference).map(AsRef::as_ref)
366    }
367
368    pub fn covered_states(
369        &self,
370    ) -> impl Iterator<
371        Item = (
372            &StoreBatchCommitRef,
373            &coven_protocol::store_commit::ResolvedStoreDeviceState,
374        ),
375    > {
376        self.covered_states
377            .iter()
378            .map(|(reference, state)| (reference, state.as_ref()))
379    }
380}
381
382pub enum RetainedMergeHistoryCheckpoint {
383    Snapshot(coven_protocol::store_commit::OpenedRetainedMergeHistorySummary),
384    Commit(Box<OwnedVerifiedMergeMaterialization>),
385}
386
387impl OwnedVerifiedMergeMaterialization {
388    pub fn verify(
389        root: coven_protocol::store_commit::StoreRootRef,
390        verified_commit: coven_protocol::store_commit::VerifiedStoreBatchCommit,
391        registrations: Vec<ActivatedStoreDeviceRegistration>,
392        device_operations: VerifiedStoreDeviceOperations,
393        circle_activations: VerifiedCircleActivations,
394        activation_head: StoreDeviceHead,
395        activation_head_object: ExactObjectRef,
396        history_evidence: RetainedMergeCommitEvidence,
397        membership_objects: Option<VerifiedMergeMembershipObjects>,
398        packages: Vec<AudiencePackage>,
399        package_application: Option<RetainedPackageApplication>,
400        input_hash: ObjectHash,
401    ) -> Result<Self, DbError> {
402        VerifiedMergeMaterialization::verify(
403            &root,
404            &verified_commit,
405            &registrations,
406            &device_operations,
407            &circle_activations,
408            &activation_head,
409            &activation_head_object,
410            &history_evidence,
411            membership_objects.as_ref(),
412            &packages,
413            package_application,
414        )?;
415        Ok(Self {
416            root,
417            verified_commit,
418            registrations,
419            device_operations,
420            circle_activations,
421            activation_head,
422            activation_head_object,
423            history_evidence,
424            membership_objects,
425            packages,
426            package_application,
427            input_hash,
428        })
429    }
430
431    pub fn input_hash(&self) -> ObjectHash {
432        self.input_hash
433    }
434
435    pub fn root(&self) -> &coven_protocol::store_commit::StoreRootRef {
436        &self.root
437    }
438
439    pub fn commit(&self) -> &StoreBatchCommit {
440        self.verified_commit.value()
441    }
442
443    pub fn commit_ref(&self) -> &StoreBatchCommitRef {
444        self.verified_commit.reference()
445    }
446
447    pub fn verified_commit(&self) -> &coven_protocol::store_commit::VerifiedStoreBatchCommit {
448        &self.verified_commit
449    }
450
451    pub fn registrations(&self) -> &[ActivatedStoreDeviceRegistration] {
452        &self.registrations
453    }
454
455    pub fn device_operations(&self) -> &VerifiedStoreDeviceOperations {
456        &self.device_operations
457    }
458
459    pub fn circle_activations(&self) -> &VerifiedCircleActivations {
460        &self.circle_activations
461    }
462
463    pub fn circle_activation(
464        &self,
465        circle_id: coven_protocol::circle::CircleId,
466        control: &coven_protocol::circle::CircleControlCoord,
467    ) -> Result<coven_protocol::circle_activation::VerifiedCircleReference, DbError> {
468        let mut matches = self
469            .circle_activations
470            .circles()
471            .iter()
472            .filter(|activation| {
473                activation.circle_id == circle_id && activation.control.coord == *control
474            });
475        let activation = matches.next().cloned().ok_or_else(|| {
476            DbError::Message(format!(
477                "Circle {circle_id} retained activation omits control {control:?}"
478            ))
479        })?;
480        if matches.next().is_some() {
481            return Err(DbError::Message(format!(
482                "Circle {circle_id} retained activation duplicates control {control:?}"
483            )));
484        }
485        Ok(activation)
486    }
487
488    pub fn activation_head(&self) -> &StoreDeviceHead {
489        &self.activation_head
490    }
491
492    pub fn activation_head_object(&self) -> &ExactObjectRef {
493        &self.activation_head_object
494    }
495
496    pub fn history_evidence(&self) -> &RetainedMergeCommitEvidence {
497        &self.history_evidence
498    }
499
500    pub fn membership_objects(&self) -> Option<&VerifiedMergeMembershipObjects> {
501        self.membership_objects.as_ref()
502    }
503
504    pub fn packages(&self) -> &[AudiencePackage] {
505        &self.packages
506    }
507
508    pub fn package_application(&self) -> Option<RetainedPackageApplication> {
509        self.package_application
510    }
511}
512
513impl<'a> VerifiedMergeMaterialization<'a> {
514    pub fn root(&self) -> &coven_protocol::store_commit::StoreRootRef {
515        self.root
516    }
517
518    pub fn commit(&self) -> &StoreBatchCommit {
519        self.verified_commit.value()
520    }
521
522    pub fn commit_ref(&self) -> &StoreBatchCommitRef {
523        self.verified_commit.reference()
524    }
525
526    pub fn verified_commit(&self) -> &coven_protocol::store_commit::VerifiedStoreBatchCommit {
527        self.verified_commit
528    }
529
530    pub fn registrations(&self) -> &[ActivatedStoreDeviceRegistration] {
531        self.registrations
532    }
533
534    pub fn device_operations(&self) -> &VerifiedStoreDeviceOperations {
535        self.device_operations
536    }
537
538    pub fn circle_activations(&self) -> &VerifiedCircleActivations {
539        self.circle_activations
540    }
541
542    pub fn activation_head(&self) -> &StoreDeviceHead {
543        self.activation_head
544    }
545
546    pub fn activation_head_object(&self) -> &ExactObjectRef {
547        self.activation_head_object
548    }
549
550    pub fn history_evidence(&self) -> &RetainedMergeCommitEvidence {
551        self.history_evidence
552    }
553
554    pub fn membership_objects(&self) -> Option<&VerifiedMergeMembershipObjects> {
555        self.membership_objects
556    }
557
558    pub fn packages(&self) -> &[AudiencePackage] {
559        self.packages
560    }
561
562    pub fn package_application(&self) -> Option<RetainedPackageApplication> {
563        self.package_application
564    }
565
566    pub fn verify(
567        root: &'a coven_protocol::store_commit::StoreRootRef,
568        verified_commit: &'a coven_protocol::store_commit::VerifiedStoreBatchCommit,
569        registrations: &'a [ActivatedStoreDeviceRegistration],
570        device_operations: &'a VerifiedStoreDeviceOperations,
571        circle_activations: &'a VerifiedCircleActivations,
572        activation_head: &'a StoreDeviceHead,
573        activation_head_object: &'a ExactObjectRef,
574        history_evidence: &'a RetainedMergeCommitEvidence,
575        membership_objects: Option<&'a VerifiedMergeMembershipObjects>,
576        packages: &'a [AudiencePackage],
577        package_application: Option<RetainedPackageApplication>,
578    ) -> Result<Self, DbError> {
579        let commit = verified_commit.value();
580        let commit_ref = verified_commit.reference();
581        history_evidence
582            .validate_for(commit_ref, commit)
583            .map_err(DbError::from)?;
584        if verified_commit.store_root_hash() != root.store_root_hash
585            || commit.store_root_hash != root.store_root_hash
586            || activation_head.author_registration != commit.author_registration
587            || activation_head.commit != *commit_ref
588            || circle_activations.stream_activations().activating_commit() != commit_ref
589            || circle_activations.stream_activations().as_slice() != commit.stream_activations()
590            || circle_activations.circles().len() != commit.circle_controls().len()
591            || circle_activations
592                .circles()
593                .iter()
594                .zip(commit.circle_controls())
595                .any(|(activation, reference)| activation.reference != *reference)
596            || packages.is_empty() != package_application.is_none()
597            || commit.control().is_some() != membership_objects.is_some()
598        {
599            return Err(DbError::Message(
600                "verified Merge materialization differs from its exact Store commit".to_string(),
601            ));
602        }
603        let verified_head = StoreDeviceHead::parse_at(
604            &activation_head.to_bytes(),
605            root.store_root_hash,
606            verified_commit.author(),
607            commit_ref,
608        )
609        .map_err(|error| DbError::context("verify Merge materialization head", error))?;
610        if &verified_head != activation_head {
611            return Err(DbError::Message(
612                "Merge materialization head differs from its verified bytes".to_string(),
613            ));
614        }
615        activation_head_object
616            .verify(&activation_head.to_bytes())
617            .map_err(|error| DbError::context("verify Merge materialization head object", error))?;
618        let expected_head_key = format!(
619            "{}.json",
620            coven_protocol::store_commit::head_slot_prefix(
621                &activation_head.author_registration.device_id.to_string(),
622                commit_ref.coord.sequence(),
623            )
624        );
625        if activation_head_object.slot().logical_key() != expected_head_key {
626            return Err(DbError::Message(
627                "Merge materialization head object occupies another protocol slot".to_string(),
628            ));
629        }
630        RetainedStoreDeviceRegistrationActivations::from_verified(root, commit, registrations)
631            .map_err(DbError::from)?;
632        Ok(Self {
633            root,
634            verified_commit,
635            device_operations,
636            circle_activations,
637            activation_head,
638            activation_head_object,
639            history_evidence,
640            membership_objects,
641            packages,
642            package_application,
643            registrations,
644        })
645    }
646}
647
648pub struct PreparedMergeMaterializationPackage {
649    pub package: AudiencePackage,
650    pub changeset: ValidatedChangeset<Vec<u8>>,
651}
652
653pub struct PreparedMergeMaterialization {
654    pub root: StoreRootRef,
655    pub verified_commit: VerifiedStoreBatchCommit,
656    pub activation_head: StoreDeviceHead,
657    pub activation_head_object: ExactObjectRef,
658    pub history_evidence: RetainedMergeCommitEvidence,
659    pub membership_objects: Option<VerifiedMergeMembershipObjects>,
660    pub membership_remote_objects: Vec<coven_protocol::remote_object::ClosedRemoteObject>,
661    pub registrations: Vec<ActivatedStoreDeviceRegistration>,
662    pub packages: Vec<PreparedMergeMaterializationPackage>,
663    pub device_operations: VerifiedStoreDeviceOperations,
664    pub circle_activations: VerifiedCircleActivations,
665    pub package_application: Option<crate::RetainedPackageApplication>,
666}
667
668pub struct MembershipAuthorityBytes {
669    canonical: Vec<u8>,
670    stored: Vec<u8>,
671}
672
673impl MembershipAuthorityBytes {
674    pub fn new(canonical: Vec<u8>, stored: Vec<u8>) -> Self {
675        Self { canonical, stored }
676    }
677}
678
679pub fn activated_merge_membership_remote_objects(
680    family: coven_protocol::store_commit::CandidateFamilyId,
681    objects: &VerifiedMergeMembershipObjects,
682    entry_bytes: MembershipAuthorityBytes,
683    head_bytes: MembershipAuthorityBytes,
684    resolution_bytes: Option<MembershipAuthorityBytes>,
685    commit_ref: &StoreBatchCommitRef,
686) -> Result<
687    Vec<coven_protocol::remote_object::ClosedRemoteObject>,
688    coven_protocol::remote_object::RemoteObjectRecordError,
689> {
690    let mut remotes = vec![
691        coven_protocol::remote_object::RemoteObjectRecord::candidate_exclusive_merge_membership_entry(
692            family,
693            objects.entry().clone(),
694            &entry_bytes.canonical,
695            &entry_bytes.stored,
696            commit_ref.clone(),
697        )?
698        .map_record(|record| record.into_observed_activated(commit_ref))?,
699        coven_protocol::remote_object::RemoteObjectRecord::candidate_exclusive_merge_membership_head(
700            family,
701            objects.head().clone(),
702            &head_bytes.canonical,
703            &head_bytes.stored,
704            commit_ref.clone(),
705        )?
706        .map_record(|record| record.into_observed_activated(commit_ref))?,
707    ];
708    if let Some(resolution) = objects.resolution() {
709        let bytes = resolution_bytes.ok_or(
710            coven_protocol::remote_object::RemoteObjectRecordError::StoredReferenceMismatch,
711        )?;
712        remotes.push(
713            coven_protocol::remote_object::RemoteObjectRecord::candidate_activated_store_membership_resolution(
714                resolution.clone(),
715                &bytes.canonical,
716                &bytes.stored,
717                commit_ref.clone(),
718            )?
719            .map_record(|record| record.into_observed_activated(commit_ref))?,
720        );
721    } else if resolution_bytes.is_some() {
722        return Err(
723            coven_protocol::remote_object::RemoteObjectRecordError::StoredReferenceMismatch,
724        );
725    }
726    Ok(remotes)
727}
728
729/// One snapshot verified as installable: its signed metadata, the cut it
730/// covers, and the devices and registrations active there. A device installs
731/// its baseline from this and verifies everything that arrives afterwards
732/// against it.
733#[derive(Debug)]
734pub struct VerifiedStoreSnapshotAuthority {
735    authority: RetainedReplaySnapshotAuthority,
736}
737
738impl VerifiedStoreSnapshotAuthority {
739    pub fn from_authority(
740        authority: RetainedReplaySnapshotAuthority,
741    ) -> Result<Self, crate::DbError> {
742        authority.validate()?;
743        Ok(Self { authority })
744    }
745
746    pub fn into_authority(self) -> RetainedReplaySnapshotAuthority {
747        self.authority
748    }
749}
750
751/// One snapshot verified as acknowledged by every device active at its cut.
752/// Reclaim deletes history behind a snapshot only against this, and carries the
753/// acknowledgements it names as the claim's evidence.
754#[derive(Debug)]
755pub struct VerifiedAcknowledgedStoreSnapshot {
756    acknowledged: coven_protocol::store_commit::AcknowledgedStoreSnapshot,
757}
758
759impl VerifiedAcknowledgedStoreSnapshot {
760    pub fn from_acknowledged(
761        acknowledged: coven_protocol::store_commit::AcknowledgedStoreSnapshot,
762    ) -> Result<Self, crate::DbError> {
763        acknowledged.validate()?;
764        Ok(Self { acknowledged })
765    }
766
767    /// The authority a device adopts when it advances its own replay baseline
768    /// over this snapshot.
769    ///
770    /// Every device required by cloud reclaim acknowledged this exact snapshot.
771    pub fn authority(&self) -> &coven_protocol::store_commit::RetainedReplaySnapshotAuthority {
772        &self.acknowledged.authority
773    }
774
775    pub fn into_acknowledged(self) -> coven_protocol::store_commit::AcknowledgedStoreSnapshot {
776        self.acknowledged
777    }
778
779    pub fn acknowledgement_refs(
780        &self,
781    ) -> Result<Vec<coven_protocol::store_commit::StoreAckRef>, crate::DbError> {
782        self.acknowledged
783            .acknowledgement_refs()
784            .map_err(crate::DbError::from)
785    }
786}
787
788/// A snapshot verified against every writer active in current Store authority.
789/// Local replay retirement requires this stronger proof; cloud reclaim keeps
790/// using [`VerifiedAcknowledgedStoreSnapshot`].
791#[derive(Debug)]
792pub struct VerifiedReplayBaselineRetirementProof {
793    proof: coven_protocol::store_commit::ReplayBaselineRetirementProof,
794}
795
796impl VerifiedReplayBaselineRetirementProof {
797    pub fn from_proof(
798        proof: coven_protocol::store_commit::ReplayBaselineRetirementProof,
799        accepted_membership: &coven_protocol::membership::MembershipChain,
800    ) -> Result<Self, crate::DbError> {
801        proof.validate(accepted_membership)?;
802        Ok(Self { proof })
803    }
804
805    pub(crate) fn into_proof(self) -> coven_protocol::store_commit::ReplayBaselineRetirementProof {
806        self.proof
807    }
808}
809
810pub struct DeviceJoinBootstrapCommit {
811    pub reference: StoreBatchCommitRef,
812    pub commit: VerifiedStoreBatchCommit,
813    pub registrations: Vec<ActivatedStoreDeviceRegistration>,
814    pub device_operations: VerifiedStoreDeviceOperations,
815    pub activation: DeviceJoinBootstrapActivation,
816}
817
818pub struct DeviceJoinBootstrapActivation {
819    pub head: StoreDeviceHead,
820    pub object: ExactObjectRef,
821    pub history_evidence: RetainedMergeCommitEvidence,
822}
823
824pub struct DeviceJoinBootstrapPlan {
825    pub founder_reference: StoreDeviceRegistrationRef,
826    pub founder: StoreDeviceRegistration,
827    pub founder_bytes: Vec<u8>,
828    pub genesis: ResolvedStoreDeviceState,
829    pub membership: InitialStoreMembershipAuthority,
830    pub commits: Vec<DeviceJoinBootstrapCommit>,
831}
832
833/// Everything one bootstrap commit needs to materialize its rows, for a commit
834/// the joining database does not already cover through an installed snapshot.
835///
836/// Installation runs inside a single database transaction and cannot read the
837/// cloud, so the joining device resolves this beforehand — reading, decrypting
838/// and verifying each package exactly the way an ordinary pull does.
839pub struct DeviceJoinBootstrapRowData {
840    pub circle_activations: VerifiedCircleActivations,
841    pub membership_objects: Option<VerifiedMergeMembershipObjects>,
842    pub membership_remote_objects: Vec<coven_protocol::remote_object::ClosedRemoteObject>,
843    pub packages: Vec<PreparedMergeMaterializationPackage>,
844}
845
846/// A bootstrap plan together with the row data for every commit in it the
847/// joining database does not already materialize. Installation only accepts
848/// this shape, so a bootstrap can never advance its position over commits
849/// whose rows were never resolved.
850pub struct ResolvedDeviceJoinBootstrap {
851    pub plan: DeviceJoinBootstrapPlan,
852    pub row_data: std::collections::BTreeMap<StoreBatchCommitRef, DeviceJoinBootstrapRowData>,
853    pub local_store_membership: coven_protocol::membership::LocalStoreMembership,
854    pub routing_key: Option<coven_protocol::circle::RowRoutingKey>,
855    pub receiver_wall_ms: u64,
856}
857
858impl DeviceJoinBootstrapPlan {
859    pub fn verified_commit(
860        &self,
861        reference: &StoreBatchCommitRef,
862    ) -> Option<&VerifiedStoreBatchCommit> {
863        self.commits
864            .iter()
865            .find(|commit| &commit.reference == reference)
866            .map(|commit| &commit.commit)
867    }
868
869    pub fn into_closure(
870        self,
871        root: &StoreRootRef,
872    ) -> Result<
873        coven_protocol::store_commit::device_join_exchange::DeviceJoinBootstrapClosure,
874        DbError,
875    > {
876        if self.founder.store_root != *root || self.founder.to_bytes() != self.founder_bytes {
877            return Err(DbError::Message(
878                "device join bootstrap founder differs from its canonical bytes".to_string(),
879            ));
880        }
881        let founder = coven_protocol::store_commit::ReferencedStoreDeviceRegistration::verified(
882            self.founder_reference,
883            self.founder,
884        )
885        .map_err(DbError::from)?;
886        let commits = self
887            .commits
888            .into_iter()
889            .map(|commit| {
890                let value = commit.commit.value();
891                if commit.commit.reference() != &commit.reference
892                    || commit.commit.store_root_hash() != root.store_root_hash
893                {
894                    return Err(DbError::Message(
895                        "device join bootstrap commit differs from its exact reference"
896                            .to_string(),
897                    ));
898                }
899                let author =
900                    coven_protocol::store_commit::ReferencedStoreDeviceRegistration::verified(
901                        value.author_registration.clone(),
902                        commit.commit.author().clone(),
903                    )
904                    .map_err(DbError::from)?;
905                let registrations = RetainedStoreDeviceRegistrationActivations::from_verified(
906                    root,
907                    value,
908                    &commit.registrations,
909                )
910                .map_err(DbError::from)?;
911                Ok(coven_protocol::store_commit::device_join_exchange::DeviceJoinBootstrapCommitClosure {
912                    reference: commit.reference,
913                    canonical_commit: value.to_bytes(),
914                    author,
915                    registrations,
916                    device_operations: commit.device_operations.to_retained(),
917                    activation_head: commit.activation.head,
918                    activation_object: commit.activation.object,
919                    history_evidence: commit.activation.history_evidence,
920                })
921            })
922            .collect::<Result<Vec<_>, DbError>>()?;
923        Ok(
924            coven_protocol::store_commit::device_join_exchange::DeviceJoinBootstrapClosure {
925                founder,
926                genesis: self.genesis,
927                membership: coven_protocol::membership::MembershipFloor(self.membership.head_refs),
928                commits,
929            },
930        )
931    }
932
933    pub fn from_closure(
934        root: &StoreRootRef,
935        closure: coven_protocol::store_commit::device_join_exchange::DeviceJoinBootstrapClosure,
936    ) -> Result<Self, DbError> {
937        if closure.founder.value().store_root != *root {
938            return Err(DbError::Message(
939                "device join bootstrap founder belongs to another Store root".to_string(),
940            ));
941        }
942        closure.membership.validate().map_err(|error| {
943            DbError::Message(format!("device join bootstrap membership: {error}"))
944        })?;
945        let founder_reference = closure.founder.reference().clone();
946        let founder = closure.founder.value().clone();
947        let founder_bytes = founder.to_bytes();
948        let commits = closure
949            .commits
950            .into_iter()
951            .map(|commit| {
952                let verified = VerifiedStoreBatchCommit::parse(
953                    &commit.canonical_commit,
954                    root.store_root_hash,
955                    &commit.reference,
956                    commit.author.value(),
957                )
958                .map_err(DbError::from)?;
959                if commit.author.reference() != &verified.value().author_registration {
960                    return Err(DbError::Message(
961                        "device join bootstrap author differs from its exact commit".to_string(),
962                    ));
963                }
964                let registrations = commit
965                    .registrations
966                    .verify_for(root, verified.value())
967                    .map_err(DbError::from)?;
968                let device_operations = commit
969                    .device_operations
970                    .verify_for(root, verified.value())
971                    .map_err(DbError::from)?;
972                Ok(DeviceJoinBootstrapCommit {
973                    reference: commit.reference,
974                    commit: verified,
975                    registrations,
976                    device_operations,
977                    activation: DeviceJoinBootstrapActivation {
978                        head: commit.activation_head,
979                        object: commit.activation_object,
980                        history_evidence: commit.history_evidence,
981                    },
982                })
983            })
984            .collect::<Result<Vec<_>, DbError>>()?;
985        Ok(Self {
986            founder_reference,
987            founder,
988            founder_bytes,
989            genesis: closure.genesis,
990            membership: InitialStoreMembershipAuthority {
991                head_refs: closure.membership.0,
992            },
993            commits,
994        })
995    }
996}