Skip to repository content267 lines · 8.3 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T02:34:39.204Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
identity_controller.rs
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