1use crate::*;
2use coven_protocol::audience_package::AudiencePackage;
3use coven_protocol::circle_activation::VerifiedCircleActivations;
4use coven_protocol::membership::{
5 AuthorHead, MembershipEntry, MembershipEntryRef, MembershipHeadRef,
6};
7use coven_protocol::objects::{ExactObjectRef, PreparedExactObject};
8use coven_protocol::remote_object::{remote_object_id, SharedLiveSetObjectDomain};
9use coven_protocol::store_commit::{
10 ActivatedStoreDeviceRegistration, CirclePackageRef, ObjectHash, RetainedStoreDeviceOperations,
11 RetainedStoreDeviceRegistrationActivations, StoreBatchCommit, StoreBatchCommitRef,
12 StorePackageRef, VerifiedStoreDeviceOperations,
13};
14use coven_protocol::store_commit::{
15 RetainedMergeCommitEvidence, RetainedReplaySnapshotAuthority, StoreRootRef,
16 VerifiedStoreBatchCommit,
17};
18
19#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
20#[serde(deny_unknown_fields)]
21pub struct RetainedMergeMaterializationInput {
22 pub commit: PreparedExactObject,
23 pub history_evidence: RetainedMergeCommitEvidence,
24 pub membership_objects: Option<VerifiedMergeMembershipObjects>,
25 pub packages: Vec<RetainedAudiencePackage>,
26 pub activation: RetainedCommitActivationInput,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
30#[serde(deny_unknown_fields)]
31pub struct VerifiedMergeMembershipObjects {
32 entry: MembershipEntryRef,
33 head: MembershipHeadRef,
34}
35
36impl VerifiedMergeMembershipObjects {
37 pub fn entry(&self) -> &MembershipEntryRef {
38 &self.entry
39 }
40
41 pub fn head(&self) -> &MembershipHeadRef {
42 &self.head
43 }
44
45 pub fn verify(
46 commit: &StoreBatchCommit,
47 commit_ref: &StoreBatchCommitRef,
48 entry: &MembershipEntry,
49 head_value: &AuthorHead,
50 head: MembershipHeadRef,
51 ) -> Result<Self, DbError> {
52 let Some(coven_protocol::store_commit::StoreControl { transition }) = commit.control()
53 else {
54 return Err(DbError::Message(
55 "Merge membership object closure accompanies another Store control".to_string(),
56 ));
57 };
58 if transition.body.entry.coord != entry.coord()
59 || !transition.matches_head(head_value, &head)
60 || !matches!(
61 &head_value.activation,
62 coven_protocol::membership::MembershipHeadActivation::StoreCommit { commit, .. }
63 if commit == commit_ref
64 )
65 {
66 return Err(DbError::Message(
67 "Merge membership object closure differs from its exact Store transition"
68 .to_string(),
69 ));
70 }
71 Ok(Self {
72 entry: transition.body.entry.clone(),
73 head,
74 })
75 }
76
77 pub fn object_ids(&self) -> impl Iterator<Item = ObjectHash> + '_ {
78 [
79 remote_object_id(&self.entry.object),
80 remote_object_id(&self.head.object),
81 ]
82 .into_iter()
83 }
84}
85
86#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
87#[serde(rename_all = "snake_case", deny_unknown_fields)]
88pub enum RetainedAudiencePackage {
89 Store {
90 reference: StorePackageRef,
91 package: AudiencePackage,
92 },
93 Circle {
94 reference: CirclePackageRef,
95 package: AudiencePackage,
96 },
97}
98
99impl RetainedAudiencePackage {
100 pub fn verify(
101 commit: &StoreBatchCommit,
102 commit_ref: &StoreBatchCommitRef,
103 package: AudiencePackage,
104 ) -> Result<Self, DbError> {
105 if package.store_root_hash() != commit.store_root_hash
106 || package.write_id() != &commit.write_id
107 || package.commit_coord() != &commit_ref.coord
108 || package.candidate_family() != commit.candidate_family()
109 {
110 return Err(DbError::Message(
111 "retained audience package differs from its exact Store commit".to_string(),
112 ));
113 }
114 package
115 .validate_blob_uploader(&commit.author_registration)
116 .map_err(DbError::from)?;
117 match package.audience() {
118 coven_protocol::audience_package::PackageAudience::Store => {
119 let reference = commit.store_package().ok_or_else(|| {
120 DbError::Message(
121 "retained Store package is absent from its exact commit".to_string(),
122 )
123 })?;
124 if package.schema_version() != reference.schema_version {
125 return Err(DbError::Message(
126 "retained Store package schema version differs from its exact commit"
127 .to_string(),
128 ));
129 }
130 commit
131 .verify_store_package(&package.to_bytes())
132 .map_err(DbError::from)?;
133 Ok(Self::Store {
134 reference: reference.clone(),
135 package,
136 })
137 }
138 coven_protocol::audience_package::PackageAudience::Circle {
139 circle_id,
140 control,
141 key_fingerprint,
142 } => {
143 let reference = commit
144 .circle_packages()
145 .iter()
146 .find(|reference| reference.circle_id == *circle_id)
147 .ok_or_else(|| {
148 DbError::Message(format!(
149 "retained Circle package {circle_id} is absent from its exact commit"
150 ))
151 })?;
152 if reference.control != *control
153 || reference.key_fingerprint != *key_fingerprint
154 || package.schema_version() != reference.package.schema_version
155 {
156 return Err(DbError::Message(format!(
157 "retained Circle package {circle_id} differs from its exact commit"
158 )));
159 }
160 commit
161 .verify_circle_package(*circle_id, &package.to_bytes())
162 .map_err(DbError::from)?;
163 Ok(Self::Circle {
164 reference: reference.clone(),
165 package,
166 })
167 }
168 }
169 }
170
171 pub fn package(&self) -> &AudiencePackage {
172 match self {
173 Self::Store { package, .. } | Self::Circle { package, .. } => package,
174 }
175 }
176
177 pub fn domain(&self) -> SharedLiveSetObjectDomain {
178 match self {
179 Self::Store { reference, .. } => SharedLiveSetObjectDomain::StorePackage {
180 reference: reference.clone(),
181 },
182 Self::Circle { reference, .. } => SharedLiveSetObjectDomain::CirclePackage {
183 reference: reference.clone(),
184 },
185 }
186 }
187
188 pub fn object(&self) -> &ExactObjectRef {
189 match self {
190 Self::Store { reference, .. } => &reference.object,
191 Self::Circle { reference, .. } => &reference.package.object,
192 }
193 }
194}
195
196#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
197#[serde(deny_unknown_fields)]
198pub struct RetainedCommitActivationInput {
199 pub registrations: RetainedStoreDeviceRegistrationActivations,
200 pub device_operations: RetainedStoreDeviceOperations,
201 pub circle_activations: Vec<u8>,
202 #[serde(skip_serializing_if = "Option::is_none")]
203 pub package_application: Option<RetainedPackageApplication>,
204}
205
206#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
207#[serde(rename_all = "snake_case", deny_unknown_fields)]
208pub enum RetainedPackageApplication {
209 LocallyAuthored,
210 Received { receiver_wall_ms: u64 },
211}
212
213pub struct RetainedMergeMaterializationKey {
214 pub commit_ref: String,
215 pub input_hash: ObjectHash,
216}
217
218pub struct VerifiedMergeMaterialization<'a> {
219 root: &'a coven_protocol::store_commit::StoreRootRef,
220 verified_commit: &'a coven_protocol::store_commit::VerifiedStoreBatchCommit,
221 device_operations: &'a VerifiedStoreDeviceOperations,
222 circle_activations: &'a VerifiedCircleActivations,
223 acceptance: &'a AcceptedStoreCommitEvidence,
224 history_evidence: &'a RetainedMergeCommitEvidence,
225 membership_objects: Option<&'a VerifiedMergeMembershipObjects>,
226 packages: &'a [AudiencePackage],
227 package_application: Option<RetainedPackageApplication>,
228 registrations: &'a [ActivatedStoreDeviceRegistration],
229}
230
231#[derive(Clone)]
232pub struct OwnedVerifiedMergeMaterialization {
233 root: coven_protocol::store_commit::StoreRootRef,
234 verified_commit: coven_protocol::store_commit::VerifiedStoreBatchCommit,
235 registrations: Vec<ActivatedStoreDeviceRegistration>,
236 device_operations: VerifiedStoreDeviceOperations,
237 circle_activations: VerifiedCircleActivations,
238 acceptance: AcceptedStoreCommitEvidence,
239 history_evidence: RetainedMergeCommitEvidence,
240 membership_objects: Option<VerifiedMergeMembershipObjects>,
241 packages: Vec<AudiencePackage>,
242 package_application: Option<RetainedPackageApplication>,
243 input_hash: ObjectHash,
244}
245
246#[derive(Debug, Clone, Default)]
256pub struct InstalledReplayBaseline {
257 covered_states: std::collections::BTreeMap<
258 StoreBatchCommitRef,
259 std::sync::Arc<coven_protocol::store_commit::ResolvedStoreDeviceState>,
260 >,
261 snapshot: Option<PublishedStoreSnapshot>,
266}
267
268impl InstalledReplayBaseline {
269 pub fn from_snapshot(
270 snapshot: PublishedStoreSnapshot,
271 covered_states: std::collections::BTreeMap<
272 StoreBatchCommitRef,
273 std::sync::Arc<coven_protocol::store_commit::ResolvedStoreDeviceState>,
274 >,
275 ) -> Self {
276 Self {
277 covered_states,
278 snapshot: Some(snapshot),
279 }
280 }
281
282 pub fn stands_on(&self, snapshot: &coven_protocol::store_commit::StoreSnapshotRef) -> bool {
284 self.snapshot
285 .as_ref()
286 .is_some_and(|installed| &installed.reference == snapshot)
287 }
288
289 pub fn snapshot(&self) -> Option<&PublishedStoreSnapshot> {
291 self.snapshot.as_ref()
292 }
293
294 pub fn coverage(&self) -> &coven_protocol::store_commit::CommitFrontier {
295 match &self.snapshot {
296 Some(snapshot) => &snapshot.meta.coverage,
297 None => {
298 static EMPTY: coven_protocol::store_commit::CommitFrontier =
299 coven_protocol::store_commit::CommitFrontier(std::collections::BTreeMap::new());
300 &EMPTY
301 }
302 }
303 }
304
305 pub fn covers(&self, reference: &StoreBatchCommitRef) -> bool {
307 self.coverage().covers_commit(reference)
308 }
309
310 pub fn covered_state(
314 &self,
315 reference: &StoreBatchCommitRef,
316 ) -> Option<&coven_protocol::store_commit::ResolvedStoreDeviceState> {
317 self.covered_states.get(reference).map(AsRef::as_ref)
318 }
319
320 pub fn covered_states(
321 &self,
322 ) -> impl Iterator<
323 Item = (
324 &StoreBatchCommitRef,
325 &coven_protocol::store_commit::ResolvedStoreDeviceState,
326 ),
327 > {
328 self.covered_states
329 .iter()
330 .map(|(reference, state)| (reference, state.as_ref()))
331 }
332}
333
334pub enum RetainedMergeHistoryCheckpoint {
335 Snapshot(coven_protocol::store_commit::OpenedRetainedMergeHistorySummary),
336 Commit(Box<OwnedVerifiedMergeMaterialization>),
337}
338
339impl OwnedVerifiedMergeMaterialization {
340 pub fn verify(
341 root: coven_protocol::store_commit::StoreRootRef,
342 verified_commit: coven_protocol::store_commit::VerifiedStoreBatchCommit,
343 registrations: Vec<ActivatedStoreDeviceRegistration>,
344 device_operations: VerifiedStoreDeviceOperations,
345 circle_activations: VerifiedCircleActivations,
346 acceptance: AcceptedStoreCommitEvidence,
347 history_evidence: RetainedMergeCommitEvidence,
348 membership_objects: Option<VerifiedMergeMembershipObjects>,
349 packages: Vec<AudiencePackage>,
350 package_application: Option<RetainedPackageApplication>,
351 input_hash: ObjectHash,
352 ) -> Result<Self, DbError> {
353 VerifiedMergeMaterialization::verify(
354 &root,
355 &verified_commit,
356 ®istrations,
357 &device_operations,
358 &circle_activations,
359 &acceptance,
360 &history_evidence,
361 membership_objects.as_ref(),
362 &packages,
363 package_application,
364 )?;
365 Ok(Self {
366 root,
367 verified_commit,
368 registrations,
369 device_operations,
370 circle_activations,
371 acceptance,
372 history_evidence,
373 membership_objects,
374 packages,
375 package_application,
376 input_hash,
377 })
378 }
379
380 pub fn input_hash(&self) -> ObjectHash {
381 self.input_hash
382 }
383
384 pub fn root(&self) -> &coven_protocol::store_commit::StoreRootRef {
385 &self.root
386 }
387
388 pub fn commit(&self) -> &StoreBatchCommit {
389 self.verified_commit.value()
390 }
391
392 pub fn commit_ref(&self) -> &StoreBatchCommitRef {
393 self.verified_commit.reference()
394 }
395
396 pub fn verified_commit(&self) -> &coven_protocol::store_commit::VerifiedStoreBatchCommit {
397 &self.verified_commit
398 }
399
400 pub fn registrations(&self) -> &[ActivatedStoreDeviceRegistration] {
401 &self.registrations
402 }
403
404 pub fn device_operations(&self) -> &VerifiedStoreDeviceOperations {
405 &self.device_operations
406 }
407
408 pub fn circle_activations(&self) -> &VerifiedCircleActivations {
409 &self.circle_activations
410 }
411
412 pub fn circle_activation(
413 &self,
414 circle_id: coven_protocol::circle::CircleId,
415 control: &coven_protocol::circle::CircleControlCoord,
416 ) -> Result<coven_protocol::circle_activation::VerifiedCircleReference, DbError> {
417 let mut matches = self
418 .circle_activations
419 .circles()
420 .iter()
421 .filter(|activation| {
422 activation.circle_id == circle_id && activation.control.coord == *control
423 });
424 let activation = matches.next().cloned().ok_or_else(|| {
425 DbError::Message(format!(
426 "Circle {circle_id} retained activation omits control {control:?}"
427 ))
428 })?;
429 if matches.next().is_some() {
430 return Err(DbError::Message(format!(
431 "Circle {circle_id} retained activation duplicates control {control:?}"
432 )));
433 }
434 Ok(activation)
435 }
436
437 pub fn acceptance(&self) -> &AcceptedStoreCommitEvidence {
438 &self.acceptance
439 }
440
441 pub fn history_evidence(&self) -> &RetainedMergeCommitEvidence {
442 &self.history_evidence
443 }
444
445 pub fn membership_objects(&self) -> Option<&VerifiedMergeMembershipObjects> {
446 self.membership_objects.as_ref()
447 }
448
449 pub(crate) fn membership_remote_objects(
450 &self,
451 ) -> Result<Vec<coven_protocol::remote_object::ClosedRemoteObject>, DbError> {
452 let Some(objects) = self.membership_objects() else {
453 return Ok(Vec::new());
454 };
455 let proof = self
456 .history_evidence
457 .membership_proof
458 .as_ref()
459 .expect("verified membership objects have their exact retained proof");
460 let entry = serde_json::to_vec(&proof.entry_value)?;
463 let head = serde_json::to_vec(&proof.head_value)?;
464 activated_merge_membership_remote_objects(
465 self.commit().candidate_family(),
466 objects,
467 MembershipAuthorityBytes::new(entry.clone(), entry),
468 MembershipAuthorityBytes::new(head.clone(), head),
469 self.commit_ref(),
470 )
471 .map_err(DbError::from)
472 }
473
474 pub fn packages(&self) -> &[AudiencePackage] {
475 &self.packages
476 }
477
478 pub fn package_application(&self) -> Option<RetainedPackageApplication> {
479 self.package_application
480 }
481}
482
483impl<'a> VerifiedMergeMaterialization<'a> {
484 pub fn root(&self) -> &coven_protocol::store_commit::StoreRootRef {
485 self.root
486 }
487
488 pub fn commit(&self) -> &StoreBatchCommit {
489 self.verified_commit.value()
490 }
491
492 pub fn commit_ref(&self) -> &StoreBatchCommitRef {
493 self.verified_commit.reference()
494 }
495
496 pub fn verified_commit(&self) -> &coven_protocol::store_commit::VerifiedStoreBatchCommit {
497 self.verified_commit
498 }
499
500 pub fn registrations(&self) -> &[ActivatedStoreDeviceRegistration] {
501 self.registrations
502 }
503
504 pub fn device_operations(&self) -> &VerifiedStoreDeviceOperations {
505 self.device_operations
506 }
507
508 pub fn circle_activations(&self) -> &VerifiedCircleActivations {
509 self.circle_activations
510 }
511
512 pub fn acceptance(&self) -> &AcceptedStoreCommitEvidence {
513 self.acceptance
514 }
515
516 pub fn history_evidence(&self) -> &RetainedMergeCommitEvidence {
517 self.history_evidence
518 }
519
520 pub fn membership_objects(&self) -> Option<&VerifiedMergeMembershipObjects> {
521 self.membership_objects
522 }
523
524 pub fn packages(&self) -> &[AudiencePackage] {
525 self.packages
526 }
527
528 pub fn package_application(&self) -> Option<RetainedPackageApplication> {
529 self.package_application
530 }
531
532 pub fn verify(
533 root: &'a coven_protocol::store_commit::StoreRootRef,
534 verified_commit: &'a coven_protocol::store_commit::VerifiedStoreBatchCommit,
535 registrations: &'a [ActivatedStoreDeviceRegistration],
536 device_operations: &'a VerifiedStoreDeviceOperations,
537 circle_activations: &'a VerifiedCircleActivations,
538 acceptance: &'a AcceptedStoreCommitEvidence,
539 history_evidence: &'a RetainedMergeCommitEvidence,
540 membership_objects: Option<&'a VerifiedMergeMembershipObjects>,
541 packages: &'a [AudiencePackage],
542 package_application: Option<RetainedPackageApplication>,
543 ) -> Result<Self, DbError> {
544 let commit = verified_commit.value();
545 let commit_ref = verified_commit.reference();
546 history_evidence
547 .validate_for(commit_ref, commit)
548 .map_err(DbError::from)?;
549 if verified_commit.store_root_hash() != root.store_root_hash
550 || acceptance.commit_ref() != commit_ref
551 || circle_activations.stream_activations().activating_commit() != commit_ref
552 || circle_activations.stream_activations().as_slice() != commit.stream_activations()
553 || circle_activations.circles().len() != commit.circle_controls().len()
554 || circle_activations
555 .circles()
556 .iter()
557 .zip(commit.circle_controls())
558 .any(|(activation, reference)| activation.reference != *reference)
559 || packages.is_empty() != package_application.is_none()
560 || commit.control().is_some() != membership_objects.is_some()
561 {
562 return Err(DbError::Message(
563 "verified Merge materialization differs from its exact Store commit".to_string(),
564 ));
565 }
566 let retained_objects = history_evidence
567 .membership_proof
568 .as_ref()
569 .map(|proof| {
570 VerifiedMergeMembershipObjects::verify(
571 commit,
572 commit_ref,
573 &proof.entry_value,
574 &proof.head_value,
575 proof.head.clone(),
576 )
577 })
578 .transpose()?;
579 if membership_objects != retained_objects.as_ref() {
580 return Err(DbError::Message(
581 "Merge membership objects differ from their retained exact proof".into(),
582 ));
583 }
584 RetainedStoreDeviceRegistrationActivations::from_verified(root, commit, registrations)
585 .map_err(DbError::from)?;
586 Ok(Self {
587 root,
588 verified_commit,
589 device_operations,
590 circle_activations,
591 acceptance,
592 history_evidence,
593 membership_objects,
594 packages,
595 package_application,
596 registrations,
597 })
598 }
599}
600
601pub struct PreparedMergeMaterializationPackage {
602 pub package: AudiencePackage,
603 pub changeset: ValidatedChangeset<Vec<u8>>,
604}
605
606pub struct PreparedMergeMaterialization {
607 pub root: StoreRootRef,
608 pub verified_commit: VerifiedStoreBatchCommit,
609 pub acceptance: AcceptedStoreCommitEvidence,
610 pub history_evidence: RetainedMergeCommitEvidence,
611 pub membership_objects: Option<VerifiedMergeMembershipObjects>,
612 pub membership_remote_objects: Vec<coven_protocol::remote_object::ClosedRemoteObject>,
613 pub registrations: Vec<ActivatedStoreDeviceRegistration>,
614 pub packages: Vec<PreparedMergeMaterializationPackage>,
615 pub device_operations: VerifiedStoreDeviceOperations,
616 pub circle_activations: VerifiedCircleActivations,
617 pub package_application: Option<crate::RetainedPackageApplication>,
618}
619
620pub struct MembershipAuthorityBytes {
621 canonical: Vec<u8>,
622 stored: Vec<u8>,
623}
624
625impl MembershipAuthorityBytes {
626 pub fn new(canonical: Vec<u8>, stored: Vec<u8>) -> Self {
627 Self { canonical, stored }
628 }
629}
630
631pub fn activated_merge_membership_remote_objects(
632 family: coven_protocol::store_commit::CandidateFamilyId,
633 objects: &VerifiedMergeMembershipObjects,
634 entry_bytes: MembershipAuthorityBytes,
635 head_bytes: MembershipAuthorityBytes,
636 commit_ref: &StoreBatchCommitRef,
637) -> Result<
638 Vec<coven_protocol::remote_object::ClosedRemoteObject>,
639 coven_protocol::remote_object::RemoteObjectRecordError,
640> {
641 let remotes = vec![
642 coven_protocol::remote_object::RemoteObjectRecord::candidate_exclusive_merge_membership_entry(
643 family,
644 objects.entry().clone(),
645 &entry_bytes.canonical,
646 &entry_bytes.stored,
647 commit_ref.clone(),
648 )?
649 .map_record(|record| record.into_observed_activated(commit_ref))?,
650 coven_protocol::remote_object::RemoteObjectRecord::candidate_exclusive_merge_membership_head(
651 family,
652 objects.head().clone(),
653 &head_bytes.canonical,
654 &head_bytes.stored,
655 commit_ref.clone(),
656 )?
657 .map_record(|record| record.into_observed_activated(commit_ref))?,
658 ];
659 Ok(remotes)
660}
661
662#[derive(Debug)]
667pub struct VerifiedStoreSnapshotAuthority {
668 authority: RetainedReplaySnapshotAuthority,
669}
670
671impl VerifiedStoreSnapshotAuthority {
672 pub fn from_authority(
673 authority: RetainedReplaySnapshotAuthority,
674 ) -> Result<Self, crate::DbError> {
675 authority.validate()?;
676 Ok(Self { authority })
677 }
678
679 pub fn into_authority(self) -> RetainedReplaySnapshotAuthority {
680 self.authority
681 }
682}
683
684#[derive(Clone)]
685pub struct DeviceJoinBootstrapCommit {
686 pub reference: StoreBatchCommitRef,
687 pub commit: VerifiedStoreBatchCommit,
688 pub registrations: Vec<ActivatedStoreDeviceRegistration>,
689 pub device_operations: VerifiedStoreDeviceOperations,
690 pub history_evidence: RetainedMergeCommitEvidence,
691}
692
693pub struct DeviceJoinBootstrapPlan {
694 pub founder_reference: StoreDeviceRegistrationRef,
695 pub founder: StoreDeviceRegistration,
696 pub founder_bytes: Vec<u8>,
697 pub genesis: ResolvedStoreDeviceState,
698 pub membership: InitialStoreMembershipAuthority,
699 pub publication: AcceptedStorePublicationInterval,
700 pub commits: Vec<DeviceJoinBootstrapCommit>,
701}
702
703pub struct DeviceJoinBootstrapRowData {
710 pub circle_activations: VerifiedCircleActivations,
711 pub membership_objects: Option<VerifiedMergeMembershipObjects>,
712 pub membership_remote_objects: Vec<coven_protocol::remote_object::ClosedRemoteObject>,
713 pub packages: Vec<PreparedMergeMaterializationPackage>,
714}
715
716pub struct ResolvedDeviceJoinBootstrap {
721 pub plan: DeviceJoinBootstrapPlan,
722 pub snapshot_circles: crate::StagedCircleRestore,
723 pub row_data: std::collections::BTreeMap<StoreBatchCommitRef, DeviceJoinBootstrapRowData>,
724 pub local_store_membership: coven_protocol::membership::LocalStoreMembership,
725 pub routing_key: Option<coven_protocol::circle::RowRoutingKey>,
726 pub receiver_wall_ms: u64,
727}
728
729#[path = "device_join_bootstrap.rs"]
730mod device_join_bootstrap;