coven_sync/operation_types.rs
1//! Application values for operations, members, circles and invitations (api.md).
2
3use coven_crypto::SecretText;
4use coven_format::admin::MemberRole;
5use coven_foundation::id_source::{InviteId, MemberId};
6use std::time::{Duration, SystemTime};
7
8pub use coven_database::{
9 Circle, CircleMemberInfo, DeviceInfo, DeviceState, MemberInfo, OperationId,
10};
11/// An operation failure retains the same typed causes as an ordinary sync call.
12pub type OperationError = crate::SyncError;
13
14/// The purpose of an unfinished operation, including maintenance.
15#[derive(
16 Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
17)]
18pub enum OperationKind {
19 /// Create the store's first slot.
20 CreateStore,
21 /// Set the store name.
22 RenameStore,
23 /// Register this installation.
24 AddDevice,
25 /// Set a member's role.
26 SetRole,
27 /// Replace provider access.
28 SetAccess,
29 /// Remove a member.
30 RemoveMember,
31 /// Remove an installation.
32 RemoveDevice,
33 /// Rename a circle.
34 RenameCircle,
35 /// Remove a circle member.
36 RemoveCircleMember,
37 /// Advance the floor and delete the covered prefix.
38 RetainSequence,
39 /// Delete unreferenced files after establishing retained-object protection.
40 RetainFiles,
41 /// Write and verify a maintenance snapshot.
42 WriteSnapshot,
43 /// Publish the app's migrated schema and its audience snapshots.
44 SchemaChange,
45 /// Snapshot this device and reset one audience to that state.
46 Reset,
47 /// Reload snapshots at the app's request, keeping waiting writes.
48 ReloadFromSnapshot,
49 /// Reload what sync requires: a changed schema or reset, a returning
50 /// circle, joining or recovery.
51 RequiredReload,
52 /// Publish a fresh audience key and its sealed copies.
53 RotateKey,
54 /// Send an older audience key to a current member whose sealing key has
55 /// no copy of it.
56 SendKeyCopy,
57 /// Create a circle and its first key.
58 CreateCircle,
59 /// Seal a circle's history to a member.
60 AddCircleMember,
61 /// Publish a circle's deletion; its rows are taken out from that slot
62 /// (§15.9).
63 DeleteCircle,
64 /// Share storage and settle an invitation.
65 Invite,
66 /// Align one provider account's grants with members and open invitations.
67 RevokeAccess,
68 /// Replace a member's signing and sealing pair: this member's own, with a
69 /// candidate kept in identity custody, or a prepared pair as an admin (§10).
70 ReplaceMemberKeys,
71 /// Set up storage on this device: check the provider, then create the
72 /// store or reconnect to it (§11.6, §20.8).
73 StorageSetup,
74}
75
76impl OperationKind {
77 /// Coven's own work, which no app call retries or discards: a failed
78 /// attempt retries under the shared backoff (§11, E5).
79 pub(crate) fn coven_owned(self) -> bool {
80 matches!(
81 self,
82 Self::WriteSnapshot
83 | Self::RetainSequence
84 | Self::RetainFiles
85 | Self::RequiredReload
86 | Self::RotateKey
87 | Self::SendKeyCopy
88 | Self::RevokeAccess
89 )
90 }
91}
92
93/// The admin's choice, fixed before the invite is shown (§13.2, §13.3).
94#[derive(Clone, Debug, PartialEq, Eq)]
95pub enum InviteTarget {
96 /// A new person: coven generates a fresh member id, added with this role.
97 NewMember {
98 /// The role the person receives on approval.
99 role: MemberRole,
100 },
101 /// An existing active member who lost every device: approval binds the
102 /// requester's new keys to this id, keeping its role, circles and devices.
103 ReplaceMemberKeys {
104 /// The member whose keys the approval replaces.
105 member: MemberId,
106 },
107}
108
109/// How the invited person reaches the store's provider.
110#[derive(Clone)]
111pub enum InviteAccess {
112 /// Share with the named provider account.
113 ProviderAccount {
114 /// The account's email address.
115 email: String,
116 },
117 /// Credentials created in the S3 console for this invite.
118 S3AccessKey {
119 /// Public key identifier.
120 access_key_id: String,
121 /// The key's secret, carried only in the invitation.
122 secret_access_key: SecretText,
123 },
124 /// The S3 key this device connects with, which coven reads from its
125 /// custody, so the secret never reaches the app (§14.3).
126 HeldS3AccessKey,
127}
128
129/// How long an invite stays open after it is made, by the inviting
130/// device's clock (§13.2).
131#[derive(Clone, Copy, Debug, PartialEq, Eq)]
132pub enum InviteLifetime {
133 /// Seven days.
134 SevenDays,
135 /// Thirty days.
136 ThirtyDays,
137}
138
139impl InviteLifetime {
140 pub(crate) fn duration(self) -> Duration {
141 Duration::from_secs(match self {
142 Self::SevenDays => 7 * 86_400,
143 Self::ThirtyDays => 30 * 86_400,
144 })
145 }
146}
147
148/// One of this device's open invites (§13.2).
149#[derive(Clone, Debug, PartialEq, Eq)]
150pub struct Invite {
151 /// The one-time request path's identity.
152 pub id: InviteId,
153 /// The invite's operation, which names its pending record in
154 /// `coven_pending` while its check or its grant waits or is refused.
155 pub operation: coven_database::OperationId,
156 /// The stable member id the invite fixed.
157 pub member: MemberId,
158 /// Whether the invite adds a new person or recovers a member.
159 pub kind: coven_format::codes::InviteKind,
160 /// The access the invite grants: the account shared with, or the id of
161 /// the S3 key it carries, never its secret (§14.3).
162 pub access: coven_format::MemberAccess,
163 /// The invite's lifetime after creation, on the inviting device's clock.
164 pub expires_at: SystemTime,
165 /// Where the invite is: granting its access, waiting for its request or
166 /// the app's decision, or approving its request.
167 pub stage: InviteStage,
168}
169
170/// Where one of this device's open invites is (§13.2).
171#[derive(Clone, PartialEq, Eq)]
172pub enum InviteStage {
173 /// The app's write made the invite. A pass checks it against the
174 /// history it catches up with and grants its storage access, and then
175 /// it shows its code. A check that refuses it, or a failed grant, is
176 /// its pending record under its operation; cancelling ends it.
177 Granting,
178 /// The code is shown, and the request, once one has arrived and been
179 /// checked, waits for the app to approve or decline it.
180 Waiting {
181 /// Code the app may show as a QR code, again whenever the admin
182 /// asks. Contains secrets.
183 code: String,
184 /// The checked request, once one has arrived.
185 request: Option<JoinRequest>,
186 },
187 /// Approval of this request has begun, and finishes without the app:
188 /// approving, declining or cancelling the invite fail with
189 /// `InvitationChanged`. The approval is the app's own, and
190 /// `coven_operations` lists its binding until the app acknowledges the
191 /// outcome.
192 Approving {
193 /// The code first shown, which the joining device scanned.
194 /// Contains secrets.
195 code: String,
196 /// The request being approved.
197 request: JoinRequest,
198 /// The operation that claims the request's copies and binds its
199 /// pair.
200 binding: coven_database::OperationId,
201 },
202}
203
204impl std::fmt::Debug for InviteStage {
205 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206 match self {
207 Self::Granting => f.write_str("Granting"),
208 Self::Waiting { request, .. } => f
209 .debug_struct("Waiting")
210 .field("code", &"[REDACTED]")
211 .field("request", request)
212 .finish(),
213 Self::Approving { request, binding, .. } => f
214 .debug_struct("Approving")
215 .field("code", &"[REDACTED]")
216 .field("request", request)
217 .field("binding", binding)
218 .finish(),
219 }
220 }
221}
222
223/// A checked request, presented with the account from its local invitation.
224#[derive(Clone, Debug, PartialEq, Eq)]
225pub struct JoinRequest {
226 /// Invitation being answered.
227 pub invite: InviteId,
228 /// The stable member id the invite fixed and the request repeats.
229 pub member: MemberId,
230 /// The fresh public pair the request's signature proves it holds;
231 /// approval binds exactly this pair to `member` (§13.2).
232 pub keys: coven_crypto::MemberPublicKeys,
233 /// The device's name, covered by its signature.
234 pub device_name: String,
235 /// Who asks: a new person with the name they typed, or a recovery. It
236 /// matches the invite's kind (§13.2).
237 pub joiner: coven_format::objects::Joiner,
238 /// Account invited by the owner, absent on S3.
239 pub provider_account_email: Option<String>,
240 /// Computed from the invite id and `keys`; the requester's device shows
241 /// the same number (format.md D11).
242 pub confirmation: coven_crypto::ConfirmationNumber,
243 pub(crate) record: Vec<u8>,
244}