etdl-core 0.1.4

ETDL runtime: BranchMonitor, retry policies, SLA anomaly detection, chaos injection, and telemetry for reliability-aware event-driven services
Documentation
use crate::chaos::ChaosController;
use crate::sla::SlaTracker;
use std::sync::Arc;
use std::sync::Mutex;

pub struct BranchMonitor {
    node_id: String,
    sla_tracker: Arc<Mutex<SlaTracker>>,
    chaos: Arc<Mutex<ChaosController>>,
}

impl BranchMonitor {
    pub fn new(node_id: &str) -> Self {
        BranchMonitor {
            node_id: node_id.to_string(),
            sla_tracker: Arc::new(Mutex::new(SlaTracker::new())),
            chaos: Arc::new(Mutex::new(ChaosController::new())),
        }
    }

    pub fn with_sla(node_id: &str, sla_tracker: Arc<Mutex<SlaTracker>>) -> Self {
        BranchMonitor {
            node_id: node_id.to_string(),
            sla_tracker,
            chaos: Arc::new(Mutex::new(ChaosController::new())),
        }
    }

    pub fn with_chaos(node_id: &str, chaos: Arc<Mutex<ChaosController>>) -> Self {
        BranchMonitor {
            node_id: node_id.to_string(),
            sla_tracker: Arc::new(Mutex::new(SlaTracker::new())),
            chaos,
        }
    }

    pub fn record_branch(&mut self, outcome: &str, declared_probability: f64) {
        let should_chaos = {
            let mut chaos = self.chaos.lock().unwrap();
            chaos.should_inject_chaos(&self.node_id)
        };
        if should_chaos {
            return;
        }

        let mut sla = self.sla_tracker.lock().unwrap();
        let anomaly = sla.record(
            &self.node_id,
            outcome,
            declared_probability,
            true,
        );

        if anomaly {
            crate::telemetry::emit_anomaly_event(
                &self.node_id,
                outcome,
                declared_probability,
                sla.observed_frequency(&self.node_id, outcome),
            );
        }

        crate::telemetry::attach_node_span_attribute(&self.node_id);
    }

    pub fn record_failure(
        &mut self,
        operation_id: &str,
        error: &dyn std::error::Error,
        declared_probability: Option<f64>,
    ) {
        let key = format!("{}.failure", operation_id);
        let outcome = "FAILURE";

        if let Some(prob) = declared_probability {
            let mut sla = self.sla_tracker.lock().unwrap();
            let anomaly = sla.record(&key, outcome, prob, true);

            if anomaly {
                crate::telemetry::emit_anomaly_event(
                    &key,
                    outcome,
                    prob,
                    sla.observed_frequency(&key, outcome),
                );
            }
        }

        crate::telemetry::attach_node_span_attribute(&key);
        eprintln!(
            "[etdl] operation '{}' failed: {}",
            operation_id, error
        );
    }

    pub fn flush(&self) {
        let sla = self.sla_tracker.lock().unwrap();
        eprintln!(
            "[etdl] node '{}': {} evaluations recorded",
            self.node_id,
            sla.total_evaluations()
        );
    }
}

impl Drop for BranchMonitor {
    fn drop(&mut self) {
        self.flush();
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_record_branch() {
        let mut monitor = BranchMonitor::new("test_barrier");
        monitor.record_branch("SUCCESS", 0.95);
        monitor.record_branch("SUCCESS", 0.95);
        monitor.record_branch("FAILURE", 0.05);
    }
}