Skip to main content

coven_protocol/remote_object/
lifecycle.rs

1use super::identity::*;
2use super::nonactivation::*;
3use super::ownership::*;
4use super::*;
5
6impl RemoteObjectRecord {
7    /// Everything this record asserts about itself that does not need its
8    /// payloads: where those payloads live, that the identity is the one the
9    /// record is filed under, and that its ownership state holds together.
10    ///
11    /// Byte agreement is [`Self::validate_payload`]'s job, and it is checked
12    /// where bytes arrive from outside this device's own durable state, rather
13    /// than on every load. Identity and payload cannot drift apart afterwards:
14    /// neither hash mutates across transitions, and the two domain changes that
15    /// do happen re-wrap the same reference.
16    pub fn validate(&self) -> Result<(), RemoteObjectRecordError> {
17        self.validate_payload_placement()?;
18        match self {
19            Self::CandidateCommit(record) => match &record.state {
20                CandidateCommitState::Prepared | CandidateCommitState::UploadedVerified => {}
21                CandidateCommitState::CleanupPending { proof }
22                | CandidateCommitState::AbsentVerified { proof } => {
23                    proof.validate()?;
24                }
25            },
26            Self::CandidateExclusive(record) => {
27                if record.identity.family != record.identity.domain.family()
28                    || record.identity.object != *record.identity.domain.object()
29                {
30                    return Err(RemoteObjectRecordError::StoredReferenceMismatch);
31                }
32                record.state.validate()?;
33            }
34            Self::RetainedAuthority(record) => {
35                record.state.validate()?;
36            }
37            Self::SharedLiveSet(record) => {
38                if let SharedLiveSetObjectDomain::StoredBlob = &record.identity.domain {
39                    let locator_bytes = record
40                        .payloads
41                        .carried_locator_bytes()
42                        .ok_or(RemoteObjectRecordError::PayloadPlacement)?;
43                    validate_semantic_hash(record.identity.semantic_hash, locator_bytes)?;
44                    let locator = crate::blob::locator::BlobLocator::parse(locator_bytes)?;
45                    crate::blob::locator::StoredBlobRef::new(
46                        locator,
47                        record.identity.object.clone(),
48                    )?;
49                }
50                record.state.validate()?;
51            }
52        }
53        Ok(())
54    }
55
56    /// Refuse a record whose payloads sit somewhere its domain cannot put them.
57    ///
58    /// This is what makes the carry-set structural rather than conventional: a
59    /// stored blob's row travels inside published images and carries its
60    /// locator, and no other domain may, because no other domain's payload
61    /// would arrive with the row.
62    fn validate_payload_placement(&self) -> Result<(), RemoteObjectRecordError> {
63        let placed = match self {
64            Self::CandidateCommit(_) => {
65                matches!(self.payloads(), RemoteObjectPayloads::SpooledInline)
66            }
67            Self::CandidateExclusive(record) => match &record.identity.domain {
68                CandidateExclusiveObjectDomain::CircleBootstrapImage { .. } => {
69                    matches!(record.payloads, RemoteObjectPayloads::SpooledExternal)
70                }
71                // A package this device sealed uploads its own ciphertext; one
72                // it observed and activated was sealed elsewhere.
73                CandidateExclusiveObjectDomain::StorePackage { .. }
74                | CandidateExclusiveObjectDomain::CirclePackage { .. } => {
75                    !matches!(record.payloads, RemoteObjectPayloads::RowBlob { .. })
76                }
77                _ => matches!(record.payloads, RemoteObjectPayloads::SpooledInline),
78            },
79            Self::RetainedAuthority(record) => {
80                matches!(record.payloads, RemoteObjectPayloads::SpooledInline)
81            }
82            Self::SharedLiveSet(record) => match &record.identity.domain {
83                SharedLiveSetObjectDomain::StoredBlob => {
84                    matches!(record.payloads, RemoteObjectPayloads::RowBlob { .. })
85                }
86                SharedLiveSetObjectDomain::StoreSnapshotImage { .. }
87                | SharedLiveSetObjectDomain::StoreMembershipRollup { .. }
88                | SharedLiveSetObjectDomain::CircleBootstrapImage { .. } => {
89                    matches!(record.payloads, RemoteObjectPayloads::SpooledExternal)
90                }
91                SharedLiveSetObjectDomain::StorePackage { .. }
92                | SharedLiveSetObjectDomain::CirclePackage { .. } => {
93                    !matches!(record.payloads, RemoteObjectPayloads::RowBlob { .. })
94                }
95            },
96        };
97        if placed {
98            Ok(())
99        } else {
100            Err(RemoteObjectRecordError::PayloadPlacement)
101        }
102    }
103
104    /// Check this record's identity against the plaintext it names — the whole
105    /// domain parse, its signature verifications, and its agreement with the
106    /// reference.
107    ///
108    /// Called where bytes enter from somewhere this device does not already
109    /// trust: a constructor handed the payload, a pull that parsed it off the
110    /// wire. Reading back this device's own durable state does not run it —
111    /// neither loading the row nor reading the spool file the row names, which
112    /// is named for the digest of its own contents and was fixed by this
113    /// record's identity when it was built.
114    pub fn validate_payload(
115        &self,
116        canonical_semantic_bytes: &[u8],
117    ) -> Result<(), RemoteObjectRecordError> {
118        self.validate()?;
119        match self {
120            Self::CandidateCommit(record) => {
121                validate_semantic_hash(record.semantic_hash, canonical_semantic_bytes)?;
122                let commit: crate::store_commit::StoreBatchCommit =
123                    serde_json::from_slice(canonical_semantic_bytes)?;
124                record.identity.verify_commit(&commit)?;
125                match &record.state {
126                    CandidateCommitState::Prepared | CandidateCommitState::UploadedVerified => {}
127                    CandidateCommitState::CleanupPending { proof }
128                    | CandidateCommitState::AbsentVerified { proof } => {
129                        proof.validate_for(&record.identity, &commit)?;
130                    }
131                }
132            }
133            Self::CandidateExclusive(record) => {
134                validate_candidate_exclusive_identity(&record.identity, canonical_semantic_bytes)?;
135            }
136            Self::RetainedAuthority(record) => {
137                validate_retained_authority_identity(&record.identity, canonical_semantic_bytes)?;
138            }
139            Self::SharedLiveSet(record) => {
140                record
141                    .identity
142                    .validate_semantic(canonical_semantic_bytes)?;
143                match &record.identity.domain {
144                    SharedLiveSetObjectDomain::StoredBlob
145                    | SharedLiveSetObjectDomain::StoreSnapshotImage { .. }
146                    | SharedLiveSetObjectDomain::StoreMembershipRollup { .. }
147                    | SharedLiveSetObjectDomain::CircleBootstrapImage { .. } => {}
148                    SharedLiveSetObjectDomain::StorePackage { reference } => {
149                        validate_package_reference(
150                            reference,
151                            None,
152                            canonical_semantic_bytes,
153                            &record.identity.object,
154                        )?;
155                    }
156                    SharedLiveSetObjectDomain::CirclePackage { reference } => {
157                        validate_package_reference(
158                            &reference.package,
159                            Some(reference),
160                            canonical_semantic_bytes,
161                            &record.identity.object,
162                        )?;
163                    }
164                }
165            }
166        }
167        Ok(())
168    }
169
170    pub fn into_activated(
171        self,
172        commit: &StoreBatchCommitRef,
173    ) -> Result<Self, RemoteObjectRecordError> {
174        let activated = match self {
175            Self::CandidateCommit(record) => {
176                if &record.identity != commit
177                    || !matches!(record.state, CandidateCommitState::UploadedVerified)
178                {
179                    return Err(RemoteObjectRecordError::InvalidActivation);
180                }
181                Self::RetainedAuthority(RetainedAuthorityRecord {
182                    identity: RetainedAuthorityObjectRef {
183                        semantic_hash: record.semantic_hash,
184                        object: record.identity.object.clone(),
185                        domain: RetainedAuthorityObjectDomain::Commit {
186                            reference: record.identity,
187                        },
188                    },
189                    payloads: record.payloads,
190                    state: RetainedAuthorityObjectState::UploadedVerified {
191                        ownership: CandidateOwnership {
192                            pending: BTreeSet::new(),
193                            activated: BTreeSet::from([commit.clone()]),
194                            nonactivated: Vec::new(),
195                        },
196                    },
197                })
198            }
199            Self::CandidateExclusive(record) => {
200                let CandidateObjectState::UploadedVerified { ownership } = &record.state else {
201                    return Err(RemoteObjectRecordError::InvalidActivation);
202                };
203                if ownership.pending.len() != 1 || !ownership.pending.contains(commit) {
204                    return Err(RemoteObjectRecordError::InvalidActivation);
205                }
206                if let Some(domain) = record.identity.domain.shared_destination() {
207                    Self::SharedLiveSet(SharedObjectRecord {
208                        identity: SharedLiveSetObjectRef {
209                            domain,
210                            semantic_hash: record.identity.semantic_hash,
211                            object: record.identity.object,
212                        },
213                        payloads: record.payloads,
214                        state: OwnedObjectState::UploadedVerified {
215                            ownership: SharedObjectOwnership {
216                                pending: BTreeSet::new(),
217                                activated: BTreeSet::from([SharedObjectOwner::StoreCommit(
218                                    commit.clone(),
219                                )]),
220                                nonactivated: Vec::new(),
221                            },
222                        },
223                    })
224                } else if let Some(domain) = record.identity.domain.retained_destination() {
225                    Self::RetainedAuthority(RetainedAuthorityRecord {
226                        identity: RetainedAuthorityObjectRef {
227                            domain,
228                            semantic_hash: record.identity.semantic_hash,
229                            object: record.identity.object,
230                        },
231                        payloads: record.payloads,
232                        state: RetainedAuthorityObjectState::UploadedVerified {
233                            ownership: CandidateOwnership {
234                                pending: BTreeSet::new(),
235                                activated: BTreeSet::from([commit.clone()]),
236                                nonactivated: Vec::new(),
237                            },
238                        },
239                    })
240                } else {
241                    return Err(RemoteObjectRecordError::DomainMismatch);
242                }
243            }
244            Self::RetainedAuthority(mut record) => {
245                let RetainedAuthorityObjectState::UploadedVerified { ownership } =
246                    &mut record.state
247                else {
248                    return Err(RemoteObjectRecordError::InvalidActivation);
249                };
250                if ownership.pending.remove(commit) {
251                    ownership.activated.insert(commit.clone());
252                } else if !ownership.activated.contains(commit) {
253                    return Err(RemoteObjectRecordError::InvalidActivation);
254                }
255                Self::RetainedAuthority(record)
256            }
257            Self::SharedLiveSet(mut record) => {
258                match &mut record.state {
259                    OwnedObjectState::UploadedVerified { ownership } => {
260                        if ownership.pending.remove(commit) {
261                            ownership
262                                .activated
263                                .insert(SharedObjectOwner::StoreCommit(commit.clone()));
264                        } else if !ownership
265                            .activated
266                            .contains(&SharedObjectOwner::StoreCommit(commit.clone()))
267                        {
268                            return Err(RemoteObjectRecordError::InvalidActivation);
269                        }
270                    }
271                    OwnedObjectState::Prepared { .. } => {
272                        return Err(RemoteObjectRecordError::InvalidActivation);
273                    }
274                    OwnedObjectState::RetirementPending { .. } => {
275                        return Err(RemoteObjectRecordError::InvalidActivation);
276                    }
277                }
278                Self::SharedLiveSet(record)
279            }
280        };
281        activated.validate()?;
282        Ok(activated)
283    }
284
285    pub fn into_observed_activated(
286        mut self,
287        commit: &StoreBatchCommitRef,
288    ) -> Result<Self, RemoteObjectRecordError> {
289        self.mark_uploaded_verified()?;
290        self.into_activated(commit)
291    }
292
293    /// Whether this device already created these exact bytes at the provider
294    /// and settled the create.
295    ///
296    /// The record is the evidence, so nothing that holds one needs to read the
297    /// object back to know its content: the bytes were hashed locally before
298    /// the upload and the provider's exact-upload verification settled the
299    /// create. Reading it back would test the provider's durability, not this
300    /// device's correctness, and an object that later goes missing surfaces on
301    /// the read that wants it.
302    pub fn records_verified_upload(&self) -> bool {
303        match self {
304            Self::CandidateCommit(record) => {
305                matches!(record.state, CandidateCommitState::UploadedVerified)
306            }
307            Self::CandidateExclusive(record) => {
308                matches!(record.state, CandidateObjectState::UploadedVerified { .. })
309            }
310            Self::RetainedAuthority(record) => matches!(
311                record.state,
312                RetainedAuthorityObjectState::UploadedVerified { .. }
313            ),
314            Self::SharedLiveSet(record) => {
315                matches!(record.state, OwnedObjectState::UploadedVerified { .. })
316            }
317        }
318    }
319
320    pub fn mark_uploaded_verified(&mut self) -> Result<(), RemoteObjectRecordError> {
321        match self {
322            Self::CandidateCommit(record) => match record.state {
323                CandidateCommitState::Prepared => {
324                    record.state = CandidateCommitState::UploadedVerified;
325                }
326                CandidateCommitState::UploadedVerified => {}
327                CandidateCommitState::CleanupPending { .. }
328                | CandidateCommitState::AbsentVerified { .. } => {
329                    return Err(RemoteObjectRecordError::InvalidUploadTransition);
330                }
331            },
332            Self::CandidateExclusive(record) => match &record.state {
333                CandidateObjectState::Prepared { ownership } => {
334                    record.state = CandidateObjectState::UploadedVerified {
335                        ownership: ownership.clone(),
336                    };
337                }
338                CandidateObjectState::UploadedVerified { .. } => {}
339                CandidateObjectState::CleanupPending { .. }
340                | CandidateObjectState::AbsentVerified { .. } => {
341                    return Err(RemoteObjectRecordError::InvalidUploadTransition);
342                }
343            },
344            Self::RetainedAuthority(record) => match &record.state {
345                RetainedAuthorityObjectState::Prepared { ownership } => {
346                    record.state = RetainedAuthorityObjectState::UploadedVerified {
347                        ownership: CandidateOwnership {
348                            pending: ownership.pending.clone(),
349                            activated: BTreeSet::new(),
350                            nonactivated: ownership.nonactivated.clone(),
351                        },
352                    };
353                }
354                RetainedAuthorityObjectState::UploadedVerified { .. } => {}
355            },
356            Self::SharedLiveSet(record) => match &record.state {
357                OwnedObjectState::Prepared { ownership } => {
358                    record.state = OwnedObjectState::UploadedVerified {
359                        ownership: SharedObjectOwnership {
360                            pending: ownership.pending.clone(),
361                            activated: BTreeSet::new(),
362                            nonactivated: ownership.nonactivated.clone(),
363                        },
364                    };
365                }
366                OwnedObjectState::UploadedVerified { .. } => {}
367                OwnedObjectState::RetirementPending { .. } => {
368                    return Err(RemoteObjectRecordError::InvalidUploadTransition);
369                }
370            },
371        }
372        self.validate()
373    }
374
375    pub fn add_retained_authority_candidate(
376        &mut self,
377        candidate: StoreBatchCommitRef,
378    ) -> Result<(), RemoteObjectRecordError> {
379        let Self::RetainedAuthority(record) = self else {
380            return Err(RemoteObjectRecordError::DomainMismatch);
381        };
382        let RetainedAuthorityObjectState::UploadedVerified { ownership } = &mut record.state else {
383            return Err(RemoteObjectRecordError::InvalidActivation);
384        };
385        if ownership.activated.contains(&candidate)
386            || ownership
387                .nonactivated
388                .iter()
389                .map(CandidateNonactivation::reference)
390                .collect::<Result<BTreeSet<_>, _>>()?
391                .contains(&candidate)
392            || !ownership.pending.insert(candidate)
393        {
394            return Err(RemoteObjectRecordError::OverlappingOwnership);
395        }
396        self.validate()
397    }
398
399    pub fn merge_retained_authority_activation(
400        &mut self,
401        expected: &Self,
402        owner: &StoreBatchCommitRef,
403    ) -> Result<(), RemoteObjectRecordError> {
404        let Self::RetainedAuthority(expected) = expected else {
405            return Err(RemoteObjectRecordError::DomainMismatch);
406        };
407        let RetainedAuthorityObjectState::UploadedVerified {
408            ownership: expected_ownership,
409        } = &expected.state
410        else {
411            return Err(RemoteObjectRecordError::InvalidActivation);
412        };
413        if !expected_ownership.pending.is_empty()
414            || !expected_ownership.nonactivated.is_empty()
415            || expected_ownership.activated != BTreeSet::from([owner.clone()])
416        {
417            return Err(RemoteObjectRecordError::InvalidActivation);
418        }
419        match self {
420            Self::CandidateExclusive(current) => {
421                if current.identity.domain.retained_destination()
422                    != Some(expected.identity.domain.clone())
423                    || current.identity.semantic_hash != expected.identity.semantic_hash
424                    || current.identity.object != expected.identity.object
425                    || current.payloads != expected.payloads
426                {
427                    return Err(RemoteObjectRecordError::StoredReferenceMismatch);
428                }
429                let owns_activation = match &current.state {
430                    CandidateObjectState::Prepared { ownership }
431                    | CandidateObjectState::UploadedVerified { ownership } => {
432                        ownership.pending == BTreeSet::from([owner.clone()])
433                    }
434                    CandidateObjectState::CleanupPending { .. }
435                    | CandidateObjectState::AbsentVerified { .. } => false,
436                };
437                if !owns_activation {
438                    return Err(RemoteObjectRecordError::InvalidActivation);
439                }
440                let mut activated = Self::CandidateExclusive(current.clone());
441                activated.mark_uploaded_verified()?;
442                *self = activated.into_activated(owner)?;
443            }
444            Self::RetainedAuthority(current) => {
445                if current.identity != expected.identity || current.payloads != expected.payloads {
446                    return Err(RemoteObjectRecordError::StoredReferenceMismatch);
447                }
448                if matches!(current.state, RetainedAuthorityObjectState::Prepared { .. }) {
449                    self.mark_uploaded_verified()?;
450                }
451                let Self::RetainedAuthority(current) = self else {
452                    unreachable!("retained authority remains in its domain")
453                };
454                let RetainedAuthorityObjectState::UploadedVerified { ownership } =
455                    &mut current.state
456                else {
457                    return Err(RemoteObjectRecordError::InvalidActivation);
458                };
459                ownership.pending.remove(owner);
460                ownership.activated.insert(owner.clone());
461            }
462            Self::CandidateCommit(_) | Self::SharedLiveSet(_) => {
463                return Err(RemoteObjectRecordError::DomainMismatch);
464            }
465        }
466        self.validate()
467    }
468
469    pub fn begin_candidate_nonactivation(
470        &mut self,
471        nonactivation: CandidateNonactivation,
472    ) -> Result<Option<ProtocolInertObject>, RemoteObjectRecordError> {
473        nonactivation.validate()?;
474        let candidate = nonactivation.reference()?;
475        match self {
476            Self::CandidateCommit(record) => {
477                if record.identity != candidate {
478                    return Err(RemoteObjectRecordError::CandidateOwnerMismatch);
479                }
480                match &record.state {
481                    CandidateCommitState::Prepared | CandidateCommitState::UploadedVerified => {
482                        record.state = CandidateCommitState::CleanupPending {
483                            proof: nonactivation.proof,
484                        };
485                    }
486                    CandidateCommitState::CleanupPending { .. }
487                    | CandidateCommitState::AbsentVerified { .. } => {}
488                }
489            }
490            Self::CandidateExclusive(record) => match &mut record.state {
491                CandidateObjectState::Prepared { ownership }
492                | CandidateObjectState::UploadedVerified { ownership } => {
493                    if !ownership.pending.remove(&candidate) {
494                        return Err(RemoteObjectRecordError::CandidateOwnerMismatch);
495                    }
496                    ownership.nonactivated.push(nonactivation);
497                    if ownership.pending.is_empty() {
498                        record.state = CandidateObjectState::CleanupPending {
499                            former_candidates: ownership.nonactivated.clone(),
500                        };
501                    }
502                }
503                CandidateObjectState::CleanupPending { former_candidates }
504                | CandidateObjectState::AbsentVerified { former_candidates } => {
505                    ensure_candidate_nonactivation(former_candidates, &candidate)?;
506                }
507            },
508            Self::RetainedAuthority(record) => match &mut record.state {
509                RetainedAuthorityObjectState::Prepared { .. } => {
510                    return Err(RemoteObjectRecordError::CandidateOwnerMismatch);
511                }
512                RetainedAuthorityObjectState::UploadedVerified { .. } => {
513                    let RetainedAuthorityObjectState::UploadedVerified { ownership } =
514                        &record.state
515                    else {
516                        unreachable!("matched uploaded retained authority")
517                    };
518                    let mut ownership = ownership.clone();
519                    if !ownership.pending.remove(&candidate) {
520                        ensure_candidate_nonactivation(&ownership.nonactivated, &candidate)?;
521                        return Ok(None);
522                    }
523                    ownership.nonactivated.push(nonactivation);
524                    if ownership.pending.is_empty() && ownership.activated.is_empty() {
525                        return ProtocolInertObject::new(
526                            record.identity.clone(),
527                            ownership.nonactivated,
528                        )
529                        .map(Some);
530                    }
531                    record.state = RetainedAuthorityObjectState::UploadedVerified { ownership };
532                }
533            },
534            Self::SharedLiveSet(record) => match &mut record.state {
535                OwnedObjectState::Prepared { ownership } => {
536                    if !ownership.pending.remove(&candidate) {
537                        return Err(RemoteObjectRecordError::CandidateOwnerMismatch);
538                    }
539                    ownership.nonactivated.push(nonactivation);
540                    if ownership.pending.is_empty() {
541                        record.state = OwnedObjectState::RetirementPending {
542                            former_candidates: ownership.nonactivated.clone(),
543                        };
544                    }
545                }
546                OwnedObjectState::UploadedVerified { ownership } => {
547                    if !ownership.pending.remove(&candidate) {
548                        return Err(RemoteObjectRecordError::CandidateOwnerMismatch);
549                    }
550                    ownership.nonactivated.push(nonactivation);
551                    if ownership.pending.is_empty() && ownership.activated.is_empty() {
552                        record.state = OwnedObjectState::RetirementPending {
553                            former_candidates: ownership.nonactivated.clone(),
554                        };
555                    }
556                }
557                OwnedObjectState::RetirementPending { former_candidates } => {
558                    ensure_candidate_nonactivation(former_candidates, &candidate)?;
559                }
560            },
561        }
562        self.validate()?;
563        Ok(None)
564    }
565
566    pub fn cleanup_target(&self) -> Option<&ExactObjectRef> {
567        match self {
568            Self::CandidateCommit(CandidateCommitRecord {
569                state: CandidateCommitState::CleanupPending { .. },
570                ..
571            })
572            | Self::CandidateExclusive(CandidateObjectRecord {
573                state: CandidateObjectState::CleanupPending { .. },
574                ..
575            }) => Some(self.object()),
576            _ => None,
577        }
578    }
579
580    pub fn mark_absent_verified(&mut self) -> Result<(), RemoteObjectRecordError> {
581        match self {
582            Self::CandidateCommit(record) => match &record.state {
583                CandidateCommitState::CleanupPending { proof } => {
584                    record.state = CandidateCommitState::AbsentVerified {
585                        proof: proof.clone(),
586                    };
587                }
588                CandidateCommitState::AbsentVerified { .. } => {}
589                _ => return Err(RemoteObjectRecordError::InvalidCleanupTransition),
590            },
591            Self::CandidateExclusive(record) => match &record.state {
592                CandidateObjectState::CleanupPending { former_candidates } => {
593                    record.state = CandidateObjectState::AbsentVerified {
594                        former_candidates: former_candidates.clone(),
595                    };
596                }
597                CandidateObjectState::AbsentVerified { .. } => {}
598                _ => return Err(RemoteObjectRecordError::InvalidCleanupTransition),
599            },
600            Self::RetainedAuthority(_) | Self::SharedLiveSet(_) => {
601                return Err(RemoteObjectRecordError::InvalidCleanupTransition);
602            }
603        }
604        self.validate()
605    }
606
607    pub fn candidate_cleanup_complete(
608        &self,
609        candidate: &StoreBatchCommitRef,
610    ) -> Result<bool, RemoteObjectRecordError> {
611        self.validate()?;
612        let contains =
613            |former: &[CandidateNonactivation]| -> Result<bool, RemoteObjectRecordError> {
614                former
615                    .iter()
616                    .map(CandidateNonactivation::reference)
617                    .try_fold(false, |found, reference| {
618                        reference.map(|reference| found || &reference == candidate)
619                    })
620            };
621        match self {
622            Self::CandidateCommit(record) => Ok(&record.identity == candidate
623                && matches!(record.state, CandidateCommitState::AbsentVerified { .. })),
624            Self::CandidateExclusive(record) => match &record.state {
625                CandidateObjectState::Prepared { ownership }
626                | CandidateObjectState::UploadedVerified { ownership } => Ok(!ownership
627                    .pending
628                    .contains(candidate)
629                    && contains(&ownership.nonactivated)?),
630                CandidateObjectState::CleanupPending { .. } => Ok(false),
631                CandidateObjectState::AbsentVerified { former_candidates } => {
632                    contains(former_candidates)
633                }
634            },
635            Self::RetainedAuthority(record) => match &record.state {
636                RetainedAuthorityObjectState::Prepared { ownership } => Ok(!ownership
637                    .pending
638                    .contains(candidate)
639                    && contains(&ownership.nonactivated)?),
640                RetainedAuthorityObjectState::UploadedVerified { ownership } => {
641                    Ok(!ownership.pending.contains(candidate)
642                        && !ownership.activated.contains(candidate)
643                        && contains(&ownership.nonactivated)?)
644                }
645            },
646            Self::SharedLiveSet(record) => match &record.state {
647                OwnedObjectState::Prepared { ownership } => Ok(!ownership
648                    .pending
649                    .contains(candidate)
650                    && contains(&ownership.nonactivated)?),
651                OwnedObjectState::UploadedVerified { ownership } => {
652                    Ok(!ownership.pending.contains(candidate)
653                        && !ownership
654                            .activated
655                            .contains(&SharedObjectOwner::StoreCommit(candidate.clone()))
656                        && contains(&ownership.nonactivated)?)
657                }
658                OwnedObjectState::RetirementPending { former_candidates } => {
659                    contains(former_candidates)
660                }
661            },
662        }
663    }
664}