Skip to main content

laminar_connectors/connector/
contracts.rs

1//! Source and sink delivery, topology, and changelog contracts.
2
3use std::str::FromStr;
4
5/// Delivery guarantee level for the pipeline.
6///
7/// Configures the expected end-to-end delivery semantics. The pipeline
8/// validates at startup that all sources and sinks meet the requirements
9/// for the chosen guarantee level.
10#[derive(
11    Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize,
12)]
13#[serde(rename_all = "snake_case")]
14pub enum DeliveryGuarantee {
15    /// Best effort: no replay contract. Intended for bare-metal/embedded
16    /// low-latency pipelines that explicitly accept loss on failure.
17    #[default]
18    BestEffort,
19    /// At-least-once: records may be replayed on recovery. Requires
20    /// checkpointing and replayable sources.
21    AtLeastOnce,
22    /// Exactly-once: no duplicates or losses. Requires all sources to
23    /// support replay, all sinks to support exactly-once, and checkpoint
24    /// to be enabled.
25    ExactlyOnce,
26}
27
28impl std::fmt::Display for DeliveryGuarantee {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        match self {
31            DeliveryGuarantee::BestEffort => write!(f, "best-effort"),
32            DeliveryGuarantee::AtLeastOnce => write!(f, "at-least-once"),
33            DeliveryGuarantee::ExactlyOnce => write!(f, "exactly-once"),
34        }
35    }
36}
37
38impl FromStr for DeliveryGuarantee {
39    type Err = String;
40
41    fn from_str(s: &str) -> Result<Self, Self::Err> {
42        match s.to_lowercase().replace('-', "_").as_str() {
43            "best_effort" | "besteffort" | "none" => Ok(Self::BestEffort),
44            "at_least_once" | "atleastonce" => Ok(Self::AtLeastOnce),
45            "exactly_once" | "exactlyonce" => Ok(Self::ExactlyOnce),
46            other => Err(format!("unknown delivery guarantee: '{other}'")),
47        }
48    }
49}
50
51/// Recovery semantics provided by a source.
52///
53/// This is deliberately a small, ordered set of operational contracts rather
54/// than a collection of independent capability flags. A source must advertise
55/// the strongest contract its implementation can actually uphold.
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
57pub enum SourceConsistency {
58    /// Events cannot be reconstructed after the runtime has accepted them.
59    #[default]
60    Ephemeral,
61    /// A persisted source position can be used to reproduce accepted events.
62    Replayable,
63    /// Replay is supported, and upstream progress/resources advance only when
64    /// the corresponding `LaminarDB` checkpoint is durably committed.
65    CommitCoupled,
66}
67
68impl SourceConsistency {
69    /// Whether a persisted source position can be replayed after recovery.
70    #[must_use]
71    pub const fn supports_replay(self) -> bool {
72        !matches!(self, Self::Ephemeral)
73    }
74
75    /// Whether checkpoint commits are required for safe upstream progress.
76    #[must_use]
77    pub const fn requires_checkpointing(self) -> bool {
78        matches!(self, Self::CommitCoupled)
79    }
80}
81
82/// How a source may be placed across runtime nodes.
83#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
84pub enum SourceTopology {
85    /// Exactly one runtime instance owns the source.
86    #[default]
87    Singleton,
88    /// Input partitions can be assigned independently across runtime nodes.
89    Splittable,
90    /// Each runtime node receives a distinct, node-local input stream.
91    NodeLocalIngress,
92}
93
94/// Update model emitted by a configured source.
95#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
96pub enum SourceInputMode {
97    /// Every row is an insertion.
98    #[default]
99    AppendOnly,
100    /// Current row images and deletes are reconciled by the declared primary key.
101    KeyedUpsert,
102    /// Decoded rows carry a non-null, non-zero `Int64` `__weight` column.
103    FullChangelog,
104}
105
106/// Whether a source emits an ordered deterministic position for every decoded row.
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
108pub enum SourceRowPositionCapability {
109    /// The source does not provide row positions.
110    #[default]
111    Unavailable,
112    /// Every emitted row carries a replay position. Within one source run, `(order_key,
113    /// sub_offset)` is nondecreasing per partition across batches; recovery may restart from an
114    /// earlier position. Replaying an equal position must produce the same logical row and
115    /// mutation.
116    OrderedDeterministic,
117}
118
119/// Complete source admission contract for a concrete connector configuration.
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
121pub struct SourceContract {
122    /// Recovery and external-progress semantics.
123    pub consistency: SourceConsistency,
124    /// Valid runtime placement model.
125    pub topology: SourceTopology,
126    /// Update model produced after connector decoding.
127    pub input_mode: SourceInputMode,
128    /// Deterministic per-row position support.
129    pub row_positions: SourceRowPositionCapability,
130    exact_delivery_certified: bool,
131}
132
133impl SourceContract {
134    /// Construct a source contract from its recovery, placement, and update dimensions.
135    /// Exactly-once certification defaults to fail-closed.
136    #[must_use]
137    pub const fn new(
138        consistency: SourceConsistency,
139        topology: SourceTopology,
140        input_mode: SourceInputMode,
141    ) -> Self {
142        Self {
143            consistency,
144            topology,
145            input_mode,
146            row_positions: SourceRowPositionCapability::Unavailable,
147            exact_delivery_certified: false,
148        }
149    }
150
151    /// Declare the source's per-row position contract.
152    #[must_use]
153    pub const fn with_row_positions(mut self, capability: SourceRowPositionCapability) -> Self {
154        self.row_positions = capability;
155        self
156    }
157
158    /// Mark a built-in connector whose exact-delivery suite is an engine release gate.
159    #[must_use]
160    pub(crate) const fn with_exact_delivery_certification(mut self) -> Self {
161        self.exact_delivery_certified = true;
162        self
163    }
164
165    /// Whether this source is certified for exactly-once delivery.
166    #[doc(hidden)]
167    #[must_use]
168    pub const fn is_exact_delivery_certified(self) -> bool {
169        self.exact_delivery_certified
170    }
171
172    /// Whether a persisted source position can be replayed after recovery.
173    #[must_use]
174    pub const fn supports_replay(self) -> bool {
175        self.consistency.supports_replay()
176    }
177
178    /// Whether checkpoint commits are required for safe upstream progress.
179    #[must_use]
180    pub const fn requires_checkpointing(self) -> bool {
181        self.consistency.requires_checkpointing()
182    }
183}
184
185/// Durability protocol provided by a sink.
186///
187/// This describes externally observable behaviour, not an implementation
188/// detail such as whether the client library buffers or retries writes.
189#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
190pub enum SinkConsistency {
191    /// An accepted write can be lost when the connector or peer fails.
192    #[default]
193    Ephemeral,
194    /// Successful writes are durably acknowledged, but replay may duplicate
195    /// them because visibility is not coupled to a `LaminarDB` checkpoint.
196    DurableAtLeastOnce,
197    /// Output can be staged and made visible by the checkpoint commit
198    /// protocol. This is necessary, but not sufficient, for exactly-once
199    /// certification: namespaces and recovery cursors must also be fenced.
200    CheckpointCommittable,
201}
202
203/// How a sink may be placed across runtime nodes.
204#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
205pub enum SinkTopology {
206    /// Only one fenced runtime writer may target the configured destination.
207    #[default]
208    Singleton,
209    /// Independent runtime writers can safely target the destination.
210    MultiWriter,
211    /// Each runtime node owns a distinct local egress endpoint or audience.
212    NodeLocalEgress,
213}
214
215/// The strongest input update model a configured sink understands.
216#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
217pub enum SinkInputMode {
218    /// Inserts only; retractions or deletes would be lost.
219    #[default]
220    AppendOnly,
221    /// Rows are reconciled by a configured key, but the connector does not
222    /// consume a native full-changelog envelope.
223    KeyedUpsert,
224    /// Inserts, updates, and deletes/retractions are represented faithfully.
225    FullChangelog,
226}
227
228impl SinkInputMode {
229    /// Whether this mode can faithfully consume a full Z-set changelog,
230    /// including deletes/retractions.
231    #[must_use]
232    pub const fn accepts_full_changelog(self) -> bool {
233        matches!(self, Self::FullChangelog)
234    }
235}
236
237/// Complete sink admission contract for a concrete connector configuration.
238#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
239pub struct SinkContract {
240    /// Durability and checkpoint-commit semantics.
241    pub consistency: SinkConsistency,
242    /// Valid runtime placement model.
243    pub topology: SinkTopology,
244    /// Strongest supported input update model.
245    pub input_mode: SinkInputMode,
246    /// True only for a built-in sink whose immutable phase-one and fenced
247    /// external cursor protocol is certified for multi-node exact delivery.
248    cluster_exact_delivery_certified: bool,
249}
250
251impl SinkContract {
252    /// Construct a sink contract from its three explicit dimensions.
253    #[must_use]
254    pub const fn new(
255        consistency: SinkConsistency,
256        topology: SinkTopology,
257        input_mode: SinkInputMode,
258    ) -> Self {
259        Self {
260            consistency,
261            topology,
262            input_mode,
263            cluster_exact_delivery_certified: false,
264        }
265    }
266
267    /// Mark a built-in sink whose cluster exact-delivery protocol is a release gate.
268    #[must_use]
269    pub(crate) const fn with_cluster_exact_delivery_certification(mut self) -> Self {
270        self.cluster_exact_delivery_certified = true;
271        self
272    }
273
274    /// Whether this sink's complete multi-node exact-delivery protocol is certified.
275    #[doc(hidden)]
276    #[must_use]
277    pub const fn is_cluster_exact_delivery_certified(self) -> bool {
278        self.cluster_exact_delivery_certified
279    }
280
281    /// Whether this contract participates in checkpoint-owned external commit.
282    #[must_use]
283    pub const fn is_checkpoint_committable(self) -> bool {
284        matches!(self.consistency, SinkConsistency::CheckpointCommittable)
285    }
286
287    /// Whether this contract faithfully consumes inserts, updates, and retractions.
288    #[must_use]
289    pub const fn accepts_full_changelog(self) -> bool {
290        self.input_mode.accepts_full_changelog()
291    }
292}