Skip to main content

coven_protocol/store_commit/
packages.rs

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/// Exact recipient-visible access envelope paired with its sealed leaf.
23#[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/// Exact recipient-sealed access-leaf object named by a Store activation.
35#[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/// Exact Circle-metadata object and the epoch key that must open it.
55#[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/// Closed exact object graph needed to verify one Store-activated Circle control.
63#[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/// The wire body of an owner-promotion request. Every field here is signed.
201#[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/// The wire body of a promoted member's acceptance. Every field here is signed.
332#[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/// The wire body of an owner's acceptance of a resolved conflict. Every field
441/// here is signed.
442#[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(&registration.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    /// An input carrying no operations; authors fill in the kinds they commit.
645    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}