1use super::*;
2use crate::store::StoreSession;
3
4impl StoreSession<'_> {
5 fn mark_candidate_cleanup_absent(&mut self, object: ExactObjectRef) -> Result<(), DbError> {
6 let conn = self.conn;
7 let object_id = remote_object_id(&object);
8 let mut remote = load_remote_object_on(conn, object_id)?;
9 if remote.cleanup_target() != Some(&object) {
10 return Err(DbError::Message(format!(
11 "remote object {object_id} is not awaiting exact cleanup"
12 )));
13 }
14 remote.mark_absent_verified().map_err(|error| {
15 DbError::context(format!("mark candidate {object_id} absent"), error)
16 })?;
17 update_remote_object_on(conn, object_id, &remote)
18 }
19
20 fn blocked_merge_candidate(
21 &mut self,
22 write_id: WriteId,
23 ) -> Result<Option<BlockedMergeCandidate>, DbError> {
24 let records = crate::store::store_session::StoreRecords::new(self.conn, self.store_dir);
25 let verified_authority = &mut *self.verified_store_authority;
26 let conn = self.conn;
27 let row: Option<(String, Option<String>)> = conn
28 .query_row(
29 "SELECT status, prepared FROM store_writes WHERE write_id = ?1",
30 [write_id.as_str()],
31 |row| Ok((row.get(0)?, row.get(1)?)),
32 )
33 .optional()
34 .map_err(DbError::from)?;
35 let Some((raw_status, raw_prepared)) = row else {
36 return Err(DbError::Message(format!("write {write_id} is absent")));
37 };
38 let status: WriteStatus = serde_json::from_str(&raw_status)
39 .map_err(|error| DbError::context("blocked Merge candidate status", error))?;
40 if !matches!(status, WriteStatus::Blocked(_)) {
41 return Err(DbError::Message(format!("write {write_id} is not blocked")));
42 }
43 let Some(raw_prepared) = raw_prepared else {
44 return Ok(None);
45 };
46 let prepared: PreparedStoreWriteState = serde_json::from_str(&raw_prepared)
47 .map_err(|error| DbError::context("blocked Merge candidate preparation", error))?;
48 let PreparedStoreWriteState::Publication { .. } = &prepared else {
49 return Ok(None);
50 };
51 let candidate = parse_prepared_merge_candidate_on(records, verified_authority, &prepared)?;
52 if candidate.commit.write_id != write_id {
53 return Err(DbError::Message(
54 "blocked Merge candidate differs from its write identity".to_string(),
55 ));
56 }
57 Ok(Some(BlockedMergeCandidate {
58 commit: candidate.commit,
59 commit_bytes: candidate.canonical_signed_bytes,
60 commit_object: candidate.reference.object.clone(),
61 head: candidate.head,
62 head_object: candidate.head_object,
63 }))
64 }
65
66 fn prepared_merge_abandonment_candidates(
67 &mut self,
68 write_id: WriteId,
69 ) -> Result<Option<PreparedMergeAbandonmentCandidates>, DbError> {
70 let records = crate::store::store_session::StoreRecords::new(self.conn, self.store_dir);
71 let verified_authority = &mut *self.verified_store_authority;
72 let conn = self.conn;
73 let raw_prepared: Option<String> = conn
74 .query_row(
75 "SELECT prepared FROM store_writes WHERE write_id = ?1",
76 [write_id.as_str()],
77 |row| row.get(0),
78 )
79 .map_err(DbError::from)?;
80 let Some(raw_prepared) = raw_prepared else {
81 return Ok(None);
82 };
83 let prepared: PreparedStoreWriteState = serde_json::from_str(&raw_prepared)
84 .map_err(|error| DbError::context("prepared Merge abandonment", error))?;
85 let PreparedStoreWriteState::MergeAbandonment {
86 candidate_commit,
87 candidate_head,
88 authority_commit,
89 authority_head,
90 outcome: MergeAbandonmentOutcome::Prepared,
91 ..
92 } = &prepared
93 else {
94 return Ok(None);
95 };
96 Ok(Some(PreparedMergeAbandonmentCandidates {
97 candidate: blocked_merge_candidate_from_prepared(
98 parse_prepared_merge_candidate_parts_on(
99 records,
100 verified_authority,
101 candidate_commit.semantic_bytes(),
102 candidate_commit.prepared().reference(),
103 candidate_head.semantic_bytes(),
104 candidate_head.prepared().reference(),
105 )?,
106 ),
107 authority: blocked_merge_candidate_from_prepared(
108 parse_prepared_merge_candidate_parts_on(
109 records,
110 verified_authority,
111 authority_commit.semantic_bytes(),
112 authority_commit.prepared().reference(),
113 authority_head.semantic_bytes(),
114 authority_head.prepared().reference(),
115 )?,
116 ),
117 }))
118 }
119
120 fn author_exclusion_activation_for_candidate(
121 &mut self,
122 root: coven_protocol::store_commit::StoreRootRef,
123 candidate: StoreBatchCommitRef,
124 author: StoreDeviceRegistrationRef,
125 ) -> Result<Option<AuthorExclusionActivationLocator>, DbError> {
126 let records = crate::store::store_session::StoreRecords::new(self.conn, self.store_dir);
127 author_exclusion_activation_for_candidate_on(
128 records,
129 self.verified_store_authority,
130 &root,
131 &candidate,
132 &author,
133 )
134 }
135
136 fn begin_blocked_merge_candidate_nonactivation(
137 &mut self,
138 root: coven_protocol::store_commit::StoreRootRef,
139 write_id: WriteId,
140 nonactivation: BlockedMergeCandidateNonactivation,
141 ) -> Result<(), DbError> {
142 let verified_authority = &mut *self.verified_store_authority;
143 let conn = self.conn;
144 let tx = conn.unchecked_transaction().map_err(DbError::from)?;
145 let (raw_status, raw_prepared): (String, String) = tx
146 .query_row(
147 "SELECT status, prepared FROM store_writes WHERE write_id = ?1",
148 [write_id.as_str()],
149 |row| Ok((row.get(0)?, row.get(1)?)),
150 )
151 .map_err(DbError::from)?;
152 let status: WriteStatus = serde_json::from_str(&raw_status)
153 .map_err(|error| DbError::context("blocked Merge candidate status", error))?;
154 if !matches!(status, WriteStatus::Blocked(_)) {
155 return Err(DbError::Message(format!(
156 "Merge candidate {write_id} is not blocked"
157 )));
158 }
159 let prepared: PreparedStoreWriteState = serde_json::from_str(&raw_prepared)
160 .map_err(|error| DbError::context("blocked Merge candidate preparation", error))?;
161 let PreparedStoreWriteState::Publication { .. } = &prepared else {
162 return Err(DbError::Message(
163 "author exclusion reached a non-candidate Merge preparation".to_string(),
164 ));
165 };
166 let store_transaction =
167 crate::store::store_session::StoreTransaction::new(&tx, self.store_dir);
168 let candidate =
169 store_transaction.prepared_merge_candidate(verified_authority, &prepared)?;
170 store_transaction.begin_blocked_merge_candidate_nonactivation(
171 verified_authority,
172 &root,
173 &write_id,
174 &candidate,
175 &nonactivation,
176 true,
177 &[],
178 )?;
179 tx.commit().map_err(DbError::from)
180 }
181
182 fn begin_prepared_merge_abandonment_nonactivation(
183 &mut self,
184 root: coven_protocol::store_commit::StoreRootRef,
185 write_id: WriteId,
186 candidate_nonactivation: BlockedMergeCandidateNonactivation,
187 authority_nonactivation: BlockedMergeCandidateNonactivation,
188 ) -> Result<WriteStatus, DbError> {
189 let verified_authority = &mut *self.verified_store_authority;
190 let conn = self.conn;
191 let tx = conn.unchecked_transaction().map_err(DbError::from)?;
192 let (raw_status, raw_prepared): (String, String) = tx
193 .query_row(
194 "SELECT status, prepared FROM store_writes WHERE write_id = ?1",
195 [write_id.as_str()],
196 |row| Ok((row.get(0)?, row.get(1)?)),
197 )
198 .map_err(DbError::from)?;
199 let status: WriteStatus = serde_json::from_str(&raw_status)
200 .map_err(|error| DbError::context("blocked Merge abandonment status", error))?;
201 if !matches!(status, WriteStatus::Publishing | WriteStatus::Blocked(_)) {
202 return Err(DbError::Message(
203 "Merge abandonment candidates are not publishing or blocked".to_string(),
204 ));
205 }
206 let mut prepared: PreparedStoreWriteState = serde_json::from_str(&raw_prepared)
207 .map_err(|error| DbError::context("blocked Merge abandonment preparation", error))?;
208 let PreparedStoreWriteState::MergeAbandonment {
209 candidate_commit,
210 candidate_head,
211 authority_commit,
212 authority_head,
213 outcome,
214 ..
215 } = &mut prepared
216 else {
217 return Err(DbError::Message(
218 "author exclusion reached a non-abandonment Merge preparation".to_string(),
219 ));
220 };
221 if !matches!(outcome, MergeAbandonmentOutcome::Prepared) {
222 return Err(DbError::Message(
223 "Merge abandonment already has a publication outcome".to_string(),
224 ));
225 }
226 let store_transaction =
227 crate::store::store_session::StoreTransaction::new(&tx, self.store_dir);
228 let candidate = store_transaction.prepared_merge_candidate_parts(
229 verified_authority,
230 candidate_commit.semantic_bytes(),
231 candidate_commit.prepared().reference(),
232 candidate_head.semantic_bytes(),
233 candidate_head.prepared().reference(),
234 )?;
235 let authority = store_transaction.prepared_merge_candidate_parts(
236 verified_authority,
237 authority_commit.semantic_bytes(),
238 authority_commit.prepared().reference(),
239 authority_head.semantic_bytes(),
240 authority_head.prepared().reference(),
241 )?;
242 store_transaction.begin_blocked_merge_candidate_nonactivation(
243 verified_authority,
244 &root,
245 &write_id,
246 &candidate,
247 &candidate_nonactivation,
248 true,
249 &[],
250 )?;
251 store_transaction.begin_blocked_merge_candidate_nonactivation(
252 verified_authority,
253 &root,
254 &write_id,
255 &authority,
256 &authority_nonactivation,
257 false,
258 &[],
259 )?;
260 *outcome = MergeAbandonmentOutcome::AuthorExcluded;
261 let replacement = serde_json::to_string(&prepared).map_err(|error| {
262 DbError::context("serialize excluded Merge abandonment preparation", error)
263 })?;
264 let updated = tx
265 .execute(
266 "UPDATE store_writes SET prepared = ?2
267 WHERE write_id = ?1 AND status = ?3 AND prepared = ?4",
268 rusqlite::params![write_id.as_str(), replacement, raw_status, raw_prepared],
269 )
270 .map_err(DbError::from)?;
271 if updated != 1 {
272 return Err(DbError::Message(
273 "Merge abandonment changed during author-exclusion transition".to_string(),
274 ));
275 }
276 let device_id = candidate.commit.author_registration.device_id.to_string();
277 let blocked =
278 WriteStatus::Blocked(coven_protocol::write::WriteBlock::InvalidProtocolState {
279 reason: format!(
280 "Store author {device_id} was excluded before candidate activation"
281 ),
282 });
283 Database::set_write_status_on(&tx, &write_id, &blocked)?;
284 tx.commit().map_err(DbError::from)?;
285 Ok(blocked)
286 }
287
288 fn merge_abandonment_state(
289 &mut self,
290 write_id: WriteId,
291 ) -> Result<MergeAbandonmentState, DbError> {
292 let records = crate::store::store_session::StoreRecords::new(self.conn, self.store_dir);
293 let verified_authority = &mut *self.verified_store_authority;
294 let conn = self.conn;
295 let raw_prepared: Option<String> = conn
296 .query_row(
297 "SELECT prepared FROM store_writes WHERE write_id = ?1",
298 [write_id.as_str()],
299 |row| row.get(0),
300 )
301 .map_err(DbError::from)?;
302 let Some(raw_prepared) = raw_prepared else {
303 return Ok(MergeAbandonmentState::None);
304 };
305 let prepared: PreparedStoreWriteState = serde_json::from_str(&raw_prepared)
306 .map_err(|error| DbError::context("prepared Merge abandonment", error))?;
307 let PreparedStoreWriteState::MergeAbandonment {
308 candidate_commit,
309 candidate_head,
310 outcome,
311 ..
312 } = &prepared
313 else {
314 return Ok(MergeAbandonmentState::None);
315 };
316 let candidate = parse_prepared_merge_candidate_parts_on(
317 records,
318 verified_authority,
319 candidate_commit.semantic_bytes(),
320 candidate_commit.prepared().reference(),
321 candidate_head.semantic_bytes(),
322 candidate_head.prepared().reference(),
323 )?;
324 Ok(match outcome {
325 MergeAbandonmentOutcome::Prepared => MergeAbandonmentState::Prepared,
326 MergeAbandonmentOutcome::Accepted { .. } => MergeAbandonmentState::Accepted,
327 MergeAbandonmentOutcome::Lost { winner_commit, .. }
328 if winner_commit == &candidate.reference =>
329 {
330 MergeAbandonmentState::CandidateWon
331 }
332 MergeAbandonmentOutcome::Lost { .. } => MergeAbandonmentState::OtherWon,
333 MergeAbandonmentOutcome::AuthorExcluded => MergeAbandonmentState::AuthorExcluded,
334 })
335 }
336
337 fn resume_winning_merge_candidate(&mut self, write_id: WriteId) -> Result<(), DbError> {
338 let verified_authority = &mut *self.verified_store_authority;
339 let conn = self.conn;
340 let tx = conn.unchecked_transaction().map_err(DbError::from)?;
341 let (raw_status, raw_prepared): (String, String) = tx
342 .query_row(
343 "SELECT status, prepared FROM store_writes WHERE write_id = ?1",
344 [write_id.as_str()],
345 |row| Ok((row.get(0)?, row.get(1)?)),
346 )
347 .map_err(DbError::from)?;
348 let status: WriteStatus = serde_json::from_str(&raw_status)
349 .map_err(|error| DbError::context("winning Merge candidate status", error))?;
350 if !matches!(status, WriteStatus::Blocked(_)) {
351 return Err(DbError::Message(
352 "winning Merge candidate is not blocked".to_string(),
353 ));
354 }
355 let prepared: PreparedStoreWriteState = serde_json::from_str(&raw_prepared)
356 .map_err(|error| DbError::context("winning Merge candidate preparation", error))?;
357 let PreparedStoreWriteState::MergeAbandonment {
358 candidate_commit,
359 candidate_head,
360 candidate_history_evidence,
361 authority_commit,
362 authority_head,
363 authority_history_evidence: _,
364 outcome: MergeAbandonmentOutcome::Lost { winner_commit, .. },
365 local_cleanup,
366 completion,
367 } = prepared
368 else {
369 return Err(DbError::Message(
370 "Merge abandonment did not lose to its prepared candidate".to_string(),
371 ));
372 };
373 let store_transaction =
374 crate::store::store_session::StoreTransaction::new(&tx, self.store_dir);
375 let candidate = store_transaction.prepared_merge_candidate_parts(
376 verified_authority,
377 candidate_commit.semantic_bytes(),
378 candidate_commit.prepared().reference(),
379 candidate_head.semantic_bytes(),
380 candidate_head.prepared().reference(),
381 )?;
382 if winner_commit != candidate.reference {
383 return Err(DbError::Message(
384 "Merge abandonment winner is another candidate".to_string(),
385 ));
386 }
387 let authority = store_transaction.prepared_merge_candidate_parts(
388 verified_authority,
389 authority_commit.semantic_bytes(),
390 authority_commit.prepared().reference(),
391 authority_head.semantic_bytes(),
392 authority_head.prepared().reference(),
393 )?;
394 remove_cleaned_merge_authority_on(&tx, &authority)?;
395 let replacement = PreparedStoreWriteState::Publication {
396 commit: candidate_commit,
397 head: candidate_head,
398 history_evidence: candidate_history_evidence,
399 local_cleanup,
400 completion,
401 };
402 let replacement = serde_json::to_string(&replacement)
403 .map_err(|error| DbError::context("serialize winning Merge candidate", error))?;
404 let publishing = serde_json::to_string(&WriteStatus::Publishing)
405 .map_err(|error| DbError::context("serialize winning Merge status", error))?;
406 let updated = tx
407 .execute(
408 "UPDATE store_writes SET prepared = ?2, status = ?3
409 WHERE write_id = ?1 AND prepared = ?4",
410 rusqlite::params![write_id.as_str(), replacement, publishing, raw_prepared],
411 )
412 .map_err(DbError::from)?;
413 if updated != 1 {
414 return Err(DbError::Message(
415 "Merge abandonment changed while restoring its winner".to_string(),
416 ));
417 }
418 tx.commit().map_err(DbError::from)?;
419 Ok(())
420 }
421
422 fn finish_lost_merge_abandonment(&mut self, write_id: WriteId) -> Result<(), DbError> {
423 let verified_authority = &mut *self.verified_store_authority;
424 let conn = self.conn;
425 let tx = conn.unchecked_transaction().map_err(DbError::from)?;
426 let (raw_status, raw_prepared): (String, String) = tx
427 .query_row(
428 "SELECT status, prepared FROM store_writes WHERE write_id = ?1",
429 [write_id.as_str()],
430 |row| Ok((row.get(0)?, row.get(1)?)),
431 )
432 .map_err(DbError::from)?;
433 let status: WriteStatus = serde_json::from_str(&raw_status)
434 .map_err(|error| DbError::context("lost Merge abandonment status", error))?;
435 if !matches!(status, WriteStatus::Blocked(_)) {
436 return Err(DbError::Message(
437 "lost Merge abandonment is not blocked".to_string(),
438 ));
439 }
440 let prepared: PreparedStoreWriteState = serde_json::from_str(&raw_prepared)
441 .map_err(|error| DbError::context("lost Merge abandonment preparation", error))?;
442 let PreparedStoreWriteState::MergeAbandonment {
443 candidate_commit,
444 candidate_head,
445 candidate_history_evidence,
446 authority_commit,
447 authority_head,
448 authority_history_evidence: _,
449 outcome: MergeAbandonmentOutcome::Lost { winner_commit, .. },
450 local_cleanup,
451 completion,
452 } = prepared
453 else {
454 return Err(DbError::Message(
455 "Merge abandonment has no third-candidate winner".to_string(),
456 ));
457 };
458 let store_transaction =
459 crate::store::store_session::StoreTransaction::new(&tx, self.store_dir);
460 let candidate = store_transaction.prepared_merge_candidate_parts(
461 verified_authority,
462 candidate_commit.semantic_bytes(),
463 candidate_commit.prepared().reference(),
464 candidate_head.semantic_bytes(),
465 candidate_head.prepared().reference(),
466 )?;
467 if winner_commit == candidate.reference {
468 return Err(DbError::Message(
469 "original candidate won the Merge abandonment race".to_string(),
470 ));
471 }
472 let authority = store_transaction.prepared_merge_candidate_parts(
473 verified_authority,
474 authority_commit.semantic_bytes(),
475 authority_commit.prepared().reference(),
476 authority_head.semantic_bytes(),
477 authority_head.prepared().reference(),
478 )?;
479 remove_cleaned_merge_authority_on(&tx, &authority)?;
480 let replacement = PreparedStoreWriteState::Publication {
481 commit: candidate_commit,
482 head: candidate_head,
483 history_evidence: candidate_history_evidence,
484 local_cleanup,
485 completion,
486 };
487 let replacement = serde_json::to_string(&replacement)
488 .map_err(|error| DbError::context("serialize lost Merge candidate", error))?;
489 let updated = tx
490 .execute(
491 "UPDATE store_writes SET prepared = ?2
492 WHERE write_id = ?1 AND status = ?3 AND prepared = ?4",
493 rusqlite::params![write_id.as_str(), replacement, raw_status, raw_prepared],
494 )
495 .map_err(DbError::from)?;
496 if updated != 1 {
497 return Err(DbError::Message(
498 "Merge abandonment changed while removing its losing authority".to_string(),
499 ));
500 }
501 tx.commit().map_err(DbError::from)
502 }
503
504 fn finish_author_excluded_merge_abandonment(
505 &mut self,
506 write_id: WriteId,
507 ) -> Result<(), DbError> {
508 let verified_authority = &mut *self.verified_store_authority;
509 let conn = self.conn;
510 let tx = conn.unchecked_transaction().map_err(DbError::from)?;
511 let (raw_status, raw_prepared): (String, String) = tx
512 .query_row(
513 "SELECT status, prepared FROM store_writes WHERE write_id = ?1",
514 [write_id.as_str()],
515 |row| Ok((row.get(0)?, row.get(1)?)),
516 )
517 .map_err(DbError::from)?;
518 let status: WriteStatus = serde_json::from_str(&raw_status)
519 .map_err(|error| DbError::context("excluded Merge abandonment status", error))?;
520 if !matches!(status, WriteStatus::Blocked(_)) {
521 return Err(DbError::Message(
522 "excluded Merge abandonment is not blocked".to_string(),
523 ));
524 }
525 let prepared: PreparedStoreWriteState = serde_json::from_str(&raw_prepared)
526 .map_err(|error| DbError::context("excluded Merge abandonment preparation", error))?;
527 let PreparedStoreWriteState::MergeAbandonment {
528 candidate_commit,
529 candidate_head,
530 candidate_history_evidence,
531 authority_commit,
532 authority_head,
533 authority_history_evidence: _,
534 outcome: MergeAbandonmentOutcome::AuthorExcluded,
535 local_cleanup,
536 completion,
537 } = prepared
538 else {
539 return Err(DbError::Message(
540 "Merge abandonment has no author-exclusion outcome".to_string(),
541 ));
542 };
543 let store_transaction =
544 crate::store::store_session::StoreTransaction::new(&tx, self.store_dir);
545 let candidate = store_transaction.prepared_merge_candidate_parts(
546 verified_authority,
547 candidate_commit.semantic_bytes(),
548 candidate_commit.prepared().reference(),
549 candidate_head.semantic_bytes(),
550 candidate_head.prepared().reference(),
551 )?;
552 let authority = store_transaction.prepared_merge_candidate_parts(
553 verified_authority,
554 authority_commit.semantic_bytes(),
555 authority_commit.prepared().reference(),
556 authority_head.semantic_bytes(),
557 authority_head.prepared().reference(),
558 )?;
559 if !merge_candidate_cleanup_targets_on(&tx, &write_id, &candidate, true, &[])?.is_empty()
560 || !merge_candidate_cleanup_targets_on(&tx, &write_id, &authority, false, &[])?
561 .is_empty()
562 {
563 return Err(DbError::Message(
564 "excluded Merge abandonment cleanup is incomplete".to_string(),
565 ));
566 }
567 remove_cleaned_author_excluded_merge_authority_on(&tx, &authority)?;
568 let replacement = PreparedStoreWriteState::Publication {
569 commit: candidate_commit,
570 head: candidate_head,
571 history_evidence: candidate_history_evidence,
572 local_cleanup,
573 completion,
574 };
575 let replacement = serde_json::to_string(&replacement).map_err(|error| {
576 DbError::context("serialize cleaned excluded Merge abandonment", error)
577 })?;
578 let updated = tx
579 .execute(
580 "UPDATE store_writes SET prepared = ?2
581 WHERE write_id = ?1 AND status = ?3 AND prepared = ?4",
582 rusqlite::params![write_id.as_str(), replacement, raw_status, raw_prepared],
583 )
584 .map_err(DbError::from)?;
585 if updated != 1 {
586 return Err(DbError::Message(
587 "excluded Merge abandonment changed during cleanup".to_string(),
588 ));
589 }
590 tx.commit().map_err(DbError::from)
591 }
592}
593
594impl StoreDatabase {
595 pub async fn mark_candidate_cleanup_absent(
596 &self,
597 object: ExactObjectRef,
598 ) -> Result<(), DbError> {
599 self.call_store(move |session| session.mark_candidate_cleanup_absent(object))
600 .await
601 }
602
603 pub async fn blocked_merge_candidate(
604 &self,
605 write_id: WriteId,
606 ) -> Result<Option<BlockedMergeCandidate>, DbError> {
607 self.call_store(move |session| session.blocked_merge_candidate(write_id))
608 .await
609 }
610
611 pub async fn prepared_merge_abandonment_candidates(
612 &self,
613 write_id: WriteId,
614 ) -> Result<Option<PreparedMergeAbandonmentCandidates>, DbError> {
615 self.call_store(move |session| session.prepared_merge_abandonment_candidates(write_id))
616 .await
617 }
618
619 pub async fn author_exclusion_activation_for_candidate(
620 &self,
621 root: coven_protocol::store_commit::StoreRootRef,
622 candidate: StoreBatchCommitRef,
623 author: StoreDeviceRegistrationRef,
624 ) -> Result<Option<AuthorExclusionActivationLocator>, DbError> {
625 self.call_store(move |session| {
626 session.author_exclusion_activation_for_candidate(root, candidate, author)
627 })
628 .await
629 }
630
631 pub async fn begin_blocked_merge_candidate_nonactivation(
632 &self,
633 root: coven_protocol::store_commit::StoreRootRef,
634 write_id: WriteId,
635 nonactivation: coven_protocol::remote_object::VerifiedCandidateNonactivation,
636 ) -> Result<(), DbError> {
637 let nonactivation = blocked_merge_candidate_nonactivation(nonactivation)?;
638 self.call_store(move |session| {
639 session.begin_blocked_merge_candidate_nonactivation(root, write_id, nonactivation)
640 })
641 .await
642 }
643
644 pub async fn begin_prepared_merge_abandonment_nonactivation(
645 &self,
646 root: coven_protocol::store_commit::StoreRootRef,
647 write_id: WriteId,
648 candidate_nonactivation: coven_protocol::remote_object::VerifiedCandidateNonactivation,
649 authority_nonactivation: coven_protocol::remote_object::VerifiedCandidateNonactivation,
650 ) -> Result<(), DbError> {
651 let candidate_nonactivation =
652 blocked_merge_candidate_nonactivation(candidate_nonactivation)?;
653 let authority_nonactivation =
654 blocked_merge_candidate_nonactivation(authority_nonactivation)?;
655 let notified_write_id = write_id.clone();
656 let blocked = self
657 .call_store(move |session| {
658 session.begin_prepared_merge_abandonment_nonactivation(
659 root,
660 write_id,
661 candidate_nonactivation,
662 authority_nonactivation,
663 )
664 })
665 .await?;
666 self.notify_write_status(notified_write_id, blocked);
667 Ok(())
668 }
669
670 pub async fn merge_abandonment_state(
671 &self,
672 write_id: &WriteId,
673 ) -> Result<MergeAbandonmentState, DbError> {
674 let write_id = write_id.clone();
675 self.call_store(move |session| session.merge_abandonment_state(write_id))
676 .await
677 }
678
679 pub async fn resume_winning_merge_candidate(&self, write_id: WriteId) -> Result<(), DbError> {
680 let notified_write_id = write_id.clone();
681 self.call_store(move |session| session.resume_winning_merge_candidate(write_id))
682 .await?;
683 self.notify_write_status(notified_write_id, WriteStatus::Publishing);
684 Ok(())
685 }
686
687 pub async fn finish_lost_merge_abandonment(&self, write_id: WriteId) -> Result<(), DbError> {
688 self.call_store(move |session| session.finish_lost_merge_abandonment(write_id))
689 .await
690 }
691
692 pub async fn finish_author_excluded_merge_abandonment(
693 &self,
694 write_id: WriteId,
695 ) -> Result<(), DbError> {
696 self.call_store(move |session| session.finish_author_excluded_merge_abandonment(write_id))
697 .await
698 }
699}