1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
use crate::chaos::ChaosController;
use crate::observation::{
generate_observation_id, now_rfc3339, NoopSink, ReliabilityObservation, SharedSink,
};
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>>,
observation_sink: SharedSink,
service: Option<String>,
service_version: Option<String>,
deployment: Option<String>,
build_ref: Option<String>,
}
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())),
observation_sink: Arc::new(NoopSink),
service: None,
service_version: None,
deployment: None,
build_ref: None,
}
}
/// Attach an observation sink (JSON Lines, OTel, database adapter, ...).
pub fn with_sink(mut self, sink: SharedSink) -> Self {
self.observation_sink = sink;
self
}
/// Identify the service/component this monitor runs in, for the
/// observations it emits.
pub fn with_service(mut self, service: impl Into<String>) -> Self {
self.service = Some(service.into());
self
}
/// Identify the software version generating observations, distinct from
/// the deployment slot. Lets an analyst compare "predictions from build
/// v1" against "observations generated by v1".
pub fn with_service_version(mut self, version: impl Into<String>) -> Self {
self.service_version = Some(version.into());
self
}
/// Identify the deployment/environment (e.g. `prod-us-east-1`).
pub fn with_deployment(mut self, deployment: impl Into<String>) -> Self {
self.deployment = Some(deployment.into());
self
}
/// A stable reference to the compiled reliability artifact/build that
/// produced this service, so observations can be traced back to the
/// model that predicted them without embedding the whole artifact.
pub fn with_build_ref(mut self, build_ref: impl Into<String>) -> Self {
self.build_ref = Some(build_ref.into());
self
}
/// Build a runtime observation stamped with this monitor's identity.
fn observation(&self, outcome: &str) -> ReliabilityObservation {
ReliabilityObservation {
id: generate_observation_id(),
event: self.node_id.clone(),
timestamp: now_rfc3339(),
service: self.service.clone(),
operation: None,
environment: None,
deployment: self.deployment.clone(),
service_version: self.service_version.clone(),
build_ref: self.build_ref.clone(),
outcome: outcome.to_string(),
conditions: Vec::new(),
duration_ms: None,
trace_id: None,
}
}
/// Record an immutable failure observation for offline analysis.
/// Lightweight: does not run statistics, query databases, or call AI.
pub fn record_failure_observation(&self, observation: ReliabilityObservation) {
self.observation_sink.emit(&observation);
}
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())),
observation_sink: Arc::new(NoopSink),
service: None,
service_version: None,
deployment: None,
build_ref: None,
}
}
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,
observation_sink: Arc::new(NoopSink),
service: None,
service_version: None,
deployment: None,
build_ref: None,
}
}
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),
);
}
drop(sla);
crate::telemetry::attach_node_span_attribute(&self.node_id);
// Record what happened. The declared probability is the prediction;
// it lives in the compiled artifact/build manifest, not on every
// observation, so it is not duplicated here (it would go stale on
// recalibration). The runtime only records data; interpretation
// (predicted vs observed) happens offline, in etdl-reliability.
self.observation_sink.emit(&self.observation(outcome));
}
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);
let mut o = self.observation("failed");
o.event = key;
o.operation = Some(operation_id.to_string());
self.observation_sink.emit(&o);
}
/// Record that `operation_id` completed *without* failing. Must be
/// called on the same `"{operation_id}.failure"` SLA key
/// [`record_failure`] uses, with `occurred = false`, or that key's
/// rolling window only ever sees failures (every entry `record_failure`
/// ever pushes) and its observed frequency is permanently `1.0` —
/// which made [`record_failure`]'s anomaly check fire unconditionally
/// for any operation that failed at least a handful of times over its
/// lifetime (`sla::MIN_OBSERVATIONS`), regardless of its actual overall
/// failure rate. Generated code calls this from
/// the operation's `Ok` arm whenever it calls `record_failure` from the
/// matching `Err` arm (i.e. whenever `onFailureProbabilitySource` is
/// declared and resolves) — see `codegen/rust.rs`'s `Ok(_result) =>`
/// arm.
pub fn record_success(&mut self, operation_id: &str, 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, false);
if anomaly {
crate::telemetry::emit_anomaly_event(
&key,
outcome,
prob,
sla.observed_frequency(&key, outcome),
);
}
}
}
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);
}
#[derive(Debug)]
struct FakeError;
impl std::fmt::Display for FakeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "fake error")
}
}
impl std::error::Error for FakeError {}
/// Regression test for the false-alarm bug: `record_failure` alone
/// pushes every call into a `"{op}.failure"`-only SLA window, so its
/// observed frequency was permanently `1.0` regardless of the
/// operation's actual overall failure rate — any operation with a
/// declared failure probability below `1.0 - threshold` would
/// eventually, and permanently, be flagged anomalous. `record_success`
/// must be called on the same key with `occurred = false` (as
/// generated code now does in the `Ok` arm) so the window reflects the
/// operation's real success/failure mix.
#[test]
fn record_success_keeps_observed_frequency_meaningful_not_permanently_one() {
let mut monitor = BranchMonitor::new("op");
// 5% declared failure probability, 20 attempts: 1 failure, 19
// successes — exactly matching the declared rate.
for _ in 0..19 {
monitor.record_success("checkout", Some(0.05));
}
monitor.record_failure("checkout", &FakeError, Some(0.05));
let observed = monitor
.sla_tracker
.lock()
.unwrap()
.observed_frequency("checkout.failure", "FAILURE");
// Without record_success, this would be 1.0 (every recorded entry
// is a failure) regardless of how rarely the operation actually
// fails. With it, the window reflects the true 1-in-20 rate.
assert!(
(observed - 0.05).abs() < 1e-9,
"expected observed frequency ~0.05 (1 failure in 20 attempts), got {observed} \
(1.0 would mean the false-alarm bug regressed)"
);
}
}