1use super::*;
2
3#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
4#[serde(deny_unknown_fields)]
5pub struct StorePackageRef {
6 pub candidate_family: CandidateFamilyId,
7 pub content_hash: ObjectHash,
8 pub schema_version: u32,
9 pub changeset_size: u64,
10 pub object: ExactObjectRef,
11}
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(deny_unknown_fields)]
15pub struct CirclePackageRef {
16 pub circle_id: CircleId,
17 pub control: CircleControlCoord,
18 pub package: StorePackageRef,
19 pub key_fingerprint: KeyFingerprint,
20}
21
22#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
24#[serde(deny_unknown_fields)]
25pub struct CircleAccessEnvelopeObjectRef {
26 pub owner_pubkey: String,
27 pub recipient_slot: String,
28 pub control_hash: ObjectHash,
29 pub leaf_id: AccessLeafId,
30 pub leaf_hash: ObjectHash,
31 pub object: ExactObjectRef,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
36#[serde(deny_unknown_fields)]
37pub struct CircleAccessLeafObjectRef {
38 pub owner_pubkey: String,
39 pub epoch_id: CircleEpochId,
40 pub recipient_slot: String,
41 pub leaf_id: AccessLeafId,
42 pub leaf_hash: ObjectHash,
43 pub object: ExactObjectRef,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
47#[serde(deny_unknown_fields)]
48pub struct CircleAccessObjectRef {
49 pub leaf: CircleAccessLeafObjectRef,
50 pub envelope: CircleAccessEnvelopeObjectRef,
51 pub bootstrap: Option<SnapshotImageRef>,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
56#[serde(deny_unknown_fields)]
57pub struct CircleMetadataObjectRef {
58 pub key_fingerprint: KeyFingerprint,
59 pub object: ExactObjectRef,
60}
61
62#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
64#[serde(deny_unknown_fields)]
65pub struct CircleActivationObjects {
66 pub control: ExactObjectRef,
67 pub close_intent: Option<crate::circle::CircleEpochCloseIntentRef>,
68 pub close_outcome: Option<crate::circle::CircleEpochCloseOutcomeRef>,
69 pub close_cancellation: Option<crate::circle::CircleEpochCloseCancellationRef>,
70 #[serde(with = "ordered_map_entries")]
71 pub roster_entries: BTreeMap<CircleRosterCoord, ExactObjectRef>,
72 pub roster_heads: Vec<CircleRosterHeadRef>,
73 #[serde(with = "ordered_map_entries")]
74 pub roster_resolutions: BTreeMap<CircleRosterConflictResolutionRef, ExactObjectRef>,
75 #[serde(with = "ordered_map_entries")]
76 pub metadata_entries: BTreeMap<CircleMetadataCoord, CircleMetadataObjectRef>,
77 pub metadata_heads: Vec<CircleMetadataHeadRef>,
78 pub access: Vec<CircleAccessObjectRef>,
79}
80
81#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
82#[serde(deny_unknown_fields)]
83pub struct CircleControlRef {
84 pub circle_id: CircleId,
85 pub control: CircleControlCoord,
86 pub head_hash: ObjectHash,
87 pub head_object: ExactObjectRef,
88 pub objects: CircleActivationObjects,
89}
90
91impl CircleControlRef {
92 pub fn circle_id(&self) -> CircleId {
93 self.circle_id
94 }
95
96 pub fn control(&self) -> &CircleControlCoord {
97 &self.control
98 }
99
100 pub fn head_hash(&self) -> ObjectHash {
101 self.head_hash
102 }
103
104 pub fn head_object(&self) -> &ExactObjectRef {
105 &self.head_object
106 }
107
108 pub fn objects(&self) -> &CircleActivationObjects {
109 &self.objects
110 }
111}
112
113#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
114#[serde(deny_unknown_fields)]
115pub struct StoreDeviceRegistrationRef {
116 pub device_id: StoreDeviceId,
117 pub registration_hash: ObjectHash,
118 pub object: ExactObjectRef,
119}
120
121impl StoreDeviceRegistrationRef {
122 pub fn from_registration(
123 registration: &StoreDeviceRegistration,
124 object: ExactObjectRef,
125 ) -> Self {
126 Self {
127 device_id: registration.device_id,
128 registration_hash: registration.registration_hash(),
129 object,
130 }
131 }
132
133 pub fn verify_registration(
134 &self,
135 registration: &StoreDeviceRegistration,
136 ) -> Result<(), StoreProtocolError> {
137 if registration.device_id != self.device_id
138 || registration.registration_hash() != self.registration_hash
139 {
140 return Err(StoreProtocolError::DeviceRegistrationRefMismatch {
141 device_id: self.device_id.to_string(),
142 expected: self.registration_hash,
143 actual: registration.registration_hash(),
144 });
145 }
146 Ok(())
147 }
148}
149
150pub struct CirclePackageInput<'a> {
151 pub circle_id: CircleId,
152 pub control: CircleControlCoord,
153 pub key_fingerprint: KeyFingerprint,
154 pub package: StorePackageInput<'a>,
155}
156
157pub struct StorePackageInput<'a> {
158 pub candidate_family: CandidateFamilyId,
159 pub schema_version: u32,
160 pub bytes: &'a [u8],
161 pub object: ExactObjectRef,
162}
163
164#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
165#[serde(deny_unknown_fields)]
166pub struct StoreControl {
167 pub transition: crate::membership::MergeMembershipHeadTransition,
168}
169
170#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
171#[serde(transparent)]
172pub struct OwnerPromotionId(ObjectHash);
173
174impl OwnerPromotionId {
175 pub fn from_generated(value: String) -> Self {
176 Self(ObjectHash::digest(
177 &[
178 b"coven.owner-promotion-id.v1\0".as_slice(),
179 value.as_bytes(),
180 ]
181 .concat(),
182 ))
183 }
184}
185
186impl fmt::Display for OwnerPromotionId {
187 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
188 fmt::Display::fmt(&self.0, formatter)
189 }
190}
191
192#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
193#[serde(deny_unknown_fields)]
194pub struct OwnerPromotionFinalization {
195 pub author_stream: AuthorStreamId,
196 pub seq: u64,
197 pub previous_hash: Option<ObjectHash>,
198}
199
200#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
202#[serde(deny_unknown_fields)]
203pub struct OwnerPromotionRequestBody {
204 pub promotion_id: OwnerPromotionId,
205 pub store_root_hash: ObjectHash,
206 pub promoter_registration: StoreDeviceRegistrationRef,
207 pub promoter_owner_grant: MembershipGrantId,
208 pub member_pubkey: String,
209 pub member_grant: MembershipGrantId,
210 pub member_registration: StoreDeviceRegistrationRef,
211 pub intended_owner_grant: MembershipGrantId,
212 pub predecessor_membership: StoreMembershipStateRef,
213 pub predecessor_devices: StoreDeviceStateRef,
214 pub finalization: OwnerPromotionFinalization,
215}
216
217impl SignedBody for OwnerPromotionRequestBody {
218 const DOMAIN: &'static [u8] = b"coven.owner-promotion-request.v1\0";
219}
220
221pub type OwnerPromotionRequest = Signed<OwnerPromotionRequestBody>;
222
223impl OwnerPromotionRequest {
224 #[allow(clippy::too_many_arguments)]
225 pub fn signed(
226 promotion_id: OwnerPromotionId,
227 root: &StoreRootRef,
228 promoter_registration: StoreDeviceRegistrationRef,
229 promoter: &StoreDeviceRegistration,
230 promoter_owner_grant: MembershipGrantId,
231 member_pubkey: String,
232 member_grant: MembershipGrantId,
233 member_registration: StoreDeviceRegistrationRef,
234 predecessor_membership: StoreMembershipStateRef,
235 predecessor_devices: StoreDeviceStateRef,
236 finalization: OwnerPromotionFinalization,
237 signer: &UserKeypair,
238 ) -> Result<Self, StoreProtocolError> {
239 let intended_owner_grant =
240 derive_owner_promotion_grant(root.store_root_hash, promotion_id, &member_pubkey);
241 let body = OwnerPromotionRequestBody {
242 promotion_id,
243 store_root_hash: root.store_root_hash,
244 promoter_registration,
245 promoter_owner_grant,
246 member_pubkey,
247 member_grant,
248 member_registration,
249 intended_owner_grant,
250 predecessor_membership,
251 predecessor_devices,
252 finalization,
253 };
254 body.validate_shape(root, promoter)?;
255 let device_signer = promoter.device_signer(signer)?;
256 Ok(Signed::sign(body, &device_signer))
257 }
258
259 pub fn verify(
260 &self,
261 root: &StoreRootRef,
262 promoter: &StoreDeviceRegistration,
263 ) -> Result<(), StoreProtocolError> {
264 self.body().validate_shape(root, promoter)?;
265 self.verify_by(&promoter.device_signing_pubkey)
266 }
267}
268
269impl OwnerPromotionRequestBody {
270 fn validate_shape(
271 &self,
272 root: &StoreRootRef,
273 promoter: &StoreDeviceRegistration,
274 ) -> Result<(), StoreProtocolError> {
275 self.promoter_registration.verify_registration(promoter)?;
276 crate::objects::verify_store_root(root.store_root_hash, self.store_root_hash)?;
277 if promoter.store_root != *root
278 || promoter.author_pubkey == self.member_pubkey
279 || self.member_pubkey.is_empty()
280 || self.intended_owner_grant
281 != derive_owner_promotion_grant(
282 self.store_root_hash,
283 self.promotion_id,
284 &self.member_pubkey,
285 )
286 || self.finalization.seq == 0
287 {
288 return Err(StoreProtocolError::OwnerPromotionMismatch);
289 }
290 Ok(())
291 }
292}
293
294pub(crate) fn derive_owner_promotion_grant(
295 store_root_hash: ObjectHash,
296 promotion_id: OwnerPromotionId,
297 member_pubkey: &str,
298) -> MembershipGrantId {
299 MembershipGrantId(ObjectHash::digest(&domain_json(
300 b"coven.owner-promotion-grant.v1\0",
301 &(store_root_hash, promotion_id, member_pubkey),
302 )))
303}
304
305#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
306#[serde(deny_unknown_fields)]
307pub struct OwnerPromotionRequestActivation {
308 pub commit: StoreBatchCommitRef,
309 pub head: StoreDeviceHeadRef,
310}
311
312impl OwnerPromotionRequestActivation {
313 pub fn commit(&self) -> &StoreBatchCommitRef {
314 &self.commit
315 }
316}
317
318#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
319#[serde(deny_unknown_fields)]
320pub struct OwnerPromotionAnchors {
321 pub membership: GrantStreamAnchor,
322 pub recovery: GrantStreamAnchor,
323}
324
325impl OwnerPromotionAnchors {
326 pub fn recovery(&self) -> &GrantStreamAnchor {
327 &self.recovery
328 }
329}
330
331#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
333#[serde(deny_unknown_fields)]
334pub struct OwnerPromotionAcceptanceBody {
335 pub request: Box<OwnerPromotionRequest>,
336 pub activation: OwnerPromotionRequestActivation,
337 pub anchors: OwnerPromotionAnchors,
338}
339
340impl SignedBody for OwnerPromotionAcceptanceBody {
341 const DOMAIN: &'static [u8] = b"coven.owner-promotion-acceptance.v1\0";
342}
343
344pub type OwnerPromotionAcceptance = Signed<OwnerPromotionAcceptanceBody>;
345
346#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
347#[serde(rename_all = "snake_case", deny_unknown_fields)]
348pub enum OwnerPromotionStaleReason {
349 MergeFinalizationPointOccupied { winner: MembershipHeadRef },
350 MergeActivationRejected,
351}
352
353impl OwnerPromotionAcceptance {
354 pub fn signed(
355 request: OwnerPromotionRequest,
356 activation: OwnerPromotionRequestActivation,
357 anchors: OwnerPromotionAnchors,
358 candidate: &StoreDeviceRegistration,
359 signer: &UserKeypair,
360 ) -> Result<Self, StoreProtocolError> {
361 let body = OwnerPromotionAcceptanceBody {
362 request: Box::new(request),
363 activation,
364 anchors,
365 };
366 body.validate_shape(candidate)?;
367 let device_signer = candidate.device_signer(signer)?;
368 Ok(Signed::sign(body, &device_signer))
369 }
370
371 pub fn verify(&self, candidate: &StoreDeviceRegistration) -> Result<(), StoreProtocolError> {
372 self.body().validate_shape(candidate)?;
373 self.verify_by(&candidate.device_signing_pubkey)
374 }
375}
376
377impl OwnerPromotionAcceptanceBody {
378 fn validate_shape(
379 &self,
380 candidate: &StoreDeviceRegistration,
381 ) -> Result<(), StoreProtocolError> {
382 self.request
383 .member_registration
384 .verify_registration(candidate)?;
385 if candidate.store_root.store_root_hash != self.request.store_root_hash
386 || candidate.author_pubkey != self.request.member_pubkey
387 || !matches!(
388 self.anchors.recovery(),
389 GrantStreamAnchor::OwnerRecovery { .. }
390 )
391 {
392 return Err(StoreProtocolError::OwnerPromotionMismatch);
393 }
394 {
395 let membership = &self.anchors.membership;
396 let recovery = &self.anchors.recovery;
397 if !matches!(membership, GrantStreamAnchor::StoreMembership { .. }) {
398 return Err(StoreProtocolError::OwnerPromotionMismatch);
399 }
400 let membership_stream = StreamActivation::grant_authorized_stream_id(
401 self.request.store_root_hash,
402 &self.request.member_registration,
403 &self.request.intended_owner_grant,
404 StreamAnchorDomain::StoreMembership,
405 );
406 let membership_key = format!(
407 "{}.json",
408 membership_head_slot_prefix(
409 &self.request.member_pubkey,
410 &self.request.intended_owner_grant,
411 membership_stream,
412 1,
413 )
414 );
415 let recovery_key = format!(
416 "{}.json",
417 owner_recovery_semantic_prefix(
418 &self.request.member_pubkey,
419 self.request.intended_owner_grant.clone(),
420 1,
421 )
422 );
423 if membership.first_slot().logical_key() != membership_key
424 || recovery.first_slot().logical_key() != recovery_key
425 || matches!(
426 (membership.first_slot().physical(), recovery.first_slot().physical()),
427 (
428 crate::objects::PhysicalObjectLocator::Opaque(left),
429 crate::objects::PhysicalObjectLocator::Opaque(right),
430 ) if left == right
431 )
432 {
433 return Err(StoreProtocolError::OwnerPromotionMismatch);
434 }
435 }
436 Ok(())
437 }
438}
439
440#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
443#[serde(deny_unknown_fields)]
444pub struct OwnerConflictResolutionAcceptanceBody {
445 pub store_root_hash: ObjectHash,
446 pub owner_grant: MembershipGrantId,
447 pub owner_registration: StoreDeviceRegistrationRef,
448 pub provider: ProviderDeviceBinding,
449 pub membership: GrantStreamAnchor,
450 pub recovery: GrantStreamAnchor,
451 pub device_state: StoreDeviceStateRef,
452}
453
454impl SignedBody for OwnerConflictResolutionAcceptanceBody {
455 const DOMAIN: &'static [u8] = b"coven.owner-conflict-resolution-acceptance.v1\0";
456}
457
458pub type OwnerConflictResolutionAcceptance = Signed<OwnerConflictResolutionAcceptanceBody>;
459
460impl OwnerConflictResolutionAcceptance {
461 #[allow(clippy::too_many_arguments)]
462 pub fn signed(
463 store_root_hash: ObjectHash,
464 owner_grant: MembershipGrantId,
465 owner_registration: StoreDeviceRegistrationRef,
466 membership: GrantStreamAnchor,
467 recovery: GrantStreamAnchor,
468 device_state: StoreDeviceStateRef,
469 registration: &StoreDeviceRegistration,
470 signer: &UserKeypair,
471 ) -> Result<Self, StoreProtocolError> {
472 let body = OwnerConflictResolutionAcceptanceBody {
473 store_root_hash,
474 owner_grant,
475 owner_registration,
476 provider: registration.provider.clone(),
477 membership,
478 recovery,
479 device_state,
480 };
481 body.validate_shape(registration)?;
482 let device_signer = registration.device_signer(signer)?;
483 Ok(Signed::sign(body, &device_signer))
484 }
485
486 pub fn verify(&self, registration: &StoreDeviceRegistration) -> Result<(), StoreProtocolError> {
487 self.body().validate_shape(registration)?;
488 self.verify_by(®istration.device_signing_pubkey)
489 }
490}
491
492impl OwnerConflictResolutionAcceptanceBody {
493 fn validate_shape(
494 &self,
495 registration: &StoreDeviceRegistration,
496 ) -> Result<(), StoreProtocolError> {
497 self.owner_registration.verify_registration(registration)?;
498 if registration.store_root.store_root_hash != self.store_root_hash
499 || registration.provider != self.provider
500 || !matches!(
501 registration.store_commits,
502 DeviceStreamAnchor::StoreAnnouncements { .. }
503 )
504 || !matches!(self.membership, GrantStreamAnchor::StoreMembership { .. })
505 || !matches!(self.recovery, GrantStreamAnchor::OwnerRecovery { .. })
506 {
507 return Err(StoreProtocolError::OwnerRecoveryMismatch);
508 }
509 Ok(())
510 }
511}
512
513#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
514#[serde(deny_unknown_fields)]
515pub struct CandidateObjectManifest {
516 pub family: CandidateFamilyId,
517 pub objects: Vec<CandidateExclusiveObjectRef>,
518}
519
520#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
521#[serde(rename_all = "snake_case", deny_unknown_fields)]
522pub enum CandidateExclusiveObjectRef {
523 StorePackage(StorePackageRef),
524 CirclePackage(CirclePackageRef),
525 CircleEpochCloseIntent {
526 circle_id: CircleId,
527 reference: crate::circle::CircleEpochCloseIntentRef,
528 },
529 CircleEpochCloseOutcome {
530 circle_id: CircleId,
531 reference: crate::circle::CircleEpochCloseOutcomeRef,
532 },
533 CircleEpochCloseCancellation {
534 circle_id: CircleId,
535 reference: crate::circle::CircleEpochCloseCancellationRef,
536 },
537 CircleAccess {
538 circle_id: CircleId,
539 access: CircleAccessObjectRef,
540 },
541}
542
543#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
544#[serde(rename_all = "snake_case", deny_unknown_fields)]
545pub enum DeviceJoinAttemptDecisionRef {
546 Attempt(DeviceJoinAttemptId),
547 Abandoned(crate::store_commit::DeviceJoinAbandonmentRef),
548}
549
550impl DeviceJoinAttemptDecisionRef {
551 pub fn attempt_id(&self) -> DeviceJoinAttemptId {
552 match self {
553 Self::Attempt(attempt_id) => *attempt_id,
554 Self::Abandoned(reference) => reference.attempt_id,
555 }
556 }
557}
558
559#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
560#[serde(deny_unknown_fields)]
561pub struct StoreCommitOperations {
562 pub acknowledgement: Option<StoreAckRef>,
563 pub circle_acknowledgements: Vec<CircleAckRef>,
564 pub control: Option<StoreControl>,
565 pub device_join_attempt_decisions: Vec<DeviceJoinAttemptDecisionRef>,
566 pub provider_access_grants: Vec<crate::provider::StoreMemberProviderAccessGrantRef>,
567 pub device_registrations: Vec<ActivatedStoreDeviceRegistrationRef>,
568 pub device_exclusion_proposals: Vec<StoreDeviceExclusionProposalRef>,
569 pub device_exclusion_outcomes: Vec<StoreDeviceExclusionOutcomeRef>,
570 pub stream_activations: Vec<StreamActivation>,
571 pub circle_controls: Vec<CircleControlRef>,
572 pub store_package: Option<StorePackageRef>,
573 pub circle_packages: Vec<CirclePackageRef>,
574}
575
576impl StoreCommitOperations {
577 pub(super) fn is_empty(&self) -> bool {
578 self.acknowledgement.is_none() && self.has_no_other_operations()
579 }
580
581 pub fn is_circle_control_activation_only(&self) -> bool {
582 self.acknowledgement.is_none()
583 && self.circle_acknowledgements.is_empty()
584 && self.control.is_none()
585 && self.device_join_attempt_decisions.is_empty()
586 && self.provider_access_grants.is_empty()
587 && self.device_registrations.is_empty()
588 && self.device_exclusion_proposals.is_empty()
589 && self.device_exclusion_outcomes.is_empty()
590 && self.circle_controls.len() == 1
591 && self.store_package.is_none()
592 && self.circle_packages.is_empty()
593 }
594
595 fn has_no_other_operations(&self) -> bool {
596 self.circle_acknowledgements.is_empty()
597 && self.control.is_none()
598 && self.device_join_attempt_decisions.is_empty()
599 && self.provider_access_grants.is_empty()
600 && self.device_registrations.is_empty()
601 && self.device_exclusion_proposals.is_empty()
602 && self.device_exclusion_outcomes.is_empty()
603 && self.stream_activations.is_empty()
604 && self.circle_controls.is_empty()
605 && self.store_package.is_none()
606 && self.circle_packages.is_empty()
607 }
608}
609
610#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
611#[serde(rename_all = "snake_case", deny_unknown_fields)]
612pub enum StoreCommitBody {
613 Operations(StoreCommitOperations),
614 ReclaimAuthorization {
615 authorization: Box<crate::reclaim::ReclaimAuthorizationRef>,
616 },
617 ReclaimReceipt {
618 receipt: Box<crate::reclaim::ReclaimReceiptRef>,
619 },
620 OwnerPromotionRequest {
621 request: Box<OwnerPromotionRequest>,
622 },
623 AbandonCandidates {
624 manifests: Vec<CandidateCleanupManifest>,
625 },
626}
627
628pub struct StoreCommitOperationsInput<'a> {
629 pub acknowledgement: Option<StoreAckRef>,
630 pub circle_acknowledgements: Vec<CircleAckRef>,
631 pub control: Option<StoreControl>,
632 pub device_join_attempt_decisions: Vec<DeviceJoinAttemptDecisionRef>,
633 pub provider_access_grants: Vec<crate::provider::StoreMemberProviderAccessGrantRef>,
634 pub device_registrations: Vec<ActivatedStoreDeviceRegistrationRef>,
635 pub device_exclusion_proposals: Vec<StoreDeviceExclusionProposalRef>,
636 pub device_exclusion_outcomes: Vec<StoreDeviceExclusionOutcomeRef>,
637 pub stream_activations: Vec<StreamActivation>,
638 pub circle_controls: Vec<CircleControlRef>,
639 pub store_package: Option<StorePackageInput<'a>>,
640 pub circle_packages: &'a [CirclePackageInput<'a>],
641}
642
643impl StoreCommitOperationsInput<'_> {
644 pub fn empty() -> StoreCommitOperationsInput<'static> {
646 StoreCommitOperationsInput {
647 acknowledgement: None,
648 circle_acknowledgements: Vec::new(),
649 control: None,
650 device_join_attempt_decisions: Vec::new(),
651 provider_access_grants: Vec::new(),
652 device_registrations: Vec::new(),
653 device_exclusion_proposals: Vec::new(),
654 device_exclusion_outcomes: Vec::new(),
655 stream_activations: Vec::new(),
656 circle_controls: Vec::new(),
657 store_package: None,
658 circle_packages: &[],
659 }
660 }
661}
662
663#[derive(Debug, Clone, PartialEq, Eq)]
664pub struct StoreOperationMembershipAuthority {
665 pub predecessor: MembershipGrantCreationAuthority,
666}
667
668impl StoreOperationMembershipAuthority {
669 pub(super) fn into_commit_authority(self) -> MembershipGrantCreationAuthority {
670 self.predecessor
671 }
672}