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#[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 snapshot: Option<coven_protocol::store_commit::StoreSnapshotRef>,
297}
298
299impl Default for InstalledReplayBaseline {
300 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 pub fn stands_on(&self, snapshot: &coven_protocol::store_commit::StoreSnapshotRef) -> bool {
335 self.snapshot.as_ref() == Some(snapshot)
336 }
337
338 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 pub fn covers(&self, reference: &StoreBatchCommitRef) -> bool {
355 self.coverage.covers_commit(reference)
356 }
357
358 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 ®istrations,
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#[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#[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 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#[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
833pub 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
846pub 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}