1use super::*;
2use coven_protocol::store_commit::VerifiedStoreBatchCommit;
3
4pub struct PreparedStoreWrite {
5 pub write_id: WriteId,
6 pub partitions: PreparedStoreWritePartitions,
7 pub base: StoreWriteBase,
8 pub blob_facts: StoreWriteBlobFacts,
9}
10
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct PreparedStoreWritePartitions {
13 pub store: Option<gate::AudiencePartition>,
14 pub circles: Vec<gate::AudiencePartition>,
15 pub local: Option<gate::AudiencePartition>,
16}
17
18#[derive(Clone)]
19pub(crate) struct MergeReplayWriteEffect {
20 pub write_id: WriteId,
21 pub partitions: PreparedStoreWritePartitions,
22}
23
24#[derive(Clone)]
25pub(crate) enum MergeReplayWrite {
26 LocalOnly {
27 effect: MergeReplayWriteEffect,
28 observed: coven_protocol::store_commit::CommitFrontier,
29 },
30 Unaccepted {
31 effect: MergeReplayWriteEffect,
32 observed: coven_protocol::store_commit::CommitFrontier,
33 },
34 Accepted {
35 effect: MergeReplayWriteEffect,
36 observed: coven_protocol::store_commit::CommitFrontier,
37 commit: StoreBatchCommitRef,
38 },
39 Consumed {
40 write_id: WriteId,
41 },
42}
43
44impl MergeReplayWrite {
45 pub(crate) fn write_id(&self) -> &WriteId {
46 match self {
47 Self::LocalOnly { effect, .. }
48 | Self::Unaccepted { effect, .. }
49 | Self::Accepted { effect, .. } => &effect.write_id,
50 Self::Consumed { write_id } => write_id,
51 }
52 }
53}
54
55pub(crate) enum ReplayJournal<'a> {
61 Omit,
64 Owed,
67 Folded(&'a [SettledStoreWrite]),
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
74pub(crate) struct SettledStoreWrite {
75 pub ordinal: i64,
76 pub write_id: WriteId,
77 pub fold: SettledWriteFold,
78 pub status: coven_protocol::write::WriteStatus,
79 pub observed: StoreWriteBase,
80 pub changeset_hash: ObjectHash,
81 pub input_hash: ObjectHash,
82}
83
84pub(crate) struct RetainedStoreWriteManifest {
85 pub ordinal: i64,
86 pub write_id: String,
87 pub status: String,
88 pub base: String,
89 pub changeset_hash: String,
90 pub prepared: Option<String>,
91 pub input_hash: ObjectHash,
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96pub(crate) enum SettledWriteFold {
97 LocalOnly,
103 Published,
108 Reversed,
111}
112
113impl SettledWriteFold {
114 pub(crate) fn states_local_rows(self) -> bool {
116 matches!(self, Self::LocalOnly | Self::Published)
117 }
118
119 pub(crate) fn keeps_receipt(self) -> bool {
122 !matches!(self, Self::LocalOnly)
123 }
124}
125
126#[derive(Clone, Copy)]
127pub enum StoreWriteRouting<'a> {
128 Unscoped,
129 MergeScoped(&'a EncryptionService),
130}
131
132impl PreparedStoreWritePartitions {
133 #[cfg(any(test, feature = "test-utils"))]
134 pub fn iter(&self) -> impl Iterator<Item = &gate::AudiencePartition> {
135 self.store
136 .iter()
137 .chain(self.circles.iter())
138 .chain(self.local.iter())
139 }
140}
141
142#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
143#[serde(deny_unknown_fields)]
144pub struct StoreWriteBase {
145 pub dependencies: BTreeMap<String, StoreBatchCommitRef>,
149}
150
151#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
152#[serde(deny_unknown_fields)]
153pub struct StoreWriteBlobFacts {
154 pub blobs: Vec<StoreWriteBlobFact>,
155}
156
157#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
158#[serde(deny_unknown_fields)]
159pub struct StoreWriteBlobFact {
160 pub table: String,
161 pub row_id: String,
162 pub row_stamp: String,
163 pub column: String,
164 pub blob: BlobRef,
165 pub plaintext_size: u64,
166 pub plaintext_hash: ObjectHash,
167 pub external_path: Option<PathBuf>,
168 pub previous: Option<StoreWriteRemoteBlob>,
169 pub audience_move: Option<StoreWriteBlobMoveDestination>,
170}
171
172#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
173#[serde(deny_unknown_fields)]
174pub struct StoreWriteRemoteBlob {
175 pub authority: coven_protocol::audience_package::PackageAudience,
176 pub stored: StoredBlobRef,
177}
178
179#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
180#[serde(rename_all = "snake_case", deny_unknown_fields)]
181pub enum StoreWriteBlobMoveDestination {
182 Local,
183 Remote {
184 audience: coven_protocol::blob::locator::RemoteAudience,
185 locator: coven_protocol::blob::locator::BlobLocator,
186 spool_path: PathBuf,
187 },
188}
189
190impl StoreWriteBlobFact {
191 pub fn identity_key(&self) -> (String, String, String, String) {
192 (
193 self.table.clone(),
194 self.row_id.clone(),
195 self.column.clone(),
196 self.row_stamp.clone(),
197 )
198 }
199}
200
201#[derive(Debug, Clone)]
202pub struct PreparedStoreWriteCommit {
203 pub audiences: PreparedAudienceObjects,
204 pub commit: ExactProtocolObject<VerifiedStoreBatchCommit>,
205 pub head: ExactProtocolObject<StoreDeviceHead>,
206}
207
208#[derive(Debug, Clone)]
216pub struct BlockedMergeCandidate {
217 pub commit: VerifiedStoreBatchCommit,
218 pub commit_bytes: Vec<u8>,
219 pub commit_object: ExactObjectRef,
220 pub head: StoreDeviceHead,
221 pub head_object: ExactObjectRef,
222}
223
224#[derive(Debug, Clone)]
225pub struct PreparedMergeAbandonmentCandidates {
226 pub candidate: BlockedMergeCandidate,
227 pub authority: BlockedMergeCandidate,
228}
229
230#[derive(Debug, Clone, PartialEq, Eq)]
231pub enum CompletePreparedStoreWriteOutcome {
232 Published,
233 AuthorExcluded {
234 device_id: coven_protocol::store_commit::StoreDeviceId,
235 },
236}
237
238#[derive(Debug, Clone, PartialEq, Eq)]
239pub struct AuthorExclusionActivationLocator {
240 exclusion: coven_protocol::store_commit::StoreDeviceExclusionRef,
241 accepted_cut: BTreeMap<coven_protocol::causal_grants::AuthorStreamId, StoreBatchCommitRef>,
242 activation_commit: StoreBatchCommitRef,
243 activation_head: coven_protocol::store_commit::StoreDeviceHeadRef,
244}
245
246impl AuthorExclusionActivationLocator {
247 pub fn verified(
248 exclusion: coven_protocol::store_commit::StoreDeviceExclusionRef,
249 accepted_cut: BTreeMap<coven_protocol::causal_grants::AuthorStreamId, StoreBatchCommitRef>,
250 activation_commit: StoreBatchCommitRef,
251 activation_head: coven_protocol::store_commit::StoreDeviceHeadRef,
252 ) -> Self {
253 Self {
254 exclusion,
255 accepted_cut,
256 activation_commit,
257 activation_head,
258 }
259 }
260
261 pub fn exclusion(&self) -> &coven_protocol::store_commit::StoreDeviceExclusionRef {
262 &self.exclusion
263 }
264
265 pub fn accepted_cut(
266 &self,
267 ) -> &BTreeMap<coven_protocol::causal_grants::AuthorStreamId, StoreBatchCommitRef> {
268 &self.accepted_cut
269 }
270
271 pub fn activation_head(&self) -> &coven_protocol::store_commit::StoreDeviceHeadRef {
272 &self.activation_head
273 }
274
275 pub fn activation_commit(&self) -> &StoreBatchCommitRef {
276 &self.activation_commit
277 }
278}
279
280#[derive(Debug, Clone)]
281pub enum TerminalCandidateAuthority {
282 AuthorExclusion(AuthorExclusionActivationLocator),
283 MembershipGrantRevocation {
284 grant_id: coven_protocol::membership::MembershipGrantId,
285 membership: coven_protocol::circle_control::StoreMembershipStateRef,
286 activation_commit: StoreBatchCommitRef,
287 activation_head: coven_protocol::store_commit::StoreDeviceHeadRef,
288 },
289 DependencyRetraction(coven_protocol::remote_object::VerifiedDependencyRetractionAuthority),
290}
291
292#[derive(Debug, Clone)]
293pub struct TerminalCandidateCleanupVerification {
294 pub authority: TerminalCandidateAuthority,
295 pub candidate: BlockedMergeCandidate,
296}
297
298#[derive(Debug)]
299pub struct InitialStoreMembershipAuthority {
300 pub head_refs: Vec<coven_protocol::membership::MembershipHeadRef>,
301}
302
303impl InitialStoreMembershipAuthority {
304 const CURSOR_STATE_KEY_PREFIX: &'static str = "membership_head_cursor/";
305
306 pub fn cursor_state_key_for_stream(
307 owner_grant: &coven_protocol::membership::MembershipGrantId,
308 stream_id: coven_protocol::membership::AuthorStreamId,
309 ) -> String {
310 format!("{}{owner_grant}/{stream_id}", Self::CURSOR_STATE_KEY_PREFIX)
311 }
312
313 fn cursor_state_key(reference: &coven_protocol::membership::MembershipHeadRef) -> String {
314 Self::cursor_state_key_for_stream(
315 &reference.coord.author_owner_grant,
316 reference.coord.stream_id,
317 )
318 }
319
320 pub(crate) fn load_on(conn: &Connection) -> Result<Self, DbError> {
321 let mut statement = conn
322 .prepare(
323 "SELECT value FROM protocol_state \
324 WHERE substr(key, 1, length(?1)) = ?1 ORDER BY key",
325 )
326 .map_err(DbError::from)?;
327 let rows = statement
328 .query_map([Self::CURSOR_STATE_KEY_PREFIX], |row| {
329 row.get::<_, String>(0)
330 })
331 .map_err(DbError::from)?;
332 let mut head_refs = Vec::new();
333 for row in rows {
334 let value = row.map_err(DbError::from)?;
335 let reference: coven_protocol::membership::MembershipHeadRef =
336 serde_json::from_str(&value).map_err(|error| {
337 DbError::context("membership head cursor is malformed", error)
338 })?;
339 if reference.coord.seq == 0 {
340 return Err(DbError::Message(
341 "membership head cursor has sequence zero".to_string(),
342 ));
343 }
344 head_refs.push(reference);
345 }
346 Ok(Self { head_refs })
347 }
348
349 pub(crate) fn install_on(&self, conn: &Connection) -> Result<(), DbError> {
350 for reference in &self.head_refs {
351 let key = Self::cursor_state_key(reference);
352 if let Some(existing) = get_protocol_state_on(conn, &key)? {
353 let existing: coven_protocol::membership::MembershipHeadRef =
354 serde_json::from_str(&existing).map_err(|error| {
355 DbError::context("membership head cursor is malformed", error)
356 })?;
357 if existing.coord.stream_key() != reference.coord.stream_key() {
358 return Err(DbError::Message(
359 "membership head cursor key names a different stream".to_string(),
360 ));
361 }
362 if existing.coord.seq > reference.coord.seq {
363 continue;
364 }
365 if existing.coord.seq == reference.coord.seq {
366 if existing == *reference {
367 continue;
368 }
369 return Err(DbError::Message(
370 "membership head cursor forks at the same sequence".to_string(),
371 ));
372 }
373 }
374 let value = serde_json::to_string(reference)
375 .map_err(|error| DbError::context("serialize membership head cursor", error))?;
376 set_protocol_state_on(conn, &key, &value)?;
377 }
378 Ok(())
379 }
380
381 #[cfg(any(test, feature = "test-utils"))]
382 pub fn cursor_state_key_for_test(
383 reference: &coven_protocol::membership::MembershipHeadRef,
384 ) -> String {
385 Self::cursor_state_key(reference)
386 }
387}
388
389#[derive(Debug, Clone, Copy, PartialEq, Eq)]
390pub enum MergeAbandonmentState {
391 None,
392 Prepared,
393 Accepted,
394 CandidateWon,
395 OtherWon,
396 AuthorExcluded,
397}
398
399#[derive(Debug, Clone)]
400pub struct OutboundStoreAck {
401 pub reference: StoreAckRef,
402 pub ack: ExactProtocolObject<StoreAck>,
403 pub circle_acknowledgements: Vec<coven_protocol::prepared_commit::CircleAckActivation>,
404 pub activation: OutboundStoreAckActivation,
405}
406
407#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
408#[serde(rename_all = "snake_case", deny_unknown_fields)]
409pub enum OutboundStoreAckActivation {
410 AwaitingCandidate,
411 Prepared(coven_protocol::prepared_commit::PreparedStoreOperationCommit),
412 Nonactivating(coven_protocol::prepared_commit::PreparedStoreOperationCommit),
413}
414
415#[derive(Debug, Clone)]
416pub struct PublishedStoreAck {
417 pub reference: StoreAckRef,
418 pub successor_slot: coven_protocol::objects::ObjectSlot,
419 pub standing: Option<coven_protocol::store_commit::StandingStoreAck>,
425}
426
427#[derive(Debug, Clone, PartialEq, Eq)]
429pub struct ActivatedStoreAck {
430 pub reference: StoreAckRef,
431 pub activating_commit: StoreBatchCommitRef,
432}