Skip to main content

coven_protocol/
write.rs

1//! Durable identity and publication status for one host transaction.
2
3use std::fmt;
4
5use serde::{Deserialize, Serialize};
6
7use crate::store_commit::{
8    AcceptedStoreSnapshotRef, StoreBatchCommitRef, StoreCommitCoord, StoreDeviceRegistrationRef,
9};
10
11/// Stable identity of one successfully committed host transaction.
12#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
13#[serde(transparent)]
14pub struct WriteId(String);
15
16impl WriteId {
17    pub fn from_generated(value: String) -> Self {
18        Self(value)
19    }
20
21    pub fn as_str(&self) -> &str {
22        &self.0
23    }
24}
25
26impl fmt::Display for WriteId {
27    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
28        formatter.write_str(&self.0)
29    }
30}
31
32/// Exact position that made a write visible to peers.
33#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(deny_unknown_fields)]
35pub struct PublishedPosition {
36    pub device_id: String,
37    pub commit: StoreBatchCommitRef,
38}
39
40impl PublishedPosition {
41    pub fn commit(&self) -> &StoreBatchCommitRef {
42        &self.commit
43    }
44}
45
46/// A reserved author position whose accepted edit is included in a snapshot.
47/// The snapshot does not identify which exact candidate completed the edit.
48#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
49#[serde(deny_unknown_fields)]
50pub struct SnapshotCoveredPosition {
51    pub author_registration: StoreDeviceRegistrationRef,
52    pub coord: StoreCommitCoord,
53    pub snapshot: AcceptedStoreSnapshotRef,
54}
55
56/// Durable evidence that a host write was published, with or without its exact commit.
57#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
58#[serde(rename_all = "snake_case", deny_unknown_fields)]
59pub enum PublishedWrite {
60    Commit(PublishedPosition),
61    Snapshot(SnapshotCoveredPosition),
62}
63
64impl PublishedWrite {
65    pub fn coord(&self) -> &StoreCommitCoord {
66        match self {
67            Self::Commit(position) => &position.commit.coord,
68            Self::Snapshot(position) => &position.coord,
69        }
70    }
71
72    pub fn exact_commit(&self) -> Option<&StoreBatchCommitRef> {
73        match self {
74            Self::Commit(position) => Some(position.commit()),
75            Self::Snapshot(_) => None,
76        }
77    }
78}
79
80/// A semantic write fault. Retrying transport cannot change this result.
81#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
82#[serde(rename_all = "snake_case", deny_unknown_fields)]
83pub enum WriteBlock {
84    RebaseConflict(WriteRebaseConflict),
85    InvalidPackage {
86        reason: String,
87    },
88    InvalidProtocolState {
89        reason: String,
90    },
91    MissingBlob {
92        namespace: String,
93        id: String,
94    },
95    LocalUserBlob {
96        namespace: String,
97        id: String,
98    },
99    RotationRequired {
100        circle_id: crate::circle::CircleId,
101        removed_members: Vec<String>,
102    },
103}
104
105/// Current durable state of one host transaction.
106#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
107#[serde(rename_all = "snake_case", deny_unknown_fields)]
108pub enum WriteStatus {
109    LocalOnly,
110    /// Private intent retained for explicit resolution; it never owes publication.
111    LocalOnlyBlocked(WriteBlock),
112    Pending,
113    Publishing,
114    Published(Box<PublishedWrite>),
115    Blocked(WriteBlock),
116    Resolved(WriteResolution),
117}
118
119#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
120#[serde(rename_all = "snake_case", deny_unknown_fields)]
121pub enum WriteResolution {
122    Discarded,
123}
124
125/// One table/primary-key identity affected by a write.
126#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
127#[serde(deny_unknown_fields)]
128pub struct AffectedRow {
129    pub table: String,
130    pub primary_key: String,
131}
132
133/// A recorded edit violates a constraint or its retained audience authority.
134#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, thiserror::Error)]
135#[serde(deny_unknown_fields)]
136#[error("write {write_id} cannot rebase rows {affected_rows:?}: {reason}")]
137pub struct WriteRebaseConflict {
138    pub write_id: WriteId,
139    /// The exact row for an attributed conflict, or the captured write's rows
140    /// when SQLite rejects the transaction without attributing a single row.
141    pub affected_rows: Vec<AffectedRow>,
142    pub reason: WriteRebaseConflictReason,
143}
144
145#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, thiserror::Error)]
146#[serde(rename_all = "snake_case", deny_unknown_fields)]
147pub enum WriteRebaseConflictReason {
148    #[error("the private edit conflicts with an accepted shared row")]
149    PrivateShared,
150    #[error("Circle {circle_id} no longer authorizes the captured edit")]
151    InvalidCircleContext { circle_id: crate::circle::CircleId },
152    #[error("the edit violates a constraint: {message}")]
153    Constraint { message: String },
154}
155
156/// Durable write information returned by `CovenHandle::pending_writes`.
157#[derive(Debug, Clone, PartialEq, Eq)]
158pub struct PendingWrite {
159    pub write_id: WriteId,
160    pub status: WriteStatus,
161    pub affected_rows: Vec<AffectedRow>,
162}
163
164/// Result of one successful host transaction and its durable publication identity.
165#[derive(Debug, Clone, PartialEq, Eq)]
166pub struct WriteReceipt<R> {
167    pub value: R,
168    pub write_id: WriteId,
169    pub status: WriteStatus,
170}