pub mod envelope;
pub(crate) mod offset;
pub mod outbox;
pub mod tamper;
pub mod worker;
use std::sync::{
atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering},
Arc,
};
use std::time::Duration;
use tokio::sync::Notify;
use secrecy::SecretString;
#[inline]
pub(crate) fn now_unix_ms() -> u64 {
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64
}
#[inline]
pub(crate) fn now_unix_secs() -> u64 {
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
}
pub trait CredentialProvider: Send + Sync {
fn retrieve(&self) -> Option<SecretString>;
}
#[derive(Debug, Clone)]
pub struct CloudConfig {
pub api_url: String,
pub timeout_connect_ms: u64,
pub timeout_total_ms: u64,
pub retry_delay_ms: u64,
pub channel_size: usize,
pub rate_limit_default_secs: u64,
pub credential_poll_interval_ms: u64,
pub fallback_max_bytes: u64,
pub batch_max_events: usize,
pub batch_max_wait_ms: u64,
}
impl Default for CloudConfig {
fn default() -> Self {
Self {
api_url: "https://app.openlatch.ai".into(),
timeout_connect_ms: 5000,
timeout_total_ms: 10000,
retry_delay_ms: 2000,
channel_size: 1000,
rate_limit_default_secs: 30,
credential_poll_interval_ms: 60_000,
fallback_max_bytes: 50 * 1024 * 1024,
batch_max_events: 50,
batch_max_wait_ms: 5000,
}
}
}
#[derive(Debug, Clone)]
pub struct CloudEvent {
pub envelope: serde_json::Value,
pub agent_id: String,
}
#[derive(Debug)]
pub enum CloudError {
AuthError,
RateLimit {
retry_after_secs: u64,
},
ServerError,
Network,
ClientError(u16),
}
impl std::fmt::Display for CloudError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CloudError::AuthError => {
write!(f, "cloud auth error (401/403) — API key invalid or revoked")
}
CloudError::RateLimit { retry_after_secs } => {
write!(
f,
"cloud rate limit (429) — retry after {retry_after_secs}s"
)
}
CloudError::ServerError => write!(f, "cloud server error (5xx)"),
CloudError::Network => write!(f, "cloud network error — endpoint unreachable"),
CloudError::ClientError(code) => write!(f, "cloud client error ({code})"),
}
}
}
#[derive(Debug, Clone)]
pub struct CloudState {
pub auth_error: Arc<AtomicBool>,
pub forwarded_count: Arc<AtomicU64>,
pub last_sync_secs: Arc<AtomicU64>,
pub drop_count: Arc<AtomicU64>,
pub consecutive_drops: Arc<AtomicU64>,
pub no_credential: Arc<AtomicBool>,
pub consecutive_probe_failures: Arc<AtomicU32>,
pub drain_notify: Arc<Notify>,
pub consecutive_live_drops: Arc<AtomicU64>,
pub live_drops_window_start: Arc<AtomicU64>,
pub emergency_mode: Arc<AtomicBool>,
pub channel_high_water_start_ms: Arc<AtomicU64>,
}
impl CloudState {
pub fn new() -> Self {
Self {
auth_error: Arc::new(AtomicBool::new(false)),
forwarded_count: Arc::new(AtomicU64::new(0)),
last_sync_secs: Arc::new(AtomicU64::new(0)),
drop_count: Arc::new(AtomicU64::new(0)),
consecutive_drops: Arc::new(AtomicU64::new(0)),
no_credential: Arc::new(AtomicBool::new(true)),
consecutive_probe_failures: Arc::new(AtomicU32::new(0)),
drain_notify: Arc::new(Notify::new()),
consecutive_live_drops: Arc::new(AtomicU64::new(0)),
live_drops_window_start: Arc::new(AtomicU64::new(0)),
emergency_mode: Arc::new(AtomicBool::new(false)),
channel_high_water_start_ms: Arc::new(AtomicU64::new(0)),
}
}
pub fn notify_drain(&self) {
self.drain_notify.notify_one();
}
pub fn is_no_credential(&self) -> bool {
self.no_credential.load(Ordering::Relaxed)
}
pub fn set_no_credential(&self, missing: bool) {
self.no_credential.store(missing, Ordering::Relaxed);
}
pub fn is_auth_error(&self) -> bool {
self.auth_error.load(Ordering::Relaxed)
}
pub fn record_successful_forward(&self) {
self.record_successful_forwards(1);
}
pub fn record_successful_forwards(&self, n: u64) {
if n == 0 {
return;
}
self.forwarded_count.fetch_add(n, Ordering::Relaxed);
self.last_sync_secs
.store(now_unix_secs(), Ordering::Relaxed);
self.consecutive_drops.store(0, Ordering::Relaxed);
self.consecutive_live_drops.store(0, Ordering::Relaxed);
self.live_drops_window_start.store(0, Ordering::Relaxed);
}
pub fn record_live_drop(&self) {
let prev = self.consecutive_live_drops.fetch_add(1, Ordering::Relaxed);
if prev == 0 {
self.live_drops_window_start
.store(now_unix_ms(), Ordering::Relaxed);
}
}
pub fn consecutive_live_drops(&self) -> u64 {
self.consecutive_live_drops.load(Ordering::Relaxed)
}
pub fn live_drops_window_start_ms(&self) -> u64 {
self.live_drops_window_start.load(Ordering::Relaxed)
}
pub fn is_emergency_mode(&self) -> bool {
self.emergency_mode.load(Ordering::Relaxed)
}
pub fn note_channel_depth_pct(&self, pct: u64) {
let current = self.channel_high_water_start_ms.load(Ordering::Relaxed);
if pct >= worker::HIGH_WATER_TRIP_PCT {
if current == 0 {
self.channel_high_water_start_ms
.store(now_unix_ms(), Ordering::Relaxed);
}
} else if pct < worker::HIGH_WATER_CLEAR_PCT && current != 0 {
self.channel_high_water_start_ms.store(0, Ordering::Relaxed);
}
}
pub fn channel_high_water_window_ms(&self) -> u64 {
let start = self.channel_high_water_start_ms.load(Ordering::Relaxed);
if start == 0 {
0
} else {
now_unix_ms().saturating_sub(start)
}
}
pub(crate) fn set_emergency_mode(&self, on: bool) {
self.emergency_mode.store(on, Ordering::Relaxed);
}
pub fn clear_auth_error(&self) -> bool {
self.auth_error.swap(false, Ordering::Relaxed)
}
pub fn record_health_ok(&self) {
self.consecutive_drops.store(0, Ordering::Relaxed);
self.consecutive_probe_failures.store(0, Ordering::Relaxed);
}
pub fn record_probe_failure(&self) {
self.consecutive_probe_failures
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |n| n.checked_add(1))
.ok();
}
pub fn consecutive_probe_failures(&self) -> u32 {
self.consecutive_probe_failures.load(Ordering::Relaxed)
}
pub fn next_health_delay(&self) -> Duration {
let failures = self.consecutive_probe_failures();
if failures == 0 {
return Duration::from_secs(60);
}
let exp = (failures - 1).min(3);
let base_secs = 5u64.saturating_mul(1u64 << exp);
let capped_secs = base_secs.min(30);
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.subsec_nanos())
.unwrap_or(0);
let jitter_pct = 80 + u64::from(nanos % 41); Duration::from_millis(capped_secs * 1000 * jitter_pct / 100)
}
pub fn forwarded_count(&self) -> u64 {
self.forwarded_count.load(Ordering::Relaxed)
}
pub fn last_sync_secs(&self) -> u64 {
self.last_sync_secs.load(Ordering::Relaxed)
}
pub fn record_drop(&self) {
self.record_drops(1);
}
pub fn record_drops(&self, n: u64) {
if n == 0 {
return;
}
self.drop_count.fetch_add(n, Ordering::Relaxed);
self.consecutive_drops.fetch_add(n, Ordering::Relaxed);
}
pub fn drop_count(&self) -> u64 {
self.drop_count.load(Ordering::Relaxed)
}
pub fn consecutive_drops(&self) -> u64 {
self.consecutive_drops.load(Ordering::Relaxed)
}
}
impl Default for CloudState {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cloud_config_default_produces_expected_values() {
let cfg = CloudConfig::default();
assert_eq!(cfg.api_url, "https://app.openlatch.ai");
assert_eq!(cfg.timeout_connect_ms, 5000);
assert_eq!(cfg.timeout_total_ms, 10000);
assert_eq!(cfg.channel_size, 1000);
assert_eq!(cfg.retry_delay_ms, 2000);
assert_eq!(cfg.rate_limit_default_secs, 30);
assert_eq!(cfg.credential_poll_interval_ms, 60_000);
assert_eq!(cfg.fallback_max_bytes, 52_428_800);
}
#[test]
fn test_cloud_error_variants_have_display() {
let e = CloudError::AuthError;
assert!(format!("{e}").contains("auth error"));
let e = CloudError::RateLimit {
retry_after_secs: 42,
};
let s = format!("{e}");
assert!(s.contains("rate limit") && s.contains("42"));
let e = CloudError::ServerError;
assert!(format!("{e}").contains("server error"));
let e = CloudError::Network;
assert!(format!("{e}").contains("network"));
let e = CloudError::ClientError(404);
let s = format!("{e}");
assert!(s.contains("404"));
}
#[test]
fn test_cloud_state_defaults_to_no_auth_error() {
let state = CloudState::new();
assert!(!state.is_auth_error());
}
#[test]
fn test_cloud_state_reflects_atomic_transitions() {
let state = CloudState::new();
state.auth_error.store(true, Ordering::Relaxed);
assert!(state.is_auth_error());
state.auth_error.store(false, Ordering::Relaxed);
assert!(!state.is_auth_error());
}
#[test]
fn test_cloud_state_no_credential_defaults_true_and_toggles() {
let state = CloudState::new();
assert!(
state.is_no_credential(),
"new CloudState must start in no_credential state until the worker polls"
);
state.set_no_credential(false);
assert!(!state.is_no_credential());
state.set_no_credential(true);
assert!(state.is_no_credential());
}
#[test]
fn test_cloud_event_fields_accessible() {
let evt = CloudEvent {
envelope: serde_json::json!({"id": "evt_123"}),
agent_id: "agt_abc".to_string(),
};
assert_eq!(evt.agent_id, "agt_abc");
assert_eq!(evt.envelope["id"], "evt_123");
}
#[test]
fn test_cloud_state_new_initializes_forwarded_count_to_zero() {
let state = CloudState::new();
assert_eq!(state.forwarded_count(), 0);
}
#[test]
fn test_cloud_state_new_initializes_last_sync_secs_to_zero() {
let state = CloudState::new();
assert_eq!(state.last_sync_secs(), 0);
}
#[test]
fn test_cloud_state_record_successful_forward_increments_count() {
let state = CloudState::new();
state.record_successful_forward();
assert_eq!(state.forwarded_count(), 1);
state.record_successful_forward();
assert_eq!(state.forwarded_count(), 2);
}
#[test]
fn test_next_health_delay_healthy_is_sixty_seconds() {
let state = CloudState::new();
assert_eq!(state.next_health_delay(), Duration::from_secs(60));
}
#[test]
fn test_next_health_delay_degraded_in_expected_range() {
let state = CloudState::new();
state.record_probe_failure();
let d = state.next_health_delay();
assert!(d >= Duration::from_millis(4000) && d <= Duration::from_millis(6000));
for _ in 0..3 {
state.record_probe_failure();
}
let d = state.next_health_delay();
assert!(d >= Duration::from_millis(24_000) && d <= Duration::from_millis(36_000));
}
#[test]
fn test_record_live_drop_increments_and_stamps_window_start() {
let state = CloudState::new();
assert_eq!(state.consecutive_live_drops(), 0);
assert_eq!(state.live_drops_window_start_ms(), 0);
state.record_live_drop();
assert_eq!(state.consecutive_live_drops(), 1);
let window_start = state.live_drops_window_start_ms();
assert!(
window_start > 0,
"window start must be stamped on first drop"
);
state.record_live_drop();
state.record_live_drop();
assert_eq!(state.consecutive_live_drops(), 3);
assert_eq!(state.live_drops_window_start_ms(), window_start);
}
#[test]
fn test_record_successful_forward_clears_live_drop_streak() {
let state = CloudState::new();
state.record_live_drop();
state.record_live_drop();
assert_eq!(state.consecutive_live_drops(), 2);
state.record_successful_forward();
assert_eq!(state.consecutive_live_drops(), 0);
assert_eq!(state.live_drops_window_start_ms(), 0);
}
#[test]
fn test_emergency_mode_flag_round_trip() {
let state = CloudState::new();
assert!(!state.is_emergency_mode());
state.set_emergency_mode(true);
assert!(state.is_emergency_mode());
state.set_emergency_mode(false);
assert!(!state.is_emergency_mode());
}
#[test]
fn test_note_channel_depth_pct_hysteresis() {
let state = CloudState::new();
state.note_channel_depth_pct(state_pct_below_trip());
assert_eq!(state.channel_high_water_window_ms(), 0);
state.note_channel_depth_pct(state_pct_above_trip());
let first_start = state.channel_high_water_start_ms.load(Ordering::Relaxed);
assert!(first_start > 0);
state.note_channel_depth_pct(state_pct_in_band());
assert_eq!(
state.channel_high_water_start_ms.load(Ordering::Relaxed),
first_start
);
state.note_channel_depth_pct(state_pct_below_clear());
assert_eq!(state.channel_high_water_start_ms.load(Ordering::Relaxed), 0);
assert_eq!(state.channel_high_water_window_ms(), 0);
}
fn state_pct_below_trip() -> u64 {
worker::HIGH_WATER_TRIP_PCT.saturating_sub(10)
}
fn state_pct_above_trip() -> u64 {
worker::HIGH_WATER_TRIP_PCT + 1
}
fn state_pct_in_band() -> u64 {
(worker::HIGH_WATER_TRIP_PCT + worker::HIGH_WATER_CLEAR_PCT) / 2
}
fn state_pct_below_clear() -> u64 {
worker::HIGH_WATER_CLEAR_PCT.saturating_sub(10)
}
#[test]
fn test_record_health_ok_resets_probe_failures() {
let state = CloudState::new();
state.record_probe_failure();
state.record_probe_failure();
assert_eq!(state.consecutive_probe_failures(), 2);
state.record_health_ok();
assert_eq!(state.consecutive_probe_failures(), 0);
}
#[test]
fn test_cloud_state_record_successful_forward_sets_last_sync_secs() {
let before = now_unix_secs();
let state = CloudState::new();
state.record_successful_forward();
let after = now_unix_secs();
let recorded = state.last_sync_secs();
assert!(
recorded >= before,
"last_sync_secs should be >= before: {recorded} < {before}"
);
assert!(
recorded <= after,
"last_sync_secs should be <= after: {recorded} > {after}"
);
}
}