coven_replication/sync/store/commit_publication/operation/
mod.rs1use super::*;
2use crate::sync::store::authorization::load_wrapped_store_key;
3use crate::sync::store::commit_verification::commit::StoreMembershipObjectVerifier;
4use crate::sync::store::membership::MembershipMutationError;
5use coven_database::VerifiedMergeMembershipObjects;
6use coven_protocol::membership::{
7 self, MembershipChain, MembershipChange, MembershipEntry, MembershipError, MembershipHeadRef,
8};
9use coven_protocol::membership_mutation::{
10 PreparedMembershipPublication, PreparedMembershipTransition,
11};
12use coven_protocol::objects::{
13 ProtocolObjectContext, ProtocolObjectDomain, StorageError, StoreObjectError,
14};
15use coven_protocol::store_commit::{
16 self, commit_semantic_prefix, head_slot_prefix, membership_head_slot_prefix,
17 StoreBatchCommitDeletionTarget, StoreDeviceHeadRef,
18};
19use coven_protocol::wrapped_store_key::{PreparedWrappedStoreKey, WrappedStoreKeyRef};
20use coven_storage as store_objects;
21use std::sync::Arc;
22
23mod abandonment;
24mod blob_lifecycle;
25pub(crate) use blob_lifecycle::TombstoneGcError;
26mod blob_preparation;
27mod blob_upload;
28pub(crate) mod commit_plan;
29pub(super) mod membership_mutation;
30pub(super) mod membership_mutation_journal;
31mod preparation;
32
33mod commit_publication;
34mod facades;
35mod membership_commands;
36mod membership_publication;
37mod operation_test_support;
38mod signing;
39mod store_writes;
40
41pub(super) use blob_preparation::close_prepared_packages;
42pub(crate) use blob_preparation::prepare_partition_blob_locator;
43
44use membership_mutation_journal::{
45 decode_membership_mutation, exact_owned_remote, AdmissionMutationPlan, MembershipMutationPlan,
46 MembershipMutationProgress, MutationPersistence, ReplacementWrappedKey, ResolveMutationPlan,
47 RevokeMembershipPublication, RevokeMutationPlan,
48};
49
50pub(crate) struct MergeConflictResolutionCommitPlan {
51 authorship: coven_database::store::OwnStreamAuthorship,
52 writer: Arc<LocalStoreWriter>,
53 root: coven_protocol::store_commit::StoreRootRef,
54 coord: coven_protocol::store_commit::StoreCommitCoord,
55 order: coven_protocol::store_commit::StoreCommitOrder,
56 membership: coven_protocol::membership::MembershipChain,
57 device_state: coven_protocol::store_commit::StoreDeviceStateRef,
58 device_state_value: coven_protocol::store_commit::ResolvedStoreDeviceState,
59}
60
61impl MergeConflictResolutionCommitPlan {
62 #[allow(clippy::too_many_arguments)]
63 fn new(
64 authorship: coven_database::store::OwnStreamAuthorship,
65 writer: Arc<LocalStoreWriter>,
66 root: coven_protocol::store_commit::StoreRootRef,
67 coord: coven_protocol::store_commit::StoreCommitCoord,
68 order: coven_protocol::store_commit::StoreCommitOrder,
69 authorization: crate::sync::store::merge_conflict::MergeConflictResolutionAuthorization,
70 ) -> Self {
71 Self {
72 authorship,
73 writer,
74 root,
75 coord,
76 order,
77 membership: authorization.membership,
78 device_state: authorization.device_state_ref,
79 device_state_value: authorization.device_state,
80 }
81 }
82
83 pub(super) fn root(&self) -> &coven_protocol::store_commit::StoreRootRef {
84 &self.root
85 }
86
87 pub(super) fn membership(&self) -> &coven_protocol::membership::MembershipChain {
88 &self.membership
89 }
90
91 pub(super) fn grant_authorized_stream_id(
92 &self,
93 grant: &coven_protocol::membership::MembershipGrantId,
94 domain: coven_protocol::store_commit::StreamAnchorDomain,
95 ) -> coven_protocol::membership::AuthorStreamId {
96 self.writer
97 .grant_authorized_stream_id(self.root.store_root_hash, grant, domain)
98 }
99
100 pub(super) fn sign_conflict_resolution(
101 &self,
102 chain: &coven_protocol::membership::MembershipChain,
103 selection: coven_protocol::membership::MembershipConflictSelection,
104 replacement_grant: coven_protocol::membership::MembershipGrantId,
105 membership: coven_protocol::store_commit::GrantStreamAnchor,
106 recovery: coven_protocol::store_commit::GrantStreamAnchor,
107 ) -> Result<
108 coven_protocol::membership::StoreMembershipConflictResolution,
109 MembershipMutationError,
110 > {
111 self.writer.sign_conflict_resolution(
112 chain,
113 self.root.store_root_hash,
114 selection,
115 replacement_grant,
116 membership,
117 recovery,
118 self.device_state.clone(),
119 )
120 }
121
122 pub(super) fn sign_conflict_resolution_activation(
123 &self,
124 chain: &coven_protocol::membership::MembershipChain,
125 stream_id: coven_protocol::membership::AuthorStreamId,
126 reference: coven_protocol::membership::StoreMembershipConflictResolutionRef,
127 resolution: &coven_protocol::membership::StoreMembershipConflictResolution,
128 created_at: String,
129 ) -> Result<
130 coven_protocol::membership::MembershipEntry,
131 coven_protocol::membership::MembershipError,
132 > {
133 self.writer.sign_conflict_resolution_activation(
134 chain,
135 self.root.store_root_hash,
136 stream_id,
137 reference,
138 resolution,
139 created_at,
140 )
141 }
142
143 pub(super) fn finish(
144 self,
145 membership: &coven_protocol::membership::MembershipChain,
146 resolution: &coven_protocol::membership::StoreMembershipConflictResolutionRef,
147 ) -> Result<commit_plan::StoreOperationCommitPlan, StoreError> {
148 let coven_protocol::membership::MembershipStatus::Resolved(resolved) = membership.status()
149 else {
150 return Err(StoreError::InvalidOutbound(
151 "conflict-resolution candidate membership remains conflicted".to_string(),
152 ));
153 };
154 if membership
155 .resolution_refs()
156 .binary_search(resolution)
157 .is_err()
158 {
159 return Err(StoreError::InvalidOutbound(
160 "conflict-resolution candidate membership omits its exact resolution".to_string(),
161 ));
162 }
163 let replacement_grant = coven_protocol::membership::derive_store_resolution_grant(
164 &resolution.conflict_hash,
165 &resolution.resolver_pubkey,
166 );
167 let authority =
168 coven_protocol::membership::MembershipGrantCreationAuthority::ConflictResolution(
169 resolution.clone(),
170 );
171 if membership
172 .active_grant(&replacement_grant)
173 .is_none_or(|record| {
174 record.member_pubkey != self.writer.author_pubkey()
175 || record.creation_authority != authority
176 })
177 {
178 return Err(StoreError::InvalidOutbound(
179 "conflict-resolution candidate is not authorized by its replacement Owner grant"
180 .to_string(),
181 ));
182 }
183 let membership_state = coven_protocol::circle_control::StoreMembershipStateRef::from_parts(
184 membership.head_refs().to_vec(),
185 membership.resolution_refs().to_vec(),
186 self.device_state.recovery().to_vec(),
187 resolved.state_hash,
188 )
189 .map_err(StoreError::from)?;
190 let common = commit_plan::StoreOperationPlanCommon::new(
191 self.authorship,
192 self.writer,
193 self.root,
194 self.coord,
195 self.order,
196 membership_state,
197 self.device_state,
198 coven_protocol::store_commit::StoreOperationMembershipAuthority {
199 predecessor: authority,
200 },
201 Some(replacement_grant),
202 );
203 Ok(commit_plan::StoreOperationCommitPlan::new(
204 common,
205 membership.clone(),
206 self.device_state_value,
207 ))
208 }
209}
210
211#[derive(Debug, thiserror::Error)]
212pub enum StoreWriterAuthorizationError {
213 #[error("Store authority: {0}")]
214 StoreAuthority(SyncCycleFailure),
215 #[error("Store writer registration: {0}")]
216 Registration(StoreRegistrationError),
217}
218
219#[derive(Debug, thiserror::Error)]
220pub(crate) enum AuthorizationRefreshError {
221 #[error("select this device's wrapped-key authority: {0}")]
222 Membership(#[source] coven_protocol::membership::MembershipError),
223 #[error("read this device's wrapped key: {0}")]
224 WrappedKey(#[source] crate::sync::store::membership::MembershipMutationError),
225 #[error("rotation gate database state: {0}")]
226 Database(#[source] coven_database::DbError),
227 #[error("merge this device's live and selected keyrings: {0}")]
228 InvalidKeyring(#[source] coven_keys::encryption::EncryptionError),
229 #[error("adopt committed store-key rotation: {0}")]
230 KeyAdoption(#[source] coven_keys::keys::KeyError),
231}