@@ -28,6 +28,11 @@ use crate::sync::outbound::vector::{PendingVectorDelete, PendingVectorInsert};
2828pub trait SyncDelegate : Send + Sync + ' static {
2929 /// Get all pending CRDT deltas to push to Origin.
3030 fn pending_deltas ( & self ) -> Vec < PendingDelta > ;
31+ /// Assign a stable stream seq to a pending CRDT delta (first-send only).
32+ ///
33+ /// No-op if the delta already has a non-zero seq, so the same seq is
34+ /// reused on reconnect re-sends and Origin can deduplicate.
35+ async fn set_pending_delta_seq ( & self , mutation_id : u64 , seq : u64 ) ;
3136 /// Acknowledge deltas up to the given mutation_id.
3237 fn acknowledge ( & self , mutation_id : u64 ) ;
3338 /// Reject a specific delta (rollback optimistic state).
@@ -73,40 +78,189 @@ pub trait SyncDelegate: Send + Sync + 'static {
7378 fn handle_array_reject ( & self , msg : & nodedb_types:: sync:: wire:: ArrayRejectMsg ) ;
7479
7580 // ── Columnar ─────────────────────────────────────────────────────────────
76- fn pending_columnar_batches ( & self ) -> Vec < PendingColumnarBatch > ;
77- fn acknowledge_columnar_batch ( & self , batch_id : u64 ) ;
78- fn reject_columnar_batch ( & self , batch : PendingColumnarBatch ) ;
81+ /// Drain up to `PUSH_DRAIN_LIMIT` pending batches from durable storage,
82+ /// skipping any currently in-flight entries.
83+ ///
84+ /// Returns `(durable_key, batch)` pairs. On successful send, call
85+ /// `mark_columnar_batch_in_flight`. The durable entry is deleted only when
86+ /// Origin's ack arrives via `ack_columnar_batch_in_flight`.
87+ async fn pending_columnar_batches ( & self ) -> Vec < ( Vec < u8 > , PendingColumnarBatch ) > ;
88+ /// Record that a columnar batch has been sent and is awaiting Origin ack.
89+ async fn mark_columnar_batch_in_flight ( & self , batch_id : u64 , durable_key : Vec < u8 > ) ;
90+ /// On Origin ack: remove in-flight record and delete the durable entry.
91+ async fn ack_columnar_batch_in_flight ( & self , batch_id : u64 ) ;
92+ /// Delete the durable entry for a confirmed batch directly (used for
93+ /// un-encodable entries that must be discarded at send time).
94+ async fn acknowledge_columnar_batch ( & self , durable_key : Vec < u8 > ) ;
7995
8096 // ── Vector ───────────────────────────────────────────────────────────────
81- fn pending_vector_inserts ( & self ) -> Vec < PendingVectorInsert > ;
82- fn acknowledge_vector_insert ( & self , batch_id : u64 ) ;
83- fn reject_vector_insert ( & self , entry : PendingVectorInsert ) ;
97+ /// Drain up to `PUSH_DRAIN_LIMIT` pending insert entries, skipping in-flight.
98+ async fn pending_vector_inserts ( & self ) -> Vec < ( Vec < u8 > , PendingVectorInsert ) > ;
99+ /// Record that a vector insert has been sent and is awaiting Origin ack.
100+ async fn mark_vector_insert_in_flight ( & self , batch_id : u64 , durable_key : Vec < u8 > ) ;
101+ /// On Origin ack: remove in-flight record and delete the durable entry.
102+ async fn ack_vector_insert_in_flight ( & self , batch_id : u64 ) ;
103+ /// Delete the durable insert entry directly (for un-encodable entries).
104+ async fn acknowledge_vector_insert ( & self , durable_key : Vec < u8 > ) ;
84105
85- fn pending_vector_deletes ( & self ) -> Vec < PendingVectorDelete > ;
86- fn acknowledge_vector_delete ( & self , batch_id : u64 ) ;
87- fn reject_vector_delete ( & self , entry : PendingVectorDelete ) ;
106+ /// Drain up to `PUSH_DRAIN_LIMIT` pending delete entries, skipping in-flight.
107+ async fn pending_vector_deletes ( & self ) -> Vec < ( Vec < u8 > , PendingVectorDelete ) > ;
108+ /// Record that a vector delete has been sent and is awaiting Origin ack.
109+ async fn mark_vector_delete_in_flight ( & self , batch_id : u64 , durable_key : Vec < u8 > ) ;
110+ /// On Origin ack: remove in-flight record and delete the durable entry.
111+ async fn ack_vector_delete_in_flight ( & self , batch_id : u64 ) ;
112+ /// Delete the durable delete entry directly (for un-encodable entries).
113+ async fn acknowledge_vector_delete ( & self , durable_key : Vec < u8 > ) ;
88114
89115 // ── FTS ──────────────────────────────────────────────────────────────────
90- fn pending_fts_indexes ( & self ) -> Vec < PendingFtsIndex > ;
91- fn acknowledge_fts_index ( & self , batch_id : u64 ) ;
92- fn reject_fts_index ( & self , entry : PendingFtsIndex ) ;
116+ /// Drain up to `PUSH_DRAIN_LIMIT` pending index entries, skipping in-flight.
117+ async fn pending_fts_indexes ( & self ) -> Vec < ( Vec < u8 > , PendingFtsIndex ) > ;
118+ /// Record that an FTS index entry has been sent and is awaiting Origin ack.
119+ async fn mark_fts_index_in_flight ( & self , batch_id : u64 , durable_key : Vec < u8 > ) ;
120+ /// On Origin ack: remove in-flight record and delete the durable entry.
121+ async fn ack_fts_index_in_flight ( & self , batch_id : u64 ) ;
122+ /// Delete the durable index entry directly (for un-encodable entries).
123+ async fn acknowledge_fts_index ( & self , durable_key : Vec < u8 > ) ;
93124
94- fn pending_fts_deletes ( & self ) -> Vec < PendingFtsDelete > ;
95- fn acknowledge_fts_delete ( & self , batch_id : u64 ) ;
96- fn reject_fts_delete ( & self , entry : PendingFtsDelete ) ;
125+ /// Drain up to `PUSH_DRAIN_LIMIT` pending FTS delete entries, skipping in-flight.
126+ async fn pending_fts_deletes ( & self ) -> Vec < ( Vec < u8 > , PendingFtsDelete ) > ;
127+ /// Record that an FTS delete entry has been sent and is awaiting Origin ack.
128+ async fn mark_fts_delete_in_flight ( & self , batch_id : u64 , durable_key : Vec < u8 > ) ;
129+ /// On Origin ack: remove in-flight record and delete the durable entry.
130+ async fn ack_fts_delete_in_flight ( & self , batch_id : u64 ) ;
131+ /// Delete the durable delete entry directly (for un-encodable entries).
132+ async fn acknowledge_fts_delete ( & self , durable_key : Vec < u8 > ) ;
97133
98134 // ── Spatial ──────────────────────────────────────────────────────────────
99- fn pending_spatial_inserts ( & self ) -> Vec < PendingSpatialInsert > ;
100- fn acknowledge_spatial_insert ( & self , batch_id : u64 ) ;
101- fn reject_spatial_insert ( & self , entry : PendingSpatialInsert ) ;
135+ /// Drain up to `PUSH_DRAIN_LIMIT` pending insert entries, skipping in-flight.
136+ async fn pending_spatial_inserts ( & self ) -> Vec < ( Vec < u8 > , PendingSpatialInsert ) > ;
137+ /// Record that a spatial insert has been sent and is awaiting Origin ack.
138+ async fn mark_spatial_insert_in_flight ( & self , batch_id : u64 , durable_key : Vec < u8 > ) ;
139+ /// On Origin ack: remove in-flight record and delete the durable entry.
140+ async fn ack_spatial_insert_in_flight ( & self , batch_id : u64 ) ;
141+ /// Delete the durable insert entry directly (for un-encodable entries).
142+ async fn acknowledge_spatial_insert ( & self , durable_key : Vec < u8 > ) ;
102143
103- fn pending_spatial_deletes ( & self ) -> Vec < PendingSpatialDelete > ;
104- fn acknowledge_spatial_delete ( & self , batch_id : u64 ) ;
105- fn reject_spatial_delete ( & self , entry : PendingSpatialDelete ) ;
144+ /// Drain up to `PUSH_DRAIN_LIMIT` pending spatial delete entries, skipping in-flight.
145+ async fn pending_spatial_deletes ( & self ) -> Vec < ( Vec < u8 > , PendingSpatialDelete ) > ;
146+ /// Record that a spatial delete has been sent and is awaiting Origin ack.
147+ async fn mark_spatial_delete_in_flight ( & self , batch_id : u64 , durable_key : Vec < u8 > ) ;
148+ /// On Origin ack: remove in-flight record and delete the durable entry.
149+ async fn ack_spatial_delete_in_flight ( & self , batch_id : u64 ) ;
150+ /// Delete the durable delete entry directly (for un-encodable entries).
151+ async fn acknowledge_spatial_delete ( & self , durable_key : Vec < u8 > ) ;
106152
107153 // ── Timeseries ───────────────────────────────────────────────────────────
108- fn pending_timeseries_batches ( & self ) -> Vec < PendingTimeseriesBatch > ;
109- /// Acknowledge all pending batches for a collection (Origin confirmed receipt).
110- fn acknowledge_timeseries_collection ( & self , collection : & str ) ;
111- fn reject_timeseries_batch ( & self , batch : PendingTimeseriesBatch ) ;
154+ /// Drain up to `PUSH_DRAIN_LIMIT` pending batches, skipping in-flight entries.
155+ async fn pending_timeseries_batches ( & self ) -> Vec < ( Vec < u8 > , PendingTimeseriesBatch ) > ;
156+ /// Record that a timeseries batch has been sent and is awaiting Origin ack.
157+ ///
158+ /// Keyed by `stream_seq` because `TimeseriesAckMsg` echoes `applied_seq`
159+ /// but not `batch_id`.
160+ async fn mark_timeseries_batch_in_flight ( & self , stream_seq : u64 , durable_key : Vec < u8 > ) ;
161+ /// On Origin ack: delete all durable entries whose seq ≤ `applied_seq`.
162+ async fn ack_timeseries_batches_through_seq ( & self , applied_seq : u64 ) ;
163+ /// Delete the durable entry directly (for empty/un-encodable batches).
164+ async fn acknowledge_timeseries_batch ( & self , durable_key : Vec < u8 > ) ;
165+
166+ // ── Stable seq persistence ────────────────────────────────────────────────
167+
168+ /// Persist an assigned stream seq into the durable columnar entry at `key`.
169+ ///
170+ /// Must be called before sending the frame. If this returns an error the
171+ /// caller must NOT send — it should retain the entry for the next drain tick.
172+ async fn persist_columnar_seq (
173+ & self ,
174+ key : & [ u8 ] ,
175+ batch : & PendingColumnarBatch ,
176+ ) -> Result < ( ) , crate :: error:: LiteError > ;
177+
178+ /// Persist an assigned stream seq into the durable timeseries entry at `key`.
179+ async fn persist_timeseries_seq (
180+ & self ,
181+ key : & [ u8 ] ,
182+ batch : & PendingTimeseriesBatch ,
183+ ) -> Result < ( ) , crate :: error:: LiteError > ;
184+
185+ /// Persist an assigned stream seq into the durable vector insert entry at `key`.
186+ async fn persist_vector_insert_seq (
187+ & self ,
188+ key : & [ u8 ] ,
189+ insert : & PendingVectorInsert ,
190+ ) -> Result < ( ) , crate :: error:: LiteError > ;
191+
192+ /// Persist an assigned stream seq into the durable vector delete entry at `key`.
193+ async fn persist_vector_delete_seq (
194+ & self ,
195+ key : & [ u8 ] ,
196+ delete : & PendingVectorDelete ,
197+ ) -> Result < ( ) , crate :: error:: LiteError > ;
198+
199+ /// Persist an assigned stream seq into the durable FTS index entry at `key`.
200+ async fn persist_fts_index_seq (
201+ & self ,
202+ key : & [ u8 ] ,
203+ entry : & PendingFtsIndex ,
204+ ) -> Result < ( ) , crate :: error:: LiteError > ;
205+
206+ /// Persist an assigned stream seq into the durable FTS delete entry at `key`.
207+ async fn persist_fts_delete_seq (
208+ & self ,
209+ key : & [ u8 ] ,
210+ entry : & PendingFtsDelete ,
211+ ) -> Result < ( ) , crate :: error:: LiteError > ;
212+
213+ /// Persist an assigned stream seq into the durable spatial insert entry at `key`.
214+ async fn persist_spatial_insert_seq (
215+ & self ,
216+ key : & [ u8 ] ,
217+ insert : & PendingSpatialInsert ,
218+ ) -> Result < ( ) , crate :: error:: LiteError > ;
219+
220+ /// Persist an assigned stream seq into the durable spatial delete entry at `key`.
221+ async fn persist_spatial_delete_seq (
222+ & self ,
223+ key : & [ u8 ] ,
224+ delete : & PendingSpatialDelete ,
225+ ) -> Result < ( ) , crate :: error:: LiteError > ;
226+
227+ // ── Reconnect ────────────────────────────────────────────────────────────
228+
229+ /// Clear all engine in-flight maps on reconnect.
230+ ///
231+ /// The durable entries are still in storage and will be re-drained on the
232+ /// next push tick. Origin's idempotent gate deduplicates re-sent batches.
233+ async fn clear_engine_in_flight ( & self ) ;
234+
235+ // ── Producer state ───────────────────────────────────────────────────────
236+
237+ /// Durably persist the server-assigned `producer_id` and `accepted_epoch`
238+ /// so they survive process restart and can be reloaded on reconnect.
239+ ///
240+ /// Called after every successful handshake acknowledgement.
241+ async fn persist_producer_state ( & self , producer_id : u64 , accepted_epoch : u64 ) ;
242+
243+ /// Load the last-persisted `producer_id` and `accepted_epoch`.
244+ ///
245+ /// Returns `(0, 0)` if no state has been persisted yet (first run).
246+ /// Called at the start of each connection attempt so the client has its
247+ /// identity available before the outbound handshake is built.
248+ async fn load_producer_state ( & self ) -> ( u64 , u64 ) ;
249+
250+ // ── Stream sequence ──────────────────────────────────────────────────────
251+
252+ /// Assign the next monotonic sequence number for the given `stream_id`.
253+ ///
254+ /// Delegates to `StreamSeqTracker::next_seq` which persists before
255+ /// returning (persist-before-send invariant). On storage error, logs a
256+ /// warning and returns `0` — a zero seq applies unconditionally on Origin,
257+ /// so this is safe degradation with no data loss.
258+ async fn next_stream_seq ( & self , stream_id : u64 ) -> u64 ;
259+
260+ /// Record that Origin has applied `applied_seq` for `stream_id`.
261+ ///
262+ /// Delegates to `StreamSeqTracker::record_ack`. Errors are logged and
263+ /// ignored — the last_assigned frontier already prevents re-sending
264+ /// un-acked seqs, so this is a refinement of the acknowledged frontier.
265+ async fn record_stream_ack ( & self , stream_id : u64 , applied_seq : u64 ) ;
112266}
0 commit comments