Skip to main content

coven_protocol/remote_object/
pending_release.rs

1use super::*;
2use crate::blob::locator::{BlobLocator, StoredBlobRef};
3
4/// The ownership change for a candidate whose operation has completed.
5/// The operation retains its original claims until any returned target has been
6/// deleted, then persists this disposition atomically with its completion.
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub enum PendingCandidateRelease {
9    Retained(RemoteObjectRecord),
10    DeleteProtocol(ExactObjectRef),
11    DeleteBlob(StoredBlobRef),
12}
13
14impl RemoteObjectRecord {
15    /// Remove a candidate's pending claim without asserting whether
16    /// that exact candidate was accepted. The caller must hold the durable
17    /// operation's completion or discard proof and select its exact manifest.
18    /// This calculation neither changes the original row nor authorizes a new
19    /// publication; the operation owns deletion and the final atomic row update.
20    pub fn release_pending_candidate(
21        mut self,
22        candidate: &StoreBatchCommitRef,
23    ) -> Result<PendingCandidateRelease, RemoteObjectRecordError> {
24        self.validate()?;
25        let retain = match &mut self {
26            Self::CandidateCommit(record) => {
27                if record.identity != *candidate {
28                    return Err(RemoteObjectRecordError::CandidateOwnerMismatch);
29                }
30                match record.state {
31                    CandidateCommitState::Prepared | CandidateCommitState::UploadedVerified => {
32                        false
33                    }
34                    CandidateCommitState::CleanupPending { .. }
35                    | CandidateCommitState::AbsentVerified { .. } => {
36                        return Err(RemoteObjectRecordError::InvalidCleanupTransition);
37                    }
38                }
39            }
40            Self::CandidateExclusive(record) => {
41                if !matches!(
42                    record.identity.domain,
43                    CandidateExclusiveObjectDomain::StorePackage { .. }
44                        | CandidateExclusiveObjectDomain::CirclePackage { .. }
45                ) {
46                    return Err(RemoteObjectRecordError::DomainMismatch);
47                }
48                match &mut record.state {
49                    CandidateObjectState::Prepared { ownership }
50                    | CandidateObjectState::UploadedVerified { ownership } => {
51                        release_pending_owner(ownership, candidate)?
52                    }
53                    CandidateObjectState::CleanupPending { .. }
54                    | CandidateObjectState::AbsentVerified { .. } => {
55                        return Err(RemoteObjectRecordError::InvalidCleanupTransition);
56                    }
57                }
58            }
59            Self::RetainedAuthority(record) => {
60                let RetainedAuthorityObjectDomain::Commit { reference } = &record.identity.domain
61                else {
62                    return Err(RemoteObjectRecordError::DomainMismatch);
63                };
64                if reference != candidate {
65                    return Err(RemoteObjectRecordError::CandidateOwnerMismatch);
66                }
67                match &record.state {
68                    RetainedAuthorityObjectState::UploadedVerified { ownership }
69                        if ownership.activated.contains(candidate) =>
70                    {
71                        true
72                    }
73                    _ => return Err(RemoteObjectRecordError::InvalidActivation),
74                }
75            }
76            Self::SharedLiveSet(record) => {
77                if !matches!(
78                    record.identity.domain,
79                    SharedLiveSetObjectDomain::StoredBlob
80                        | SharedLiveSetObjectDomain::StorePackage { .. }
81                        | SharedLiveSetObjectDomain::CirclePackage { .. }
82                ) {
83                    return Err(RemoteObjectRecordError::DomainMismatch);
84                }
85                match &mut record.state {
86                    OwnedObjectState::Prepared { ownership } => {
87                        release_pending_owner(ownership, candidate)?
88                    }
89                    OwnedObjectState::UploadedVerified { ownership } => {
90                        if !ownership.pending.remove(candidate)
91                            && !ownership
92                                .activated
93                                .contains(&SharedObjectOwner::StoreCommit(candidate.clone()))
94                        {
95                            return Err(RemoteObjectRecordError::CandidateOwnerMismatch);
96                        }
97                        !ownership.pending.is_empty() || !ownership.activated.is_empty()
98                    }
99                    OwnedObjectState::RetirementPending { .. } => {
100                        return Err(RemoteObjectRecordError::InvalidCleanupTransition);
101                    }
102                }
103            }
104        };
105        if retain {
106            self.validate()?;
107            return Ok(PendingCandidateRelease::Retained(self));
108        }
109        if let Self::SharedLiveSet(record) = &self {
110            if matches!(
111                record.identity.domain,
112                SharedLiveSetObjectDomain::StoredBlob
113            ) {
114                let bytes = record
115                    .payloads
116                    .carried_locator_bytes()
117                    .ok_or(RemoteObjectRecordError::PayloadPlacement)?;
118                return Ok(PendingCandidateRelease::DeleteBlob(StoredBlobRef::new(
119                    BlobLocator::parse(bytes)?,
120                    record.identity.object.clone(),
121                )?));
122            }
123        }
124        Ok(PendingCandidateRelease::DeleteProtocol(
125            self.object().clone(),
126        ))
127    }
128}
129
130fn release_pending_owner(
131    ownership: &mut PendingCandidateOwnership,
132    candidate: &StoreBatchCommitRef,
133) -> Result<bool, RemoteObjectRecordError> {
134    if !ownership.pending.remove(candidate) {
135        return Err(RemoteObjectRecordError::CandidateOwnerMismatch);
136    }
137    Ok(!ownership.pending.is_empty())
138}