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::StoreBatchCommitRef;
8
9/// Stable identity of one successfully committed host transaction.
10#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
11#[serde(transparent)]
12pub struct WriteId(String);
13
14impl WriteId {
15    pub fn from_generated(value: String) -> Self {
16        Self(value)
17    }
18
19    pub fn as_str(&self) -> &str {
20        &self.0
21    }
22}
23
24impl fmt::Display for WriteId {
25    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
26        formatter.write_str(&self.0)
27    }
28}
29
30/// Exact position that made a write visible to peers.
31#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
32#[serde(deny_unknown_fields)]
33pub struct PublishedPosition {
34    pub device_id: String,
35    pub commit: StoreBatchCommitRef,
36}
37
38impl PublishedPosition {
39    pub fn commit(&self) -> &StoreBatchCommitRef {
40        &self.commit
41    }
42}
43
44/// A semantic write fault. Retrying transport cannot change this result.
45#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
46#[serde(rename_all = "snake_case", deny_unknown_fields)]
47pub enum WriteBlock {
48    InvalidPackage {
49        reason: String,
50    },
51    InvalidProtocolState {
52        reason: String,
53    },
54    MissingBlob {
55        namespace: String,
56        id: String,
57    },
58    LocalUserBlob {
59        namespace: String,
60        id: String,
61    },
62    RotationRequired {
63        circle_id: crate::circle::CircleId,
64        removed_members: Vec<String>,
65    },
66}
67
68/// Current durable state of one host transaction.
69#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
70#[serde(rename_all = "snake_case", deny_unknown_fields)]
71pub enum WriteStatus {
72    LocalOnly,
73    Pending,
74    Publishing,
75    Published(Box<PublishedPosition>),
76    Blocked(WriteBlock),
77    Resolved(WriteResolution),
78}
79
80#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
81#[serde(rename_all = "snake_case", deny_unknown_fields)]
82pub enum WriteResolution {
83    Discarded,
84    Retracted { witness: WriteRetractionWitness },
85}
86
87/// Durable proof that a previously published write cannot activate.
88#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
89#[serde(deny_unknown_fields)]
90pub struct WriteRetractionWitness {
91    original: PublishedPosition,
92    nonactivation: crate::remote_object::CandidateNonactivation,
93}
94
95#[derive(Debug, thiserror::Error)]
96pub enum WriteRetractionError {
97    #[error("invalid candidate nonactivation: {0}")]
98    Nonactivation(#[from] crate::remote_object::RemoteObjectRecordError),
99    #[error("write retraction proof names another published commit")]
100    CommitMismatch,
101}
102
103impl WriteRetractionWitness {
104    pub fn new(
105        original: PublishedPosition,
106        nonactivation: crate::remote_object::CandidateNonactivation,
107    ) -> Result<Self, WriteRetractionError> {
108        let candidate = nonactivation.reference()?;
109        if original.commit() != &candidate {
110            return Err(WriteRetractionError::CommitMismatch);
111        }
112        let witness = Self {
113            original,
114            nonactivation,
115        };
116        witness.validate()?;
117        Ok(witness)
118    }
119
120    pub fn original_position(&self) -> &PublishedPosition {
121        &self.original
122    }
123
124    pub fn validate(&self) -> Result<(), WriteRetractionError> {
125        let candidate = self.nonactivation.reference()?;
126        if self.original.commit() != &candidate {
127            return Err(WriteRetractionError::CommitMismatch);
128        }
129        Ok(())
130    }
131}
132
133/// One table/primary-key identity affected by the shared part of a write.
134#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
135#[serde(deny_unknown_fields)]
136pub struct AffectedRow {
137    pub table: String,
138    pub primary_key: String,
139}
140
141/// Durable write information returned by `CovenHandle::pending_writes`.
142#[derive(Debug, Clone, PartialEq, Eq)]
143pub struct PendingWrite {
144    pub write_id: WriteId,
145    pub status: WriteStatus,
146    pub affected_rows: Vec<AffectedRow>,
147}
148
149/// Result of one successful host transaction and its durable publication identity.
150#[derive(Debug, Clone, PartialEq, Eq)]
151pub struct WriteReceipt<R> {
152    pub value: R,
153    pub write_id: WriteId,
154    pub status: WriteStatus,
155}