Skip to main content

chio_kernel/admission_operation/
store.rs

1use super::*;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub enum AdmissionBeginResult {
5    Created(AdmissionOperationV1),
6    ExactReplay {
7        operation: AdmissionOperationV1,
8        terminal_replay: Option<AdmissionTerminalReplay>,
9    },
10    Conflict {
11        existing_operation_id: AdmissionOperationId,
12    },
13}
14
15#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
16pub enum AdmissionOperationStoreError {
17    #[error("admission operation store is unavailable: {0}")]
18    Unavailable(String),
19    #[error("admission operation mutation was fenced")]
20    Fenced,
21    #[error("admission operation was not found")]
22    NotFound,
23    #[error("admission operation invariant failed: {0}")]
24    Invariant(String),
25    #[error("admission operation durable outcome is unknown: {0}")]
26    OutcomeUnknown(String),
27    #[error(transparent)]
28    Operation(#[from] AdmissionOperationError),
29}
30
31pub trait AdmissionOperationStore: Send + Sync {
32    fn begin(
33        &self,
34        operation: &AdmissionOperationV1,
35        fence: &StoreMutationFence,
36        trusted_now_unix_ms: u64,
37    ) -> Result<AdmissionBeginResult, AdmissionOperationStoreError>;
38
39    fn load_by_operation_id(
40        &self,
41        operation_id: &AdmissionOperationId,
42    ) -> Result<Option<AdmissionOperationV1>, AdmissionOperationStoreError>;
43
44    fn load_by_replay_key(
45        &self,
46        replay_key: &AdmissionReplayKey,
47    ) -> Result<Option<AdmissionOperationV1>, AdmissionOperationStoreError>;
48
49    fn compare_and_swap(
50        &self,
51        command: &AdmissionOperationCommand,
52        trusted_now_unix_ms: u64,
53    ) -> Result<AdmissionCommandResult, AdmissionOperationStoreError>;
54
55    /// Persist a structurally checked claim. The returned value remains
56    /// untrusted until `QualifiedAdmissionOperationStore::claim_recovery`
57    /// rechecks it through this store.
58    fn claim_recovery_untrusted(
59        &self,
60        operation_id: &AdmissionOperationId,
61        expected_version: u64,
62        claimant_id: &AdmissionIdentifier,
63        trusted_now_unix_ms: u64,
64        expires_at_unix_ms: u64,
65        fence: &StoreMutationFence,
66    ) -> Result<UntrustedAdmissionRecoveryClaim, AdmissionOperationStoreError>;
67
68    /// Re-read the durable claim under the current store fence and verify its
69    /// exact operation snapshot and historical coordinator lease.
70    fn revalidate_recovery_claim(
71        &self,
72        operation: &AdmissionOperationV1,
73        claim: &UntrustedAdmissionRecoveryClaim,
74        trusted_now_unix_ms: u64,
75        current_store_fence: &StoreMutationFence,
76    ) -> Result<(), AdmissionOperationStoreError>;
77
78    /// Lists non-terminal operations that require startup recovery work.
79    ///
80    /// Quiescent `ApprovalRequired` operations must be excluded before applying
81    /// `limit`: they are waiting for external approval rather than recovery, and
82    /// allowing them to occupy a page can starve later operations that do need
83    /// reconciliation.
84    fn list_recoverable(
85        &self,
86        not_after_unix_ms: u64,
87        limit: usize,
88    ) -> Result<Vec<AdmissionOperationV1>, AdmissionOperationStoreError>;
89
90    fn load_terminal_replay(
91        &self,
92        replay_key: &AdmissionReplayKey,
93    ) -> Result<Option<AdmissionTerminalReplay>, AdmissionOperationStoreError>;
94}
95
96/// Explicit trust boundary for stores allowed to qualify durable recovery
97/// claims.
98///
99/// # Implementation contract
100///
101/// Implementations must durably serialize claims with operation mutations,
102/// enforce the current serving-owner fence and trusted time, and revalidate the
103/// exact persisted claim and historical coordinator lease. An implementation
104/// that returns success without those guarantees can authorize an unsafe
105/// recovery transition.
106pub trait QualifiedAdmissionOperationStore: AdmissionOperationStore {}
107
108/// Non-overridable recovery qualification for explicitly trusted stores.
109pub trait QualifiedAdmissionOperationStoreExt: QualifiedAdmissionOperationStore {
110    fn claim_recovery(
111        &self,
112        operation_id: &AdmissionOperationId,
113        expected_version: u64,
114        claimant_id: &AdmissionIdentifier,
115        trusted_now_unix_ms: u64,
116        expires_at_unix_ms: u64,
117        current_store_fence: &StoreMutationFence,
118    ) -> Result<AdmissionRecoveryLease, AdmissionOperationStoreError> {
119        let claim = self.claim_recovery_untrusted(
120            operation_id,
121            expected_version,
122            claimant_id,
123            trusted_now_unix_ms,
124            expires_at_unix_ms,
125            current_store_fence,
126        )?;
127        let operation = self
128            .load_by_operation_id(operation_id)?
129            .ok_or(AdmissionOperationStoreError::NotFound)?;
130        claim.validate_for_qualification(
131            &operation,
132            expected_version,
133            claimant_id,
134            trusted_now_unix_ms,
135            expires_at_unix_ms,
136            current_store_fence,
137        )?;
138        self.revalidate_recovery_claim(
139            &operation,
140            &claim,
141            trusted_now_unix_ms,
142            current_store_fence,
143        )?;
144        Ok(AdmissionRecoveryLease::from_qualified(claim))
145    }
146}
147
148impl<T: QualifiedAdmissionOperationStore + ?Sized> QualifiedAdmissionOperationStoreExt for T {}