1use super::*;
2
3impl<'storage> AuthorizedWriterOperation<'storage> {
4 pub(crate) async fn history_has_only_acknowledgements(
5 &mut self,
6 previous: &coven_protocol::store_commit::StoreHistoryCut,
7 current: &coven_protocol::store_commit::StoreHistoryCut,
8 ) -> Result<bool, crate::sync::store::pull::StorePullError> {
9 self.history
10 .history_has_only_acknowledgements(previous, current)
11 .await
12 }
13
14 pub(super) fn membership_objects(&self) -> StoreMembershipObjectVerifier<'_, 'storage> {
15 self.history.membership_objects()
16 }
17
18 pub(crate) fn store_root(&self) -> &coven_protocol::store_commit::StoreRootRef {
19 self.history.root()
20 }
21
22 pub(crate) async fn snapshot_publication(
23 &self,
24 ) -> crate::sync::store::snapshots::AuthorizedSnapshotPublication<'_> {
25 crate::sync::store::snapshots::AuthorizedSnapshotPublication::begin(
26 &self.database,
27 self.storage.as_ref(),
28 )
29 .await
30 }
31
32 pub(crate) async fn resume_snapshot_publication(
33 &mut self,
34 ) -> Result<
35 Option<coven_protocol::store_commit::SnapshotMeta>,
36 crate::sync::store::snapshots::SnapshotError,
37 > {
38 self.snapshots().resume_pending_publication().await
39 }
40
41 pub(crate) async fn publish_store_snapshot(
42 &mut self,
43 pending: &coven_database::DurableSnapshotPublication,
44 objects: &crate::sync::store::snapshots::AuthorizedSnapshotPublication<'_>,
45 ) -> Result<
46 crate::sync::store::authorization::history::publication::StoreSnapshotPublicationAttemptOutcome,
47 crate::sync::store::snapshots::SnapshotError,
48 >{
49 self.writer
50 .publish_store_snapshot(&mut self.history, &mut self.membership, pending, objects)
51 .await
52 }
53
54 pub(crate) async fn capture_current_store_snapshot_cut(
55 &self,
56 ) -> Result<
57 (
58 coven_database::CreatedSnapshot,
59 coven_protocol::store_commit::CommitFrontier,
60 ),
61 coven_database::DbError,
62 > {
63 self.history.capture_current_store_snapshot_cut().await
64 }
65
66 pub(crate) fn protocol_root(&self) -> &coven_protocol::store_commit::StoreProtocolRoot {
67 &self.history.verified_root_object().value
68 }
69
70 pub(crate) async fn prepare_wrapped_key(
71 &self,
72 recipient: &str,
73 value: coven_protocol::wrapped_store_key::WrappedStoreKey,
74 ) -> Result<
75 coven_protocol::wrapped_store_key::PreparedWrappedStoreKey,
76 coven_protocol::objects::StorageError,
77 > {
78 self.keyrings.prepare(recipient, value).await
79 }
80
81 pub(super) async fn select_membership_author_stream(
85 &self,
86 chain: &coven_protocol::membership::MembershipChain,
87 ) -> Result<
88 coven_protocol::membership::AuthorStreamId,
89 crate::sync::store::commit_publication::membership::MembershipMutationError,
90 > {
91 self.history
92 .select_membership_author_stream(chain, &self.writer.author_pubkey())
93 .await
94 }
95
96 pub(crate) async fn resolve_accepted_snapshot(
97 &mut self,
98 ) -> Result<
99 Result<
100 crate::sync::store::commit_verification::merge_history::SelectedStoreSnapshot,
101 crate::sync::store::ReplayBaselineDecline,
102 >,
103 crate::sync::store::acknowledgements::StoreAckError,
104 > {
105 self.history.resolve_accepted_snapshot().await
106 }
107
108 pub(super) async fn stage_verified_blob_plaintext(
109 &self,
110 authority: &coven_protocol::blob::RowBlobAuthority,
111 stored: &coven_protocol::blob::locator::StoredBlobRef,
112 destination: &std::path::Path,
113 ) -> Result<coven_foundation::local_file::AtomicStagedFile, crate::sync::BlobCacheError> {
114 let stage = self
115 .store_dir
116 .stage_atomic_file(destination)
117 .await
118 .map_err(crate::sync::BlobCacheError::File)?;
119 self.history
120 .stage_verified_blob_plaintext(
121 authority,
122 stored,
123 stage,
124 coven_storage::cloud::no_download_progress(),
125 )
126 .await
127 }
128
129 pub(super) async fn authorize_retained_preparation(
130 &self,
131 order: &coven_protocol::store_commit::StoreCommitOrder,
132 membership_heads: &[coven_protocol::membership::MembershipHeadRef],
133 ) -> Result<
134 crate::sync::store::commit_verification::merge_history::MergeOutboundAuthorization,
135 crate::sync::store::pull::StorePullError,
136 > {
137 self.writer
138 .authorize_retained_preparation(&self.history, order, membership_heads)
139 .await
140 }
141
142 pub(crate) async fn retire_arrived_device_joins(
166 &self,
167 ) -> Result<usize, crate::sync::store::DeviceJoinError> {
168 let awaiting = self.database.owner_device_joins_awaiting_arrival().await?;
169 if awaiting.is_empty() {
170 return Ok(0);
171 }
172 let frontier = self.database.materialized_frontier().await?;
173 let store_root_hash = self.store_root().store_root_hash;
174 let mut retired = 0;
175 for (attempt_id, registration) in awaiting {
176 let stream =
177 coven_protocol::store_commit::StreamActivation::device_authorized_stream_id(
178 store_root_hash,
179 ®istration,
180 coven_protocol::store_commit::StreamAnchorDomain::StoreAnnouncements,
181 );
182 if !frontier.contains_key(&stream.to_string()) {
183 continue;
184 }
185 self.database
186 .retire_device_join(attempt_id, crate::sync::store::DeviceJoinRole::Owner)
187 .await?;
188 retired += 1;
189 }
190 Ok(retired)
191 }
192
193 pub(crate) async fn seed_retained_history(
194 &mut self,
195 ) -> Result<(), crate::sync::store::pull::StorePullError> {
196 self.history.seed_retained_history().await
197 }
198
199 pub(super) async fn prepare_merge_history_successor(
200 &self,
201 commit: &coven_protocol::store_commit::VerifiedStoreBatchCommit,
202 membership: &coven_protocol::membership::MembershipChain,
203 recovery_author: Option<&coven_protocol::store_commit::StoreDeviceRegistrationRef>,
204 predecessor_state: &coven_protocol::store_commit::ResolvedStoreDeviceState,
205 state_after: &coven_protocol::store_commit::ResolvedStoreDeviceState,
206 evidence: crate::sync::store::commit_verification::merge_history::MergeHistorySuccessorEvidence,
207 ) -> Result<
208 crate::sync::store::commit_verification::merge_history::PreparedMergeHistorySuccessor,
209 crate::sync::store::pull::StorePullError,
210 > {
211 self.history
212 .prepare_merge_history_successor(
213 commit,
214 membership,
215 recovery_author,
216 predecessor_state,
217 state_after,
218 evidence,
219 )
220 .await
221 }
222
223 pub(super) async fn upload_commit(
224 &self,
225 candidate: &commit_plan::PreparedStoreOperationCommit,
226 ) -> Result<(), StoreError> {
227 let stream_id = candidate.reference.coord.stream_id;
228 let context = coven_protocol::objects::ProtocolObjectContext::signed_plaintext(
229 candidate.commit.store_root_hash,
230 coven_protocol::objects::ProtocolObjectDomain::StoreCommit,
231 );
232 let prefix = coven_protocol::store_commit::commit_semantic_prefix(
233 candidate.commit.candidate_family(),
234 &stream_id.to_string(),
235 candidate.commit.seq(),
236 candidate.commit.commit_hash(),
237 );
238 self.storage
239 .as_ref()
240 .create_verified_protocol_object(
241 &context,
242 &candidate.prepared_commit()?,
243 &prefix,
244 &candidate.commit.to_bytes(),
245 )
246 .await
247 .map_err(StoreError::prepared_object)
248 }
249
250 pub async fn pull(
251 &mut self,
252 routing_encryption: Option<&coven_keys::encryption::EncryptionService>,
253 ) -> Result<crate::sync::store::StorePullResult, SyncCycleFailure> {
254 let membership = self.membership.clone();
255 let execution = self
256 .writer
257 .pull(&mut self.history, &membership, routing_encryption)
258 .await
259 .map_err(|error| SyncCycleFailure::operation("pull Store commits", error))?;
260 self.membership = execution.membership;
261 Ok(execution.result)
262 }
263
264 pub(crate) fn require_current_owner(
265 &self,
266 author_pubkey: &str,
267 ) -> Result<(), coven_protocol::membership::MembershipError> {
268 if self.membership.is_owner_now(author_pubkey) {
269 Ok(())
270 } else {
271 Err(
272 coven_protocol::membership::MembershipError::SignerIsNotOwner(
273 author_pubkey.to_string(),
274 ),
275 )
276 }
277 }
278
279 pub(crate) async fn prepare_merge_snapshot_history_summary(
280 &self,
281 coverage: &coven_protocol::store_commit::CommitFrontier,
282 membership: &coven_protocol::membership::MembershipChain,
283 state: &coven_protocol::store_commit::ResolvedStoreDeviceState,
284 publication: &coven_protocol::store_commit::StoreCurrentPublicationRecord,
285 ) -> Result<
286 coven_protocol::store_commit::RetainedVerifiedMergeHistorySummary,
287 crate::sync::store::pull::StorePullError,
288 > {
289 self.writer
290 .prepare_merge_snapshot_history_summary(
291 &self.history,
292 coverage,
293 membership,
294 state,
295 publication,
296 )
297 .await
298 }
299
300 pub(crate) async fn membership_rollup_parts(
311 &mut self,
312 membership: &coven_protocol::membership::MembershipChain,
313 ) -> Result<
314 Vec<coven_protocol::store_commit::MembershipRollupStream>,
315 crate::sync::store::membership::AnchoredChainError,
316 > {
317 self.history.membership_rollup_parts(membership).await
318 }
319
320 pub(crate) fn snapshots(
321 &mut self,
322 ) -> crate::sync::store::snapshots::AuthorizedSnapshots<'_, 'storage> {
323 let database = self.database.clone();
324 let storage = Arc::clone(self.storage);
325 let store_dir = self.store_dir;
326 let local_writer = Arc::clone(&self.writer);
327 crate::sync::store::snapshots::AuthorizedSnapshots::new(
328 self,
329 database,
330 storage,
331 store_dir,
332 local_writer,
333 )
334 }
335
336 pub(crate) fn acknowledgements(
337 &mut self,
338 ) -> crate::sync::store::acknowledgements::AuthorizedAcknowledgements<'_, 'storage> {
339 let database = self.database.clone();
340 let storage = Arc::clone(self.storage);
341 let local_writer = Arc::clone(&self.writer);
342 crate::sync::store::acknowledgements::AuthorizedAcknowledgements::new(
343 self,
344 database,
345 storage,
346 local_writer,
347 )
348 }
349
350 pub(crate) fn reclaim_history(
351 &mut self,
352 ) -> crate::sync::store::reclaim::ReclaimHistory<'_, 'storage> {
353 self.history.reclaim()
354 }
355
356 pub(crate) fn owner_promotion(
357 &mut self,
358 ) -> crate::sync::store::owner_role_promotion::AuthorizedOwnerPromotion<'_, 'storage> {
359 let database = self.database.clone();
360 let storage = self.storage.clone();
361 let root = self.store_root().clone();
362 crate::sync::store::owner_role_promotion::AuthorizedOwnerPromotion::new(
363 self, database, storage, root,
364 )
365 }
366
367 pub(crate) fn owner_promotion_history(
368 &mut self,
369 ) -> crate::sync::store::owner_role_promotion::OwnerPromotionHistory<'_, 'storage> {
370 self.history.owner_promotion()
371 }
372
373 pub(crate) async fn refresh_authorization_state(
374 &self,
375 cipher: &dyn coven_storage::CloudSyncCipherStateAccess,
376 pending_rotation: &dyn coven_storage::CloudSyncRotationStateAccess,
377 master_keys: Option<&dyn coven_keys::keys::MasterKeyCustody>,
378 ) -> Result<(), SyncCycleFailure> {
379 let result = async {
380 if cipher.is_plaintext() {
381 tracing::debug!("refresh: plaintext home, nothing to refresh");
382 return Ok(());
383 }
384
385 let recipient = self.writer.author_pubkey();
386 let wrapped_keys = self
387 .membership
388 .wrapped_key_authority_for(&recipient)
389 .map_err(AuthorizationRefreshError::Membership)?;
390 if wrapped_keys.is_empty() {
391 tracing::debug!(
392 "refresh: no activated wrapped key for this device; keeping the live key"
393 );
394 return Ok(());
395 }
396
397 match self.keyrings.open(&self.membership).await {
398 Ok(new_encryption) => {
399 let merged = cipher
400 .merged_keyring(&new_encryption)
401 .map_err(AuthorizationRefreshError::InvalidKeyring)?;
402 if merged.merged_key_count() == merged.live_key_count() {
403 if pending_rotation.gate().is_some() {
404 let gate = self
405 .database
406 .complete_peer_rotation_adoption(merged.merged_generation())
407 .await
408 .map_err(AuthorizationRefreshError::Database)?;
409 pending_rotation.install_durable_gate(gate);
410 }
411 tracing::debug!(
412 "refresh: wrapped store key is already held by the live keyring"
413 );
414 } else {
415 let gate = self
416 .database
417 .record_peer_rotation(merged.merged_generation())
418 .await
419 .map_err(AuthorizationRefreshError::Database)?;
420 pending_rotation.install_durable_gate(Some(gate));
421 match master_keys {
422 None => {
423 tracing::info!(
424 committed_generation = merged.merged_generation(),
425 "refresh: found a rotated store key but this cycle has no \
426 master-key custody to adopt it; sealing is paused until a \
427 cycle with custody adopts it"
428 );
429 }
430 Some(master_keys) => {
431 let adopted = cipher
432 .adopt_key_rotation(&new_encryption, master_keys)
433 .map_err(AuthorizationRefreshError::KeyAdoption)?;
434 let gate = self
435 .database
436 .complete_peer_rotation_adoption(adopted.generation())
437 .await
438 .map_err(AuthorizationRefreshError::Database)?;
439 pending_rotation.install_durable_gate(gate);
440 tracing::info!(
441 fingerprint = adopted.fingerprint(),
442 "Adopted rotated store key"
443 );
444 }
445 }
446 }
447 }
448 Err(error) => return Err(AuthorizationRefreshError::WrappedKey(error)),
449 }
450
451 Ok(())
452 }
453 .await;
454
455 result.map_err(|error| SyncCycleFailure::operation("refresh authorization state", error))
456 }
457
458 pub(crate) fn circles(
459 &mut self,
460 ) -> crate::sync::store::circles::AuthorizedCircleWriter<'_, 'storage> {
461 let database = self.database.clone();
462 let storage = Arc::clone(self.storage);
463 let store_dir = self.store_dir;
464 let root = self.store_root().clone();
465 let local_writer = Arc::clone(&self.writer);
466 crate::sync::store::circles::AuthorizedCircleWriter::from_parts(
467 self,
468 database,
469 storage,
470 store_dir,
471 root,
472 local_writer,
473 )
474 }
475
476 pub(crate) fn circle_history(
477 &mut self,
478 ) -> crate::sync::store::commit_publication::circles::VerifiedCircleHistory<'_, 'storage> {
479 self.history.circles()
480 }
481
482 pub(crate) fn join_history(
483 &mut self,
484 ) -> crate::sync::store::device_join::history::DeviceJoinHistory<'_, 'storage> {
485 self.history.device_join()
486 }
487
488 pub(crate) fn device_exclusion_history(
489 &mut self,
490 ) -> crate::sync::store::device_exclusion::DeviceExclusionHistory<'_, 'storage> {
491 self.history.device_exclusion()
492 }
493
494 pub(crate) fn device_exclusion(
495 &mut self,
496 ) -> crate::sync::store::device_exclusion::AuthorizedDeviceExclusion<'_, 'storage> {
497 let database = self.database.clone();
498 let storage = Arc::clone(self.storage);
499 crate::sync::store::device_exclusion::AuthorizedDeviceExclusion::new(
500 self, database, storage,
501 )
502 }
503
504 pub(crate) fn join_operation(
505 &mut self,
506 ) -> crate::sync::store::commit_publication::device_join::AuthorizedJoin<'_, 'storage> {
507 let database = self.database.clone();
508 let storage = Arc::clone(self.storage);
509 let root = self.store_root().clone();
510 let verified_root = self.history.verified_root_object().clone();
511 let membership = self.membership.clone();
512 let local_writer = Arc::clone(&self.writer);
513 crate::sync::store::commit_publication::device_join::AuthorizedJoin::from_parts(
514 self,
515 database,
516 storage,
517 root,
518 verified_root,
519 membership,
520 local_writer,
521 )
522 }
523
524 pub(super) async fn membership_mutation_permit(
525 &self,
526 ) -> coven_database::store::MembershipMutationPermit {
527 self.database.membership_mutation_permit().await
528 }
529
530 pub(super) fn writer_pubkey(&self) -> String {
531 self.writer.author_pubkey()
532 }
533
534 pub(crate) fn local_author_pubkey(&self) -> String {
535 self.writer.author_pubkey()
536 }
537
538 pub(crate) fn is_local_registration(
539 &self,
540 registration: &coven_protocol::store_commit::StoreDeviceRegistrationRef,
541 ) -> bool {
542 self.writer.is_authored_by_registration(registration)
543 }
544
545 pub(crate) fn is_current_owner(
546 &self,
547 membership: &coven_protocol::membership::MembershipChain,
548 ) -> bool {
549 self.writer.is_current_owner(membership)
550 }
551
552 pub(crate) fn matches_local_author(
553 &self,
554 registration: &coven_protocol::store_commit::StoreDeviceRegistrationRef,
555 author_pubkey: &str,
556 ) -> bool {
557 self.writer.matches_author(registration, author_pubkey)
558 }
559
560 pub(crate) fn grant_authorized_stream_id(
561 &self,
562 grant: &coven_protocol::membership::MembershipGrantId,
563 domain: coven_protocol::store_commit::StreamAnchorDomain,
564 ) -> coven_protocol::membership::AuthorStreamId {
565 self.writer
566 .grant_authorized_stream_id(self.store_root().store_root_hash, grant, domain)
567 }
568}