1use super::{
2 CircleAuthoringState, CircleOperationError, CircleOperationIntent, CircleTransitionHistory,
3};
4use coven_protocol::circle::{CircleCloseStatus, CircleId, CircleRole, CircleRosterChain};
5use coven_protocol::store_commit::CircleControlRef;
6use coven_storage::BlobPathScheme;
7
8pub struct StoreCircleCommands<'store> {
9 store: &'store super::Store,
10}
11
12impl<'store> StoreCircleCommands<'store> {
13 pub(crate) fn new(store: &'store super::Store) -> Self {
14 Self { store }
15 }
16
17 fn require_protected_paths(&self) -> Result<(), CircleOperationError> {
20 if matches!(self.store.blob_path_scheme(), BlobPathScheme::Plain) {
21 return Err(CircleOperationError::BrowsableStorage);
22 }
23 Ok(())
24 }
25
26 async fn writer(
27 &self,
28 ) -> Result<
29 crate::sync::store::commit_publication::AuthorizedWriterOperation<'store>,
30 CircleOperationError,
31 > {
32 self.require_protected_paths()?;
33 self.store
34 .authorize_writer()
35 .await
36 .map_err(CircleOperationError::from)
37 }
38
39 pub(crate) async fn circle_close_status(
46 &self,
47 circle_id: CircleId,
48 ) -> Result<CircleCloseStatus, CircleOperationError> {
49 self.store.circle_close_status(circle_id).await
50 }
51
52 pub(crate) async fn create_circle(
53 &self,
54 metadata_stamp: &str,
55 name: &str,
56 ) -> Result<CircleId, CircleOperationError> {
57 let mut writer = self.writer().await?;
58 writer.circles().create_circle(metadata_stamp, name).await
59 }
60
61 pub(crate) async fn rename_circle(
62 &self,
63 metadata_stamp: &str,
64 circle_id: CircleId,
65 name: &str,
66 ) -> Result<(), CircleOperationError> {
67 let mut writer = self.writer().await?;
68 writer
69 .circles()
70 .rename_circle(metadata_stamp, circle_id, name)
71 .await
72 }
73
74 pub(crate) async fn remove_circle_member(
75 &self,
76 circle_id: CircleId,
77 member_pubkey: String,
78 ) -> Result<coven_protocol::circle::CircleOperationId, CircleOperationError> {
79 let mut writer = self.writer().await?;
80 writer
81 .circles()
82 .remove_circle_member(circle_id, member_pubkey)
83 .await
84 }
85
86 pub(crate) async fn resolve_circle_control(
95 &self,
96 circle_id: CircleId,
97 chosen: coven_protocol::circle::CircleControlCoord,
98 ) -> Result<(), CircleOperationError> {
99 let mut writer = self.writer().await?;
100 writer
101 .circles()
102 .resolve_circle_control(circle_id, chosen)
103 .await
104 }
105
106 pub(crate) async fn cancel_circle_epoch_close(
115 &self,
116 circle_id: CircleId,
117 ) -> Result<coven_protocol::circle::CircleOperationId, CircleOperationError> {
118 let mut writer = self.writer().await?;
119 writer.circles().cancel_circle_epoch_close(circle_id).await
120 }
121
122 pub(crate) async fn exclude_circle_close_device(
127 &self,
128 circle_id: CircleId,
129 excluded_device_id: coven_protocol::store_commit::StoreDeviceId,
130 ) -> Result<(), CircleOperationError> {
131 let mut writer = self.writer().await?;
132 writer
133 .circles()
134 .exclude_circle_close_device(circle_id, excluded_device_id)
135 .await
136 }
137
138 pub(crate) async fn retry_circle_operation(
144 &self,
145 operation_id: &coven_protocol::circle::CircleOperationId,
146 routing_encryption: Option<&coven_keys::encryption::EncryptionService>,
147 ) -> Result<(), CircleOperationError> {
148 let mut writer = self.writer().await?;
149 writer
150 .circles()
151 .retry_circle_operation(operation_id, routing_encryption)
152 .await
153 }
154
155 pub(crate) async fn discard_circle_operation(
165 &self,
166 operation_id: &coven_protocol::circle::CircleOperationId,
167 ) -> Result<(), CircleOperationError> {
168 self.require_protected_paths()?;
169 let mut authorized = self
170 .store
171 .authorize()
172 .await
173 .map_err(CircleOperationError::from)?;
174 authorized.discard_circle_operation(operation_id).await
175 }
176
177 pub(crate) async fn delete_circle(
184 &self,
185 circle_id: CircleId,
186 ) -> Result<(), CircleOperationError> {
187 let mut writer = self.writer().await?;
188 writer.circles().delete_circle(circle_id).await
189 }
190}
191
192pub(crate) struct CircleRenameRequest {
193 pub(super) circle_id: CircleId,
194 pub(super) name: String,
195 pub(super) metadata_stamp: String,
196 pub(super) current: CircleAuthoringState,
197 pub(super) previous_control: CircleControlRef,
198}
199
200pub(crate) struct CircleAddMemberRequest {
201 pub(super) circle_id: CircleId,
202 pub(super) member_pubkey: String,
203 pub(super) role: CircleRole,
204 bootstrap: crate::sync::store::SnapshotCut,
205 pub(super) current: CircleAuthoringState,
206 pub(super) previous_control: CircleControlRef,
207 pub(super) roster_chain: CircleRosterChain,
208}
209
210impl CircleAddMemberRequest {
211 pub(super) fn new(
212 circle_id: CircleId,
213 member_pubkey: String,
214 role: CircleRole,
215 bootstrap: crate::sync::store::SnapshotCut,
216 current: CircleAuthoringState,
217 previous_control: CircleControlRef,
218 roster_chain: CircleRosterChain,
219 ) -> Self {
220 Self {
221 circle_id,
222 member_pubkey,
223 role,
224 bootstrap,
225 current,
226 previous_control,
227 roster_chain,
228 }
229 }
230
231 pub(super) fn bootstrap_blobs(&self) -> &[coven_database::SnapshotBlobFact] {
232 self.bootstrap.blobs()
233 }
234
235 pub(super) async fn read_bootstrap_image(
236 &self,
237 ) -> Result<Vec<u8>, coven_database::SnapshotImageError> {
238 self.bootstrap.read_image().await
239 }
240
241 pub(super) fn bootstrap_coverage(&self) -> &coven_protocol::store_commit::CommitFrontier {
242 self.bootstrap.coverage()
243 }
244}
245
246pub(crate) struct CircleRemoveMemberRequest {
247 pub(super) circle_id: CircleId,
248 pub(super) member_pubkey: String,
249 pub(super) current: CircleAuthoringState,
250 pub(super) previous_control: CircleControlRef,
251 pub(super) roster_chain: CircleRosterChain,
252}
253
254pub(crate) struct CircleDeleteRequest {
255 pub(super) circle_id: CircleId,
256 pub(super) current: CircleAuthoringState,
257 pub(super) previous_control: CircleControlRef,
258}
259
260pub(crate) struct CircleResolveControlRequest {
261 pub(super) circle_id: CircleId,
262 pub(super) chosen: CircleAuthoringState,
263 pub(super) previous_control: CircleControlRef,
264 pub(super) losing_branches: Vec<CircleResolveLosingBranch>,
268 pub(super) conflicting_branches: Vec<coven_protocol::circle::CircleControlCoord>,
274}
275
276pub(crate) struct CircleResolveLosingBranch {
277 pub(super) reference: CircleControlRef,
281 pub(super) selected_metadata: coven_protocol::circle::CircleMetadata,
284}
285
286pub(crate) struct CircleFinalizeEpochCloseRequest {
287 pub(super) operation_id: coven_protocol::circle::CircleOperationId,
288 pub(super) circle_id: CircleId,
289 pub(super) member_pubkey: String,
290 pub(super) metadata_stamp: String,
291 pub(super) current: CircleAuthoringState,
292 pub(super) previous_control: CircleControlRef,
293 pub(super) roster_chain: CircleRosterChain,
294 pub(super) intent: coven_protocol::circle::CircleEpochCloseIntent,
295 pub(super) responses: Vec<coven_protocol::circle::CircleEpochCloseSettlement>,
296 bootstrap: crate::sync::store::SnapshotCut,
297}
298
299impl CircleFinalizeEpochCloseRequest {
300 #[allow(clippy::too_many_arguments)]
301 pub(super) fn new(
302 operation_id: coven_protocol::circle::CircleOperationId,
303 circle_id: CircleId,
304 member_pubkey: String,
305 metadata_stamp: String,
306 current: CircleAuthoringState,
307 previous_control: CircleControlRef,
308 roster_chain: CircleRosterChain,
309 intent: coven_protocol::circle::CircleEpochCloseIntent,
310 responses: Vec<coven_protocol::circle::CircleEpochCloseSettlement>,
311 bootstrap: crate::sync::store::SnapshotCut,
312 ) -> Self {
313 Self {
314 operation_id,
315 circle_id,
316 member_pubkey,
317 metadata_stamp,
318 current,
319 previous_control,
320 roster_chain,
321 intent,
322 responses,
323 bootstrap,
324 }
325 }
326
327 pub(super) fn bootstrap_blobs(&self) -> &[coven_database::SnapshotBlobFact] {
328 self.bootstrap.blobs()
329 }
330
331 pub(super) async fn read_bootstrap_image(
332 &self,
333 ) -> Result<Vec<u8>, coven_database::SnapshotImageError> {
334 self.bootstrap.read_image().await
335 }
336
337 pub(super) fn bootstrap_coverage(&self) -> &coven_protocol::store_commit::CommitFrontier {
338 self.bootstrap.coverage()
339 }
340}
341
342pub(crate) struct CircleCancelEpochCloseRequest {
343 pub(super) operation_id: coven_protocol::circle::CircleOperationId,
344 pub(super) circle_id: CircleId,
345 pub(super) member_pubkey: String,
346 pub(super) current: CircleAuthoringState,
347 pub(super) previous_control: CircleControlRef,
348}
349
350pub(crate) enum CircleOperationRequest {
351 Create {
352 name: String,
353 metadata_stamp: String,
354 },
355 Rename(Box<CircleRenameRequest>),
356 AddMember(Box<CircleAddMemberRequest>),
357 RemoveMember(Box<CircleRemoveMemberRequest>),
358 ResolveControl(Box<CircleResolveControlRequest>),
359 Delete(Box<CircleDeleteRequest>),
360 FinalizeEpochClose(Box<CircleFinalizeEpochCloseRequest>),
361 CancelEpochClose(Box<CircleCancelEpochCloseRequest>),
362}
363
364impl CircleOperationRequest {
365 pub(super) fn intent(&self) -> CircleOperationIntent {
366 match self {
367 Self::Create { name, .. } => CircleOperationIntent::Create { name: name.clone() },
368 Self::Rename(request) => CircleOperationIntent::Rename {
369 name: request.name.clone(),
370 },
371 Self::AddMember(request) => CircleOperationIntent::AddMember {
372 member_pubkey: request.member_pubkey.clone(),
373 role: request.role,
374 },
375 Self::RemoveMember(request) => CircleOperationIntent::RemoveMember {
376 member_pubkey: request.member_pubkey.clone(),
377 },
378 Self::ResolveControl(request) => CircleOperationIntent::ResolveControl {
379 chosen: request.chosen.control.coord.clone(),
380 },
381 Self::Delete(_) => CircleOperationIntent::Delete,
382 Self::FinalizeEpochClose(request) => CircleOperationIntent::RemoveMember {
383 member_pubkey: request.member_pubkey.clone(),
384 },
385 Self::CancelEpochClose(request) => CircleOperationIntent::RemoveMember {
386 member_pubkey: request.member_pubkey.clone(),
387 },
388 }
389 }
390
391 pub(super) fn history(&self) -> CircleTransitionHistory {
392 let previous_control = match self {
393 Self::Create { .. } => return CircleTransitionHistory::Founder,
394 Self::Rename(request) => &request.previous_control,
395 Self::AddMember(request) => &request.previous_control,
396 Self::RemoveMember(request) => &request.previous_control,
397 Self::ResolveControl(request) => &request.previous_control,
398 Self::Delete(request) => &request.previous_control,
399 Self::FinalizeEpochClose(request) => &request.previous_control,
400 Self::CancelEpochClose(request) => &request.previous_control,
401 };
402 CircleTransitionHistory::Successor(Box::new(previous_control.clone()))
403 }
404
405 pub(super) fn settlement(
410 &self,
411 ) -> Option<(
412 coven_protocol::circle::CircleOperationId,
413 coven_protocol::write::WriteId,
414 )> {
415 match self {
416 Self::FinalizeEpochClose(request) => Some((
417 request.operation_id.clone(),
418 request.operation_id.finalization_write_id(),
419 )),
420 Self::CancelEpochClose(request) => Some((
421 request.operation_id.clone(),
422 request.operation_id.cancellation_write_id(),
423 )),
424 Self::Create { .. }
425 | Self::Rename(_)
426 | Self::AddMember(_)
427 | Self::RemoveMember(_)
428 | Self::ResolveControl(_)
429 | Self::Delete(_) => None,
430 }
431 }
432}