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 Rebase,
70 Folded(&'a [SettledStoreWrite]),
72}
73
74#[derive(Debug, Clone, PartialEq, Eq)]
77pub(crate) struct SettledStoreWrite {
78 pub ordinal: i64,
79 pub write_id: WriteId,
80 pub status: coven_protocol::write::WriteStatus,
81 pub observed: StoreWriteBase,
82 pub changeset_hash: ObjectHash,
83 pub input_hash: ObjectHash,
84}
85
86pub(crate) struct RetainedStoreWriteManifest {
87 pub ordinal: i64,
88 pub write_id: String,
89 pub status: String,
90 pub base: String,
91 pub changeset_hash: String,
92 pub prepared: Option<String>,
93 pub input_hash: ObjectHash,
94}
95
96#[derive(Clone, Copy)]
97pub enum StoreWriteRouting<'a> {
98 Unscoped,
99 MergeScoped(&'a EncryptionService),
100}
101
102impl PreparedStoreWritePartitions {
103 #[cfg(any(test, feature = "test-utils"))]
104 pub fn iter(&self) -> impl Iterator<Item = &gate::AudiencePartition> {
105 self.store
106 .iter()
107 .chain(self.circles.iter())
108 .chain(self.local.iter())
109 }
110}
111
112#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
113#[serde(deny_unknown_fields)]
114pub struct StoreWriteBase {
115 pub dependencies: BTreeMap<String, StoreBatchCommitRef>,
119}
120
121#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
125#[serde(deny_unknown_fields)]
126pub(crate) struct RebasedStoreWrite {
127 pub base: StoreWriteBase,
128 pub publication_base: coven_protocol::store_commit::StorePublicationBase,
129 pub changeset_hash: ObjectHash,
130 pub blob_facts: StoreWriteBlobFacts,
131}
132
133#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
134#[serde(deny_unknown_fields)]
135pub struct StoreWriteBlobFacts {
136 pub blobs: Vec<StoreWriteBlobFact>,
137}
138
139#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
140#[serde(deny_unknown_fields)]
141pub struct StoreWriteBlobFact {
142 pub table: String,
143 pub row_id: String,
144 pub row_stamp: String,
145 pub column: String,
146 pub blob: BlobRef,
147 pub plaintext_size: u64,
148 pub plaintext_hash: ObjectHash,
149 pub external_path: Option<PathBuf>,
150 pub previous: Option<StoreWriteRemoteBlob>,
151 pub audience_move: Option<StoreWriteBlobMoveMaterialization>,
152}
153
154#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
155#[serde(deny_unknown_fields)]
156pub struct StoreWriteRemoteBlob {
157 pub authority: coven_protocol::audience_package::PackageAudience,
158 pub stored: StoredBlobRef,
159}
160
161#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
162#[serde(rename_all = "snake_case", deny_unknown_fields)]
163pub enum StoreWriteBlobMoveMaterialization {
164 Local,
165 Payload,
168}
169
170impl StoreWriteBlobFacts {
171 pub(crate) fn captured_payloads(&self) -> impl Iterator<Item = ObjectHash> + '_ {
172 self.blobs.iter().filter_map(|fact| {
173 (fact.audience_move == Some(StoreWriteBlobMoveMaterialization::Payload))
174 .then_some(fact.plaintext_hash)
175 })
176 }
177}
178
179impl StoreWriteBlobFact {
180 pub fn identity_key(&self) -> (String, String, String, String) {
181 (
182 self.table.clone(),
183 self.row_id.clone(),
184 self.column.clone(),
185 self.row_stamp.clone(),
186 )
187 }
188}
189
190#[derive(Debug, Clone)]
191pub struct PreparedStoreWriteCommit {
192 pub audiences: PreparedAudienceObjects,
193 pub commit: ExactProtocolObject<VerifiedStoreBatchCommit>,
194 pub publication: coven_protocol::prepared_commit::PreparedStorePublication,
195}
196
197#[derive(Debug)]
198pub struct InitialStoreMembershipAuthority {
199 pub head_refs: Vec<coven_protocol::membership::MembershipHeadRef>,
200}
201
202impl InitialStoreMembershipAuthority {
203 const CURSOR_STATE_KEY_PREFIX: &'static str = "membership_head_cursor/";
204
205 pub fn cursor_state_key_for_stream(
206 owner_grant: &coven_protocol::membership::MembershipGrantId,
207 stream_id: coven_protocol::membership::AuthorStreamId,
208 ) -> String {
209 format!("{}{owner_grant}/{stream_id}", Self::CURSOR_STATE_KEY_PREFIX)
210 }
211
212 fn cursor_state_key(reference: &coven_protocol::membership::MembershipHeadRef) -> String {
213 Self::cursor_state_key_for_stream(
214 &reference.coord.author_owner_grant,
215 reference.coord.stream_id,
216 )
217 }
218
219 pub(crate) fn load_on(conn: &Connection) -> Result<Self, DbError> {
220 let mut statement = conn
221 .prepare(
222 "SELECT value FROM protocol_state \
223 WHERE substr(key, 1, length(?1)) = ?1 ORDER BY key",
224 )
225 .map_err(DbError::from)?;
226 let rows = statement
227 .query_map([Self::CURSOR_STATE_KEY_PREFIX], |row| {
228 row.get::<_, String>(0)
229 })
230 .map_err(DbError::from)?;
231 let mut head_refs = Vec::new();
232 for row in rows {
233 let value = row.map_err(DbError::from)?;
234 let reference: coven_protocol::membership::MembershipHeadRef =
235 serde_json::from_str(&value).map_err(|error| {
236 DbError::context("membership head cursor is malformed", error)
237 })?;
238 if reference.coord.seq == 0 {
239 return Err(DbError::Message(
240 "membership head cursor has sequence zero".to_string(),
241 ));
242 }
243 head_refs.push(reference);
244 }
245 Ok(Self { head_refs })
246 }
247
248 pub(crate) fn install_on(&self, conn: &Connection) -> Result<(), DbError> {
249 for reference in &self.head_refs {
250 let key = Self::cursor_state_key(reference);
251 if let Some(existing) = get_protocol_state_on(conn, &key)? {
252 let existing: coven_protocol::membership::MembershipHeadRef =
253 serde_json::from_str(&existing).map_err(|error| {
254 DbError::context("membership head cursor is malformed", error)
255 })?;
256 if existing.coord.stream_key() != reference.coord.stream_key() {
257 return Err(DbError::Message(
258 "membership head cursor key names a different stream".to_string(),
259 ));
260 }
261 if existing.coord.seq > reference.coord.seq {
262 continue;
263 }
264 if existing.coord.seq == reference.coord.seq {
265 if existing == *reference {
266 continue;
267 }
268 return Err(DbError::Message(
269 "membership head cursor forks at the same sequence".to_string(),
270 ));
271 }
272 }
273 let value = serde_json::to_string(reference)
274 .map_err(|error| DbError::context("serialize membership head cursor", error))?;
275 set_protocol_state_on(conn, &key, &value)?;
276 }
277 Ok(())
278 }
279
280 #[cfg(any(test, feature = "test-utils"))]
281 pub fn cursor_state_key_for_test(
282 reference: &coven_protocol::membership::MembershipHeadRef,
283 ) -> String {
284 Self::cursor_state_key(reference)
285 }
286}
287
288#[derive(Debug, Clone)]
289pub struct OutboundStoreAck {
290 pub reference: StoreAckRef,
291 pub ack: ExactProtocolObject<StoreAck>,
292 pub circle_acknowledgements: Vec<coven_protocol::prepared_commit::CircleAckActivation>,
293 pub activation: OutboundStoreAckActivation,
294}
295
296#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
297#[serde(rename_all = "snake_case", deny_unknown_fields)]
298pub enum OutboundStoreAckActivation {
299 AwaitingCandidate,
300 Created,
301 Prepared(coven_protocol::prepared_commit::PreparedStoreOperationCommit),
302}
303
304#[derive(Debug, Clone)]
305pub struct PublishedStoreAck {
306 pub reference: StoreAckRef,
307 pub successor_slot: coven_protocol::objects::ObjectSlot,
308 pub standing: Option<coven_protocol::store_commit::StandingStoreAck>,
314}
315
316#[derive(Debug, Clone, PartialEq, Eq)]
318pub struct ActivatedStoreAck {
319 pub reference: StoreAckRef,
320 pub activating_commit: StoreBatchCommitRef,
321}