1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
//! Pluggable storage backends for dedup and reconciliation.
//!
//! This module provides trait-based abstractions for idempotency key tracking
//! and reconciliation request queuing, enabling production deployments to use
//! distributed backends (Redis, PostgreSQL, etc.) while maintaining simple
//! in-memory implementations for development and testing.
//!
use crateReconciliationRequest;
use Future;
use Pin;
use Poll;
/// A conformance suite an embedder runs against its own backend, to establish
/// the durability and atomicity that `is_durable()`, `cluster_safe()` and
/// `DurableAuditSink::durability()` only *declare*.
///
/// Covers all three storage traits: [`DedupStorage`], [`ReconciliationStorage`]
/// and [`crate::observability::audit_sink::DurableAuditSink`].
/// `dyn`-safe boxed async future for storage trait methods.
///
/// This is the return type of [`DedupStorage::first_seen`]. Implementations
/// box their async body with `Box::pin(async move { ... })`.
pub type BoxFuture<'a, T> = ;
/// Core implementation of the synchronous-poll drive pattern.
///
/// Both [`drive_dedup_future`] and [`drive_reconciliation_future`] delegate here.
/// Having one code path ensures the noop-waker poll logic is maintained in a
/// single place while preserving the distinct diagnostic messages.
/// Drive a `BoxFuture` to completion synchronously.
///
/// This is provided for **sync receive paths** that cannot `.await` a future:
/// - `receive_push_with_dedup_sync` (dedup)
/// - `receive_with_mdn_with_reliability` and internal AS4 helpers (reconciliation)
///
/// All in-memory storage implementations return a `Poll::Ready` future immediately
/// and this function resolves them in O(1).
///
/// # Panics
/// Panics with a clear message if the future returns `Poll::Pending`, which
/// indicates an async backend being called from the sync path. For dedup, switch
/// to `receive_push_with_dedup_async`. For reconciliation sync callers, ensure
/// only in-memory backends are used on sync paths.
pub
/// Drive a [`ReconciliationStorage`] `BoxFuture` to completion synchronously.
///
/// Provided for sync receive paths (`receive_with_mdn_with_reliability`, internal
/// AS4 pull/push helpers) that hold a `&dyn ReconciliationStorage` and cannot `.await`.
/// In-memory backends resolve immediately (`Poll::Ready`); network-backed backends must
/// not be used from sync paths — this function will panic with a diagnostic if they do.
///
/// # Panics
/// Panics if the future returns `Poll::Pending` (async backend on a sync path).
pub
/// Trait for distributed dedup state storage.
///
/// The single required method [`first_seen`](Self::first_seen) is **async** via
/// a `BoxFuture` return so that production backends backed by Redis, PostgreSQL,
/// DynamoDB, or SlateDB can implement it natively without
/// `block_in_place` / `Handle::current().block_on(…)` boilerplate.
///
/// In-memory implementations simply wrap synchronous logic in `Box::pin(async move { … })` —
/// the future resolves immediately on the first `.await`.
///
/// Implementations must provide strict idempotency guarantees: each idempotency key
/// is seen exactly once, and lock poison/infrastructure failures must fail-closed.
///
/// # Implementing it
///
/// A process-local backend wraps synchronous logic; the future resolves on the
/// first `.await`.
///
/// ```
/// use asx_rs::storage::{BoxFuture, DedupStorage};
/// use std::collections::HashSet;
/// use std::sync::Mutex;
///
/// #[derive(Debug, Default)]
/// struct MyMemoryStore {
/// seen: Mutex<HashSet<String>>,
/// }
///
/// impl DedupStorage for MyMemoryStore {
/// fn is_durable(&self) -> bool {
/// false // forgets everything on restart
/// }
///
/// fn first_seen<'a>(&'a self, key: &'a str) -> BoxFuture<'a, asx_rs::Result<bool>> {
/// Box::pin(async move {
/// // A poisoned lock must fail closed, never report "not seen".
/// let mut seen = self.seen.lock().map_err(|_| {
/// asx_rs::AsxError::new(
/// asx_rs::ErrorCode::ReliabilityFailure,
/// "dedup mutex poisoned",
/// asx_rs::ErrorContext::new("my_memory_store"),
/// )
/// })?;
/// Ok(seen.insert(key.to_string()))
/// })
/// }
/// }
///
/// # tokio_test_helper();
/// # fn tokio_test_helper() {
/// let store = MyMemoryStore::default();
/// let rt = tokio::runtime::Builder::new_current_thread().build().unwrap();
/// assert!(rt.block_on(store.first_seen("msg-1")).unwrap()); // first time
/// assert!(!rt.block_on(store.first_seen("msg-1")).unwrap()); // duplicate
/// # }
/// ```
///
/// A durable, cluster-safe backend declares both properties and does the work in
/// the returned future — one round trip, no `block_on`:
///
/// ```text
/// impl DedupStorage for RedisDedup {
/// fn is_durable(&self) -> bool { true }
/// fn cluster_safe(&self) -> bool { true }
///
/// fn first_seen<'a>(&'a self, key: &'a str) -> BoxFuture<'a, asx_rs::Result<bool>> {
/// Box::pin(async move {
/// // SET key 1 NX EX <window> -> Ok(true) when the key was created.
/// self.redis.set_nx(key, self.window).await.map_err(storage_err)
/// })
/// }
/// }
/// ```
/// Trait for distributed reconciliation request queuing.
/// Implementations must preserve order and prevent duplicate reconciliation attempts.
///
/// # Stability
///
/// `ReconciliationStorage` is part of the public API and is accepted by
/// several functions in [`crate::presets`] and [`crate::reliability`], but its
/// **trait shape — method signatures, return types, and error variants — is
/// subject to breaking change** while this crate is at `0.x`.
///
/// If you implement this trait in downstream code, pin to an exact `asx-rs`
/// version in your `Cargo.toml` to avoid unexpected breakage:
///
/// ```toml
/// [dependencies]
/// asx-rs = "=0.13.0" # exact-version pin — ReconciliationStorage is not yet stable
/// ```
///
/// The sealed-trait pattern is intentionally not used here so that downstream
/// crates can provide production-grade backends (PostgreSQL, Redis, etc.) before
/// this crate reaches `1.0`. Once the trait stabilises the exact-pin
/// requirement will be lifted and a crate-level migration notice will be
/// published.
pub use ;
pub use DurableInMemoryDedupBackend;