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}