codetether_agent/session/bus/sink.rs
1//! Durable sink trait and a no-op implementation for tests.
2//!
3//! Durable events (see [`SessionEvent::is_durable`]) are dispatched
4//! synchronously to an implementor of [`DurableSink`]. The sink is
5//! expected to buffer or persist the event before returning; the bus
6//! guarantees no durable event is silently dropped.
7//!
8//! [`SessionEvent::is_durable`]: crate::session::SessionEvent::is_durable
9
10use std::sync::Arc;
11
12use crate::session::SessionEvent;
13
14/// A write-ahead sink for durable [`SessionEvent`]s.
15///
16/// Implementors are responsible for persisting or queueing the event
17/// before returning from [`DurableSink::write`]. Errors must be reported
18/// through the returned `Result` so callers can fall back to logging —
19/// **never swallow durable-write failures silently**.
20///
21/// # Thread-safety
22///
23/// Implementations must be `Send + Sync + 'static` and are shared behind
24/// `Arc`. Interior mutability (e.g. `tokio::sync::Mutex`) is the
25/// implementor's responsibility.
26///
27/// # Examples
28///
29/// A minimal in-memory sink useful in tests:
30///
31/// ```rust
32/// use std::sync::{Arc, Mutex};
33/// use codetether_agent::session::{DurableSink, SessionEvent};
34///
35/// struct Collector(Arc<Mutex<Vec<String>>>);
36/// impl DurableSink for Collector {
37/// fn write(&self, event: &SessionEvent) -> std::io::Result<()> {
38/// self.0.lock().unwrap().push(format!("{event:?}"));
39/// Ok(())
40/// }
41/// }
42///
43/// let c = Collector(Arc::new(Mutex::new(Vec::new())));
44/// c.write(&SessionEvent::Done).unwrap();
45/// ```
46pub trait DurableSink: Send + Sync + 'static {
47 /// Persist one durable event.
48 ///
49 /// # Errors
50 ///
51 /// Returns an `io::Error` if the underlying storage (file, network,
52 /// object store) refused the write. Callers log and continue — a
53 /// bus emit never panics on sink failure.
54 fn write(&self, event: &SessionEvent) -> std::io::Result<()>;
55}
56
57/// A [`DurableSink`] that drops every event. Useful as a default when the
58/// caller has no persistence requirement (e.g. in unit tests or one-shot
59/// CLI invocations).
60///
61/// # Examples
62///
63/// ```rust
64/// use codetether_agent::session::{DurableSink, NoopSink, SessionEvent};
65///
66/// let sink = NoopSink;
67/// sink.write(&SessionEvent::Done).unwrap();
68/// ```
69#[derive(Debug, Default, Clone, Copy)]
70pub struct NoopSink;
71
72impl DurableSink for NoopSink {
73 fn write(&self, _event: &SessionEvent) -> std::io::Result<()> {
74 Ok(())
75 }
76}
77
78/// Type alias for the shared sink handle used inside [`crate::session::SessionBus`].
79pub(crate) type SharedSink = Arc<dyn DurableSink>;