1use std::collections::BTreeSet;
2
3use super::{
4 MergeMaterializationTransaction, StoreDatabase, StoreSession, StoreTransactionOutcome,
5 VerifiedStoreTransaction,
6};
7use crate::{install_store_founder_state_on, DbError, VerifiedMergeMaterialization};
8use coven_protocol::circle_activation::VerifiedCircleActivations;
9use coven_protocol::objects::ExactObjectRef;
10use coven_protocol::store_commit::{
11 ActivatedStoreDeviceRegistration, StoreDeviceHead, VerifiedStoreBatchCommit,
12 VerifiedStoreDeviceOperations,
13};
14
15#[cfg(any(test, feature = "test-utils"))]
16fn reach_materialization_failure(
17 armed: &std::sync::Mutex<Option<crate::MergeMaterializationFailurePoint>>,
18 point: crate::MergeMaterializationFailurePoint,
19) -> Result<bool, DbError> {
20 let mut armed = armed
21 .lock()
22 .map_err(|_| DbError::Message("Merge materialization failure lock poisoned".to_string()))?;
23 if armed.as_ref() != Some(&point) {
24 return Ok(false);
25 }
26 armed.take();
27 Ok(true)
28}
29
30fn device_join_bootstrap_represented_on(
41 tx: &rusqlite::Transaction<'_>,
42 commits: &[crate::DeviceJoinBootstrapCommit],
43) -> Result<BTreeSet<coven_protocol::store_commit::StoreBatchCommitRef>, DbError> {
44 let mut represented = BTreeSet::new();
45 let coverage = crate::store::materialized_commit_index::snapshot_coverage_on(tx)?;
46 for prepared in commits {
47 let stream_id = prepared.reference.coord.stream_id.to_string();
48 let sequence = prepared.reference.coord.sequence();
49 if let Some(existing) = crate::store::materialized_commit_index::materialized_commit_ref_on(
50 tx, &stream_id, sequence,
51 )? {
52 if existing != prepared.reference {
53 return Err(DbError::Message(format!(
54 "device join bootstrap conflicts at {stream_id}/{sequence}"
55 )));
56 }
57 represented.insert(prepared.reference.clone());
58 continue;
59 }
60 if coverage
61 .get(&stream_id)
62 .is_some_and(|tip| sequence <= tip.coord.sequence())
63 {
64 return Err(DbError::Message(format!(
65 "device join bootstrap history at {stream_id}/{sequence} is not the history the \
66 installed snapshot covers"
67 )));
68 }
69 }
70 Ok(represented)
71}
72
73impl VerifiedStoreTransaction<'_, '_, '_> {
74 fn apply_received_merge_materialization(
75 &mut self,
76 materialization: crate::PreparedMergeMaterialization,
77 retractions: Vec<coven_protocol::remote_object::VerifiedCandidateNonactivation>,
78 local_store_membership: coven_protocol::membership::LocalStoreMembership,
79 routing_key: Option<coven_protocol::circle::RowRoutingKey>,
80 receiver_wall_ms: u64,
81 ) -> Result<super::merge_materialization_transaction::AppliedMergeMaterialization, DbError>
82 {
83 #[cfg(any(test, feature = "test-utils"))]
84 let materialization_failure = self.merge_materialization_failure;
85 let authority = &mut *self.authority;
86 let blob_decls = self.blob_decls;
87 let gates = self.gates;
88 let synced_tables = self.synced_tables;
89 let root = materialization.root.clone();
90 let candidate = materialization.verified_commit.reference().clone();
91 let local_exclusions = materialization
92 .circle_activations
93 .local_exclusions()
94 .to_vec();
95 if !materialization.packages.is_empty()
96 && materialization.package_application
97 != Some(crate::RetainedPackageApplication::Received { receiver_wall_ms })
98 {
99 return Err(DbError::Message(
100 "received Merge packages carry another application timestamp".to_string(),
101 ));
102 }
103 let tx = self.store.transaction;
104 let merge_transaction = MergeMaterializationTransaction::from_store(self.store);
105 merge_transaction.record_prepared_materialization_authority(&materialization)?;
106 let retained =
107 merge_transaction.retain_prepared_merge_materialization(authority, &materialization)?;
108 authority.insert_verified(retained)?;
109 #[cfg(any(test, feature = "test-utils"))]
110 if reach_materialization_failure(
111 materialization_failure,
112 crate::MergeMaterializationFailurePoint::SummaryMaterialization,
113 )? {
114 return Err(DbError::Message(
115 "injected failure after Merge summary materialization".to_string(),
116 ));
117 }
118 for exclusion in &local_exclusions {
119 super::circle_operations::record_circle_close_exclusion_on(tx, exclusion)?;
120 }
121 let retracted = retractions
122 .iter()
123 .map(|retraction| retraction.candidate_reference().map_err(DbError::from))
124 .collect::<Result<BTreeSet<_>, _>>()?;
125 let mut write_status_notifications = Vec::new();
126 if !retractions.is_empty() {
127 write_status_notifications =
128 super::merge_materialization_transaction::retract_verified_merge_materializations(
129 &merge_transaction,
130 &root,
131 authority,
132 retractions,
133 )?;
134 #[cfg(any(test, feature = "test-utils"))]
135 if reach_materialization_failure(
136 materialization_failure,
137 crate::MergeMaterializationFailurePoint::RetractionDeletion,
138 )? {
139 return Err(DbError::Message(
140 "injected failure after Merge retraction deletion".to_string(),
141 ));
142 }
143 }
144 let replayed = authority.replay_projection_watching_on(
145 crate::store::store_session::StoreTransaction::new(tx, self.store.store_dir),
146 blob_decls,
147 gates,
148 synced_tables,
149 routing_key.as_ref(),
150 &retracted,
151 crate::ReplayJournal::Owed,
152 local_store_membership,
153 &candidate,
154 )?;
155 let watched = replayed.watched_outcome().ok_or_else(|| {
156 DbError::Message("incoming Merge materialization was not replayed".to_string())
157 })?;
158 let max_updated_at = match watched {
159 super::WatchedReplayOutcome::Applied { max_updated_at } => max_updated_at,
160 super::WatchedReplayOutcome::Held(reason) => {
161 return Ok(
162 super::merge_materialization_transaction::AppliedMergeMaterialization {
163 outcome: crate::MaterializationOutcome::Held(reason),
164 max_updated_at: None,
165 write_status_notifications: Vec::new(),
166 },
167 );
168 }
169 };
170 let rows = replayed.install_on(self, &root)?;
171 Ok(
172 super::merge_materialization_transaction::AppliedMergeMaterialization {
173 outcome: crate::MaterializationOutcome::Applied(rows),
174 max_updated_at,
175 write_status_notifications,
176 },
177 )
178 }
179
180 pub(super) fn install_replay_projection(
181 &self,
182 root: &coven_protocol::store_commit::StoreRootRef,
183 replay: &super::ReplayProjection,
184 ) -> Result<Vec<coven_foundation::changeset::RowChange>, DbError> {
185 let tx = self.store.transaction;
186 let mut host_changes = rusqlite::session::Session::new(tx).map_err(DbError::from)?;
187 for table in self.synced_tables {
188 host_changes
189 .attach(Some(table.name()))
190 .map_err(DbError::from)?;
191 }
192 let mut tables = crate::projection_table_names(self.gates.has_scoped_graph());
193 tables.extend(
194 self.synced_tables
195 .iter()
196 .map(|table| table.name().to_string()),
197 );
198 tables.sort();
199 tables.dedup();
200 let projected_blobs = replay
201 .publication_blobs(self.blob_decls)?
202 .into_iter()
203 .map(|publication| publication.blob)
204 .collect::<Vec<_>>();
205 let suspended_cleanup =
206 super::local_blob_cleanup::suspend_leased_blob_cleanup_for_restoration_on(
207 tx,
208 &projected_blobs,
209 )?;
210 let old_exact_bindings = super::local_blob_cleanup::exact_blob_bindings_on(tx)?;
211 tx.pragma_update(None, "defer_foreign_keys", "ON")
212 .map_err(DbError::from)?;
213 crate::store::store_session::StoreTransaction::new(tx, self.store.store_dir)
214 .replace_tables_from_projection(replay, &tables)?;
215 let violations: bool = tx
216 .query_row(
217 "SELECT EXISTS(SELECT 1 FROM pragma_foreign_key_check)",
218 [],
219 |row| row.get(0),
220 )
221 .map_err(DbError::from)?;
222 if violations {
223 let violation: (String, Option<i64>, String, i64) = tx
224 .query_row(
225 "SELECT * FROM pragma_foreign_key_check LIMIT 1",
226 [],
227 |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)),
228 )
229 .map_err(DbError::from)?;
230 return Err(DbError::Message(format!(
231 "retained replay projection violates foreign keys: {violation:?}"
232 )));
233 }
234 let mut projection_changeset = Vec::new();
235 host_changes
236 .changeset_strm(&mut projection_changeset)
237 .map_err(DbError::from)?;
238 #[cfg(any(test, feature = "test-utils"))]
239 if reach_materialization_failure(
240 self.merge_materialization_failure,
241 crate::MergeMaterializationFailurePoint::ProjectionReplacement,
242 )? {
243 return Err(DbError::Message(
244 "injected failure after Merge projection replacement".to_string(),
245 ));
246 }
247 MergeMaterializationTransaction::from_store(self.store)
248 .replace_store_device_exclusion_freezes_from_replay(root)?;
249 let old_projection =
250 crate::walk_old_changeset(&projection_changeset).map_err(DbError::Changeset)?;
251 let new_projection =
252 crate::walk_changeset(&projection_changeset).map_err(DbError::Changeset)?;
253 for intent in crate::local_blob_cleanup_intents::intents_from_changes(
254 self.blob_decls,
255 &old_projection,
256 &new_projection,
257 )
258 .map_err(DbError::from)?
259 {
260 super::local_blob_cleanup::record_obsolete_copy_intents_from_bindings_on(
261 tx,
262 self.blob_decls,
263 &intent,
264 &old_exact_bindings,
265 )?;
266 }
267 super::local_blob_cleanup::reevaluate_suspended_blob_cleanup_on(
268 tx,
269 self.blob_decls,
270 &suspended_cleanup,
271 )?;
272 crate::Database::cancel_transitions_for_deleted_roots_on(
273 tx,
274 &super::merge_materialization_transaction::deleted_rows(&new_projection),
275 )?;
276 Ok(new_projection)
277 }
278
279 #[allow(clippy::too_many_arguments)]
280 fn materialize_published_store_operation(
281 &mut self,
282 root: coven_protocol::store_commit::StoreRootRef,
283 verified_commit: VerifiedStoreBatchCommit,
284 registrations: Vec<ActivatedStoreDeviceRegistration>,
285 device_operations: VerifiedStoreDeviceOperations,
286 circle_activations: VerifiedCircleActivations,
287 activation_head: StoreDeviceHead,
288 activation_head_object: ExactObjectRef,
289 history_evidence: coven_protocol::store_commit::RetainedMergeCommitEvidence,
290 membership_objects: Option<crate::VerifiedMergeMembershipObjects>,
291 operation_object_ids: Option<Vec<coven_protocol::store_commit::ObjectHash>>,
292 membership_completion: Option<
293 coven_protocol::membership_mutation::StoreMembershipJournalCompletion,
294 >,
295 ) -> Result<(), DbError> {
296 let reference = verified_commit.reference().clone();
297 let tx = self.store.transaction;
298 let authority = &mut *self.authority;
299 let store_transaction = MergeMaterializationTransaction::from_store(self.store);
300 if let Some(object_ids) = operation_object_ids {
301 store_transaction.activate_store_operation_remote_objects(&reference, &object_ids)?;
302 }
303 if !registrations.is_empty() {
304 super::record_activated_store_device_registrations_on(
305 tx,
306 verified_commit.value(),
307 ®istrations,
308 )?;
309 }
310 let materialization = VerifiedMergeMaterialization::verify(
311 &root,
312 &verified_commit,
313 ®istrations,
314 &device_operations,
315 &circle_activations,
316 &activation_head,
317 &activation_head_object,
318 &history_evidence,
319 membership_objects.as_ref(),
320 &[],
321 None,
322 )?;
323 if let Some(completion) = membership_completion {
324 store_transaction
325 .complete_membership_journal(completion, &reference)
326 .map_err(|error| DbError::context("complete exact membership journal", error))?;
327 }
328 let retained = store_transaction
329 .record_verified_merge_materialization(authority, materialization)
330 .map_err(|error| DbError::context("record exact Merge materialization", error))?;
331 authority.insert_verified(retained)?;
332 Ok(())
333 }
334
335 fn materialize_device_join_activation(
336 &mut self,
337 root: coven_protocol::store_commit::StoreRootRef,
338 verified_commit: VerifiedStoreBatchCommit,
339 registrations: Vec<ActivatedStoreDeviceRegistration>,
340 device_operations: VerifiedStoreDeviceOperations,
341 activation_head: StoreDeviceHead,
342 activation_head_object: ExactObjectRef,
343 history_evidence: coven_protocol::store_commit::RetainedMergeCommitEvidence,
344 ) -> Result<(), DbError> {
345 let expected_ref = verified_commit.reference().clone();
346 let stream_id = expected_ref.coord.stream_id.to_string();
347 let sequence = expected_ref.coord.sequence();
348 let tx = self.store.transaction;
349 if let Some(materialized) =
350 crate::store::materialized_commit_index::materialized_commit_ref_on(
351 tx, &stream_id, sequence,
352 )?
353 {
354 if materialized != expected_ref {
355 return Err(DbError::Message(format!(
356 "device join activation coordinate {stream_id}/{sequence} is already occupied by another commit"
357 )));
358 }
359 super::record_activated_store_device_registrations_on(
360 tx,
361 verified_commit.value(),
362 ®istrations,
363 )?;
364 return Ok(());
365 }
366 let authority = &mut *self.authority;
367 super::record_activated_store_device_registrations_on(
368 tx,
369 verified_commit.value(),
370 ®istrations,
371 )?;
372 let circle_activations =
373 VerifiedCircleActivations::none(verified_commit.value(), verified_commit.reference())
374 .map_err(DbError::from)?;
375 let materialization = VerifiedMergeMaterialization::verify(
376 &root,
377 &verified_commit,
378 ®istrations,
379 &device_operations,
380 &circle_activations,
381 &activation_head,
382 &activation_head_object,
383 &history_evidence,
384 None,
385 &[],
386 None,
387 )?;
388 let retained = MergeMaterializationTransaction::from_store(self.store)
389 .record_verified_merge_materialization(authority, materialization)?;
390 authority.insert_verified(retained)?;
391 Ok(())
392 }
393
394 fn install_device_join_bootstrap(
395 &mut self,
396 root: coven_protocol::store_commit::StoreRootRef,
397 resolved: crate::ResolvedDeviceJoinBootstrap,
398 ) -> Result<Option<coven_protocol::hlc::Timestamp>, DbError> {
399 let crate::ResolvedDeviceJoinBootstrap {
400 plan,
401 mut row_data,
402 local_store_membership,
403 routing_key,
404 receiver_wall_ms,
405 } = resolved;
406 let tx = self.store.transaction;
407 let blob_decls = self.blob_decls;
408 let gates = self.gates;
409 let synced_tables = self.synced_tables;
410 let authority = &mut *self.authority;
411 let installed_root = authority.root().clone();
412 if installed_root != root || plan.founder.store_root != root {
413 return Err(DbError::Message(
414 "device join bootstrap root differs from the installed exact root".to_string(),
415 ));
416 }
417 install_store_founder_state_on(
418 tx,
419 &root,
420 &plan.founder_reference,
421 &plan.founder,
422 &plan.founder_bytes,
423 &plan.genesis,
424 )?;
425 crate::set_protocol_state_on(
426 tx,
427 coven_protocol::membership::OWNER_PUBKEY_STATE_KEY,
428 &plan.founder.author_pubkey,
429 )?;
430 plan.membership.install_on(tx)?;
431
432 let represented = device_join_bootstrap_represented_on(tx, &plan.commits)?;
433
434 for prepared in &plan.commits {
438 if represented.contains(&prepared.reference) {
439 continue;
440 }
441 let commit = prepared.commit.value();
442 let resolved = row_data.get(&prepared.reference);
443 let carries_store_package = resolved.is_some_and(|data| {
444 data.packages.iter().any(|prepared| {
445 matches!(
446 prepared.package.audience(),
447 coven_protocol::audience_package::PackageAudience::Store
448 )
449 })
450 });
451 if resolved.is_none() || (commit.store_package().is_some() && !carries_store_package) {
452 return Err(DbError::Message(format!(
453 "device join bootstrap cannot advance over unmaterialized row data at {}/{}",
454 prepared.reference.coord.stream_id,
455 prepared.reference.coord.sequence()
456 )));
457 }
458 }
459
460 let mut retained_any = false;
461 for prepared in plan.commits {
462 if represented.contains(&prepared.reference) {
463 continue;
464 }
465 let stream_id = prepared.reference.coord.stream_id.to_string();
466 if let Some(existing) =
467 crate::store::materialized_commit_index::materialized_commit_ref_on(
468 tx,
469 &stream_id,
470 prepared.reference.coord.sequence(),
471 )?
472 {
473 if existing != prepared.reference {
474 return Err(DbError::Message(format!(
475 "device join bootstrap conflicts at {stream_id}/{}",
476 prepared.reference.coord.sequence()
477 )));
478 }
479 continue;
480 }
481 let data = row_data.remove(&prepared.reference).ok_or_else(|| {
482 DbError::Message(format!(
483 "device join bootstrap has no resolved row data at {stream_id}/{}",
484 prepared.reference.coord.sequence()
485 ))
486 })?;
487 let activation = prepared.activation;
488 let materialization = crate::PreparedMergeMaterialization {
489 root: root.clone(),
490 verified_commit: prepared.commit,
491 activation_head: activation.head,
492 activation_head_object: activation.object,
493 history_evidence: activation.history_evidence,
494 membership_objects: data.membership_objects,
495 membership_remote_objects: data.membership_remote_objects,
496 registrations: prepared.registrations,
497 package_application: (!data.packages.is_empty())
498 .then_some(crate::RetainedPackageApplication::Received { receiver_wall_ms }),
499 packages: data.packages,
500 device_operations: prepared.device_operations,
501 circle_activations: data.circle_activations,
502 };
503 let merge_transaction = MergeMaterializationTransaction::from_store(self.store);
504 merge_transaction.record_prepared_materialization_authority(&materialization)?;
505 let retained = merge_transaction
506 .retain_prepared_merge_materialization(authority, &materialization)?;
507 authority.insert_verified(retained)?;
508 retained_any = true;
509 }
510 if !row_data.is_empty() {
511 return Err(DbError::Message(
512 "device join bootstrap resolved row data outside its exact history".to_string(),
513 ));
514 }
515 if !retained_any {
516 return Ok(None);
517 }
518 let replayed = authority.replay_projection_result_on(
519 crate::store::store_session::StoreTransaction::new(tx, self.store.store_dir),
520 blob_decls,
521 gates,
522 synced_tables,
523 routing_key.as_ref(),
524 crate::ReplayJournal::Owed,
525 local_store_membership,
526 )?;
527 replayed.install_on(self, &root)?;
528 Ok(replayed.max_updated_at())
529 }
530
531 fn complete_owner_recovery(
532 &mut self,
533 verified_commit: VerifiedStoreBatchCommit,
534 activation_head: StoreDeviceHead,
535 activation_head_object: ExactObjectRef,
536 history_evidence: coven_protocol::store_commit::RetainedMergeCommitEvidence,
537 registration: ActivatedStoreDeviceRegistration,
538 ) -> Result<(), DbError> {
539 let tx = self.store.transaction;
540 let authority = &mut *self.authority;
541 let root = authority.root().clone();
542 let registrations = vec![registration];
543 let commit = verified_commit.value();
544 super::record_activated_store_device_registrations_on(tx, commit, ®istrations)?;
545 let retained = MergeMaterializationTransaction::from_store(self.store)
546 .record_materialized_merge_commit(
547 authority,
548 &root,
549 &verified_commit,
550 ®istrations,
551 &activation_head,
552 &activation_head_object,
553 &history_evidence,
554 &[],
555 None,
556 )?;
557 authority.insert_verified(retained)?;
558 super::owner_recovery_publication::complete_owner_recovery_publication_on(
559 tx,
560 &verified_commit,
561 &activation_head,
562 &activation_head_object,
563 )?;
564 #[cfg(any(test, feature = "test-utils"))]
565 if reach_materialization_failure(
566 self.merge_materialization_failure,
567 crate::MergeMaterializationFailurePoint::SummaryMaterialization,
568 )? {
569 return Err(DbError::Message(
570 "injected failure after Merge summary materialization".to_string(),
571 ));
572 }
573 Ok(())
574 }
575}
576
577impl StoreSession<'_> {
578 fn apply_received_merge_materialization(
579 &mut self,
580 materialization: crate::PreparedMergeMaterialization,
581 retractions: Vec<coven_protocol::remote_object::VerifiedCandidateNonactivation>,
582 local_store_membership: coven_protocol::membership::LocalStoreMembership,
583 routing_key: Option<coven_protocol::circle::RowRoutingKey>,
584 receiver_wall_ms: u64,
585 ) -> Result<super::merge_materialization_transaction::AppliedMergeMaterialization, DbError>
586 {
587 let applied = self.verified_store_transaction(move |transaction| {
588 let applied = transaction.apply_received_merge_materialization(
589 materialization,
590 retractions,
591 local_store_membership,
592 routing_key,
593 receiver_wall_ms,
594 )?;
595 if matches!(applied.outcome, crate::MaterializationOutcome::Applied(_)) {
596 Ok(StoreTransactionOutcome::Commit(applied))
597 } else {
598 Ok(StoreTransactionOutcome::Rollback(applied))
599 }
600 })?;
601 if let Some(max_applied) = applied.max_updated_at.as_ref() {
602 self.hlc.advance_past(max_applied);
603 }
604 Ok(applied)
605 }
606
607 #[allow(clippy::too_many_arguments)]
608 fn materialize_published_store_operation(
609 &mut self,
610 root: coven_protocol::store_commit::StoreRootRef,
611 verified_commit: VerifiedStoreBatchCommit,
612 registrations: Vec<ActivatedStoreDeviceRegistration>,
613 device_operations: VerifiedStoreDeviceOperations,
614 circle_activations: VerifiedCircleActivations,
615 activation_head: StoreDeviceHead,
616 activation_head_object: ExactObjectRef,
617 history_evidence: coven_protocol::store_commit::RetainedMergeCommitEvidence,
618 membership_objects: Option<crate::VerifiedMergeMembershipObjects>,
619 operation_object_ids: Option<Vec<coven_protocol::store_commit::ObjectHash>>,
620 membership_completion: Option<
621 coven_protocol::membership_mutation::StoreMembershipJournalCompletion,
622 >,
623 ) -> Result<(), DbError> {
624 self.verified_store_transaction(move |transaction| {
625 transaction.materialize_published_store_operation(
626 root,
627 verified_commit,
628 registrations,
629 device_operations,
630 circle_activations,
631 activation_head,
632 activation_head_object,
633 history_evidence,
634 membership_objects,
635 operation_object_ids,
636 membership_completion,
637 )?;
638 Ok(StoreTransactionOutcome::Commit(()))
639 })
640 }
641
642 fn materialize_device_join_activation(
643 &mut self,
644 root: coven_protocol::store_commit::StoreRootRef,
645 verified_commit: VerifiedStoreBatchCommit,
646 registrations: Vec<ActivatedStoreDeviceRegistration>,
647 device_operations: VerifiedStoreDeviceOperations,
648 activation_head: StoreDeviceHead,
649 activation_head_object: ExactObjectRef,
650 history_evidence: coven_protocol::store_commit::RetainedMergeCommitEvidence,
651 ) -> Result<(), DbError> {
652 self.verified_store_transaction(move |transaction| {
653 transaction.materialize_device_join_activation(
654 root,
655 verified_commit,
656 registrations,
657 device_operations,
658 activation_head,
659 activation_head_object,
660 history_evidence,
661 )?;
662 Ok(StoreTransactionOutcome::Commit(()))
663 })
664 }
665
666 fn unrepresented_device_join_bootstrap_commits(
667 &mut self,
668 plan: crate::DeviceJoinBootstrapPlan,
669 ) -> Result<
670 (
671 crate::DeviceJoinBootstrapPlan,
672 Vec<coven_protocol::store_commit::StoreBatchCommitRef>,
673 ),
674 DbError,
675 > {
676 self.verified_store_transaction(move |transaction| {
677 let represented =
678 device_join_bootstrap_represented_on(transaction.store.transaction, &plan.commits)?;
679 let unrepresented = plan
680 .commits
681 .iter()
682 .map(|prepared| prepared.reference.clone())
683 .filter(|reference| !represented.contains(reference))
684 .collect::<Vec<_>>();
685 Ok(StoreTransactionOutcome::Rollback((plan, unrepresented)))
686 })
687 }
688
689 fn install_device_join_bootstrap(
690 &mut self,
691 root: coven_protocol::store_commit::StoreRootRef,
692 resolved: crate::ResolvedDeviceJoinBootstrap,
693 ) -> Result<(), DbError> {
694 let max_updated_at = self.verified_store_transaction(move |transaction| {
695 let max_updated_at = transaction.install_device_join_bootstrap(root, resolved)?;
696 Ok(StoreTransactionOutcome::Commit(max_updated_at))
697 })?;
698 if let Some(max_applied) = max_updated_at.as_ref() {
699 self.hlc.advance_past(max_applied);
700 }
701 Ok(())
702 }
703
704 fn complete_owner_recovery(
705 &mut self,
706 verified_commit: VerifiedStoreBatchCommit,
707 activation_head: StoreDeviceHead,
708 activation_head_object: ExactObjectRef,
709 history_evidence: coven_protocol::store_commit::RetainedMergeCommitEvidence,
710 registration: ActivatedStoreDeviceRegistration,
711 ) -> Result<(), DbError> {
712 self.verified_store_transaction(move |transaction| {
713 transaction.complete_owner_recovery(
714 verified_commit,
715 activation_head,
716 activation_head_object,
717 history_evidence,
718 registration,
719 )?;
720 Ok(StoreTransactionOutcome::Commit(()))
721 })
722 }
723}
724
725impl StoreDatabase {
726 pub async fn apply_received_merge_materialization(
727 &self,
728 materialization: crate::PreparedMergeMaterialization,
729 retractions: Vec<coven_protocol::remote_object::VerifiedCandidateNonactivation>,
730 local_store_membership: coven_protocol::membership::LocalStoreMembership,
731 routing_key: Option<coven_protocol::circle::RowRoutingKey>,
732 receiver_wall_ms: u64,
733 ) -> Result<crate::MaterializationOutcome, DbError> {
734 let applied = self
735 .call_store(move |session| {
736 session.apply_received_merge_materialization(
737 materialization,
738 retractions,
739 local_store_membership,
740 routing_key,
741 receiver_wall_ms,
742 )
743 })
744 .await?;
745 for (write_id, status) in applied.write_status_notifications {
746 self.notify_write_status(write_id, status);
747 }
748 Ok(applied.outcome)
749 }
750
751 #[allow(clippy::too_many_arguments)]
752 pub async fn materialize_published_store_operation(
753 &self,
754 root: coven_protocol::store_commit::StoreRootRef,
755 verified_commit: VerifiedStoreBatchCommit,
756 registrations: Vec<ActivatedStoreDeviceRegistration>,
757 device_operations: VerifiedStoreDeviceOperations,
758 circle_activations: VerifiedCircleActivations,
759 activation_head: StoreDeviceHead,
760 activation_head_object: ExactObjectRef,
761 history_evidence: coven_protocol::store_commit::RetainedMergeCommitEvidence,
762 membership_objects: Option<crate::VerifiedMergeMembershipObjects>,
763 operation_object_ids: Option<Vec<coven_protocol::store_commit::ObjectHash>>,
764 membership_completion: Option<
765 coven_protocol::membership_mutation::StoreMembershipJournalCompletion,
766 >,
767 ) -> Result<(), DbError> {
768 self.call_store(move |session| {
769 session.materialize_published_store_operation(
770 root,
771 verified_commit,
772 registrations,
773 device_operations,
774 circle_activations,
775 activation_head,
776 activation_head_object,
777 history_evidence,
778 membership_objects,
779 operation_object_ids,
780 membership_completion,
781 )
782 })
783 .await
784 }
785
786 pub async fn materialize_device_join_activation(
787 &self,
788 root: coven_protocol::store_commit::StoreRootRef,
789 verified_commit: VerifiedStoreBatchCommit,
790 registrations: Vec<ActivatedStoreDeviceRegistration>,
791 device_operations: VerifiedStoreDeviceOperations,
792 activation_head: StoreDeviceHead,
793 activation_head_object: ExactObjectRef,
794 history_evidence: coven_protocol::store_commit::RetainedMergeCommitEvidence,
795 ) -> Result<(), DbError> {
796 self.call_store(move |session| {
797 session.materialize_device_join_activation(
798 root,
799 verified_commit,
800 registrations,
801 device_operations,
802 activation_head,
803 activation_head_object,
804 history_evidence,
805 )
806 })
807 .await
808 }
809
810 pub async fn unrepresented_device_join_bootstrap_commits(
813 &self,
814 plan: crate::DeviceJoinBootstrapPlan,
815 ) -> Result<
816 (
817 crate::DeviceJoinBootstrapPlan,
818 Vec<coven_protocol::store_commit::StoreBatchCommitRef>,
819 ),
820 DbError,
821 > {
822 self.call_store(move |session| session.unrepresented_device_join_bootstrap_commits(plan))
823 .await
824 }
825
826 pub async fn install_device_join_bootstrap(
827 &self,
828 root: coven_protocol::store_commit::StoreRootRef,
829 resolved: crate::ResolvedDeviceJoinBootstrap,
830 ) -> Result<(), DbError> {
831 self.call_store(move |session| session.install_device_join_bootstrap(root, resolved))
832 .await
833 }
834
835 pub async fn complete_owner_recovery(
836 &self,
837 verified_commit: VerifiedStoreBatchCommit,
838 activation_head: StoreDeviceHead,
839 activation_head_object: ExactObjectRef,
840 history_evidence: coven_protocol::store_commit::RetainedMergeCommitEvidence,
841 registration: ActivatedStoreDeviceRegistration,
842 ) -> Result<(), DbError> {
843 self.call_store(move |session| {
844 session.complete_owner_recovery(
845 verified_commit,
846 activation_head,
847 activation_head_object,
848 history_evidence,
849 registration,
850 )
851 })
852 .await
853 }
854}