Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T02:34:39.204Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

identity_controller.rs

267 lines · 8.3 KB · rust
1use omega_identity::{CustodyResult, IdentityInspection, ReceiptRef};
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub(crate) enum IdentityOperation {
5    Inspect,
6    Create { receipt_ref: ReceiptRef },
7    ResumeIncomplete,
8    PrepareRecovery,
9    AdoptRecovery { receipt_ref: ReceiptRef },
10    ResolveConflict { receipt_ref: ReceiptRef },
11    ExportRecovery,
12    Reset { receipt_ref: ReceiptRef },
13    ResumeReset,
14}
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub(crate) struct OperationToken {
18    generation: u64,
19    operation: IdentityOperation,
20}
21
22impl OperationToken {
23    pub(crate) fn operation(&self) -> &IdentityOperation {
24        &self.operation
25    }
26}
27
28#[derive(Debug, Copy, Clone, PartialEq, Eq)]
29pub(crate) enum IdentityUiError {
30    InvalidSecret,
31    InvalidPassword,
32    UnsafeRecoveryFile,
33    RecoveryFileExists,
34    SecureStorageUnavailable,
35    OperationFailed,
36}
37
38#[derive(Debug, Default)]
39pub(crate) struct IdentityControllerState {
40    generation: u64,
41    durable: Option<IdentityInspection>,
42    operation: Option<OperationToken>,
43    error: Option<IdentityUiError>,
44}
45
46impl IdentityControllerState {
47    pub(crate) fn durable(&self) -> Option<&IdentityInspection> {
48        self.durable.as_ref()
49    }
50
51    pub(crate) fn operation(&self) -> Option<&IdentityOperation> {
52        self.operation.as_ref().map(OperationToken::operation)
53    }
54
55    pub(crate) fn error(&self) -> Option<IdentityUiError> {
56        self.error
57    }
58
59    pub(crate) fn begin(&mut self, operation: IdentityOperation) -> Option<OperationToken> {
60        if self.operation.is_some() {
61            return None;
62        }
63        Some(self.replace(operation))
64    }
65
66    pub(crate) fn replace(&mut self, operation: IdentityOperation) -> OperationToken {
67        self.generation = self.generation.wrapping_add(1);
68        let token = OperationToken {
69            generation: self.generation,
70            operation,
71        };
72        self.operation = Some(token.clone());
73        self.error = None;
74        token
75    }
76
77    pub(crate) fn cancel(&mut self) {
78        self.generation = self.generation.wrapping_add(1);
79        self.operation = None;
80        self.error = None;
81    }
82
83    pub(crate) fn cancel_if_current(&mut self, token: &OperationToken) -> bool {
84        if self.operation.as_ref() != Some(token) || token.generation != self.generation {
85            return false;
86        }
87        self.cancel();
88        true
89    }
90
91    pub(crate) fn forget_durable(&mut self) {
92        self.durable = None;
93    }
94
95    pub(crate) fn report_error(&mut self, error: IdentityUiError) {
96        self.generation = self.generation.wrapping_add(1);
97        self.operation = None;
98        self.error = Some(error);
99    }
100
101    pub(crate) fn apply(
102        &mut self,
103        token: &OperationToken,
104        result: Result<IdentityInspection, IdentityUiError>,
105    ) -> bool {
106        if self.operation.as_ref() != Some(token) || token.generation != self.generation {
107            return false;
108        }
109        self.operation = None;
110        match result {
111            Ok(result) => {
112                self.durable = Some(result);
113                self.error = None;
114            }
115            Err(error) => {
116                self.error = Some(error);
117            }
118        }
119        true
120    }
121
122    pub(crate) fn apply_custody(
123        &mut self,
124        token: &OperationToken,
125        result: Result<CustodyResult, IdentityUiError>,
126    ) -> bool {
127        if self.operation.as_ref() != Some(token) || token.generation != self.generation {
128            return false;
129        }
130        self.operation = None;
131        match result {
132            Ok(custody) => {
133                if let Some(inspection) = &mut self.durable {
134                    inspection.custody = custody;
135                    inspection.pending_transaction = None;
136                    inspection.conflict = None;
137                }
138                self.error = None;
139            }
140            Err(error) => {
141                self.error = Some(error);
142            }
143        }
144        true
145    }
146
147    pub(crate) fn accept(&mut self, token: &OperationToken) -> bool {
148        if self.operation.as_ref() != Some(token) || token.generation != self.generation {
149            return false;
150        }
151        self.operation = None;
152        self.error = None;
153        true
154    }
155
156    pub(crate) fn reject(&mut self, token: &OperationToken, error: IdentityUiError) -> bool {
157        if self.operation.as_ref() != Some(token) || token.generation != self.generation {
158            return false;
159        }
160        self.operation = None;
161        self.error = Some(error);
162        true
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use omega_identity::{
169        CustodyResult, CustodyState, RecoveryProtectionState, RecoveryProtectionStatus,
170    };
171
172    use super::*;
173
174    fn result(state: CustodyState) -> IdentityInspection {
175        IdentityInspection {
176            custody: CustodyResult {
177                state,
178                identity: None,
179                receipt_ref: None,
180            },
181            pending_transaction: None,
182            conflict: None,
183            recovery_protection: RecoveryProtectionStatus {
184                state: RecoveryProtectionState::NotApplicable,
185                record: None,
186            },
187        }
188    }
189
190    #[test]
191    fn only_the_current_generation_and_phase_can_update_public_state() {
192        let mut controller = IdentityControllerState::default();
193        let first = controller.replace(IdentityOperation::Inspect);
194        let second = controller.replace(IdentityOperation::ResumeIncomplete);
195
196        assert!(!controller.apply(&first, Ok(result(CustodyState::Absent))));
197        assert!(controller.durable().is_none());
198        assert!(controller.apply(&second, Ok(result(CustodyState::Ready))));
199        assert_eq!(
200            controller
201                .durable()
202                .map(|inspection| inspection.custody.state),
203            Some(CustodyState::Ready)
204        );
205    }
206
207    #[test]
208    fn errors_do_not_replace_the_last_durable_fact() {
209        let mut controller = IdentityControllerState::default();
210        let inspect = controller.replace(IdentityOperation::Inspect);
211        assert!(controller.apply(&inspect, Ok(result(CustodyState::Lost))));
212
213        let retry = controller.replace(IdentityOperation::Inspect);
214        assert!(controller.apply(&retry, Err(IdentityUiError::SecureStorageUnavailable)));
215        assert_eq!(
216            controller
217                .durable()
218                .map(|inspection| inspection.custody.state),
219            Some(CustodyState::Lost)
220        );
221        assert_eq!(
222            controller.error(),
223            Some(IdentityUiError::SecureStorageUnavailable)
224        );
225    }
226
227    #[test]
228    fn busy_operations_reject_double_submission_and_cancel_fences_late_results() {
229        let mut controller = IdentityControllerState::default();
230        let create_receipt = ReceiptRef::new("create-one").expect("valid receipt");
231        let create = controller
232            .begin(IdentityOperation::Create {
233                receipt_ref: create_receipt,
234            })
235            .expect("start create");
236        assert!(controller.begin(IdentityOperation::Inspect).is_none());
237
238        controller.cancel();
239        assert!(!controller.apply(&create, Ok(result(CustodyState::Ready))));
240        assert!(controller.operation().is_none());
241        assert!(controller.durable().is_none());
242    }
243
244    #[test]
245    fn dialog_cancellation_only_cancels_its_own_operation() {
246        let mut controller = IdentityControllerState::default();
247        let dialog = controller.replace(IdentityOperation::PrepareRecovery);
248        let newer = controller.replace(IdentityOperation::Inspect);
249
250        assert!(!controller.cancel_if_current(&dialog));
251        assert_eq!(controller.operation(), Some(&IdentityOperation::Inspect));
252        assert!(controller.cancel_if_current(&newer));
253        assert!(controller.operation().is_none());
254    }
255
256    #[test]
257    fn durable_facts_can_be_forgotten_before_reinspection() {
258        let mut controller = IdentityControllerState::default();
259        let inspect = controller.replace(IdentityOperation::Inspect);
260        assert!(controller.apply(&inspect, Ok(result(CustodyState::Ready))));
261
262        controller.forget_durable();
263
264        assert!(controller.durable().is_none());
265    }
266}
267
Served at tenant.openagents/omega Member data and write actions are omitted.