···186186 }
187187 }));
188188189189+ // Spawn QUIC reconciliation server (if configured)
190190+ let quic_handle = config.quic_listen_addr.map(|addr| {
191191+ tokio::spawn(task_monitor.instrument({
192192+ let db = db.clone();
193193+ let config = config.clone();
194194+ let cancel = cancel.clone();
195195+ async move {
196196+ if let Err(e) =
197197+ ramjet::server::quic::run_quic_server(addr, db, config, cancel).await
198198+ {
199199+ tracing::error!(error = %e, "QUIC reconciliation server failed");
200200+ }
201201+ }
202202+ }))
203203+ });
204204+189205 // Build router and bind
190206 let router = build_router(state);
191207 let listener = TcpListener::bind(config.listen_addr).await?;
···205221 backfill_handle,
206222 tokio_metrics_handle,
207223 );
224224+ if let Some(handle) = quic_handle {
225225+ let _ = handle.await;
226226+ }
208227209228 tracing::info!("ramjet shut down");
210229 Ok(())
+1
src/server/mod.rs
···44pub mod dictionary;
55pub mod health;
66pub mod metrics;
77+pub mod quic;
78pub mod reconciliation;
89pub mod tokio_metrics;
910pub mod websocket;
···3232///
3333/// Sizes are multiples of 25,000. The sketch stays at the current tier
3434/// until the record count crosses 50% into the next increment, then bumps.
3535-fn sketch_size_for_records(record_count: usize) -> usize {
3535+pub fn sketch_size_for_records(record_count: usize) -> usize {
3636 let mut size = SKETCH_INCREMENT;
3737 while record_count >= size + SKETCH_INCREMENT / 2 {
3838 size += SKETCH_INCREMENT;
···4141}
42424343/// Build a ByteSymbol for a record identified by (collection, rkey, cid).
4444-fn make_record_symbol(collection: &str, rkey: &str, cid: &[u8]) -> ByteSymbol {
4444+pub fn make_record_symbol(collection: &str, rkey: &str, cid: &[u8]) -> ByteSymbol {
4545 let mut bytes = Vec::with_capacity(collection.len() + 1 + rkey.len() + 1 + cid.len());
4646 bytes.extend_from_slice(collection.as_bytes());
4747 bytes.push(0x00);
···146146 }
147147}
148148149149-/// Build an RIBLT sketch from the records keyspace for a DID.
149149+/// Collect ByteSymbols for a DID's tracked records.
150150///
151151/// Scans all records, groups by (collection, rkey), takes the latest
152152-/// non-tombstone revision of each, and produces coded symbols.
153153-fn build_sketch(
152152+/// non-tombstone revision of each, and returns the symbols plus the
153153+/// current repo revision.
154154+pub fn collect_symbols(
154155 db: &FjallDb,
155156 did: &str,
156157 collection_filter: Option<&str>,
157157-) -> anyhow::Result<(Vec<u8>, u64, String)> {
158158+) -> anyhow::Result<(Vec<ByteSymbol>, String)> {
158159 let prefix = if let Some(coll) = collection_filter {
159160 keys::encode_collection_prefix(did, coll)
160161 } else {
···231232 }
232233 }
233234235235+ let rev = db.get_repo_state(did)?.map(|rs| rs.rev).unwrap_or_default();
236236+237237+ Ok((symbols, rev))
238238+}
239239+240240+/// Build an RIBLT sketch from the records keyspace for a DID.
241241+///
242242+/// Collects symbols and encodes them into an RibltFile.
243243+fn build_sketch(
244244+ db: &FjallDb,
245245+ did: &str,
246246+ collection_filter: Option<&str>,
247247+) -> anyhow::Result<(Vec<u8>, u64, String)> {
248248+ let (symbols, rev) = collect_symbols(db, did, collection_filter)?;
249249+234250 let record_count = symbols.len() as u64;
235251 let sketch_size = sketch_size_for_records(symbols.len());
236252···250266251267 let mut buf = Vec::new();
252268 file.write_to(&mut buf)?;
253253-254254- let rev = db.get_repo_state(did)?.map(|rs| rs.rev).unwrap_or_default();
255269256270 Ok((buf, record_count, rev))
257271}