1#![allow(dead_code)]
16
17use std::collections::HashMap;
18use std::error::Error;
19use std::fmt;
20
21use chrono::Utc;
22use serde::Serialize;
23use serde_json::Value;
24
25use crate::ir_nodes::{
26 IRFabric, IRIntentionOperation, IRIntentionTree, IRManifest, IRObserve, IRProgram,
27 IRResource,
28};
29
30pub const VALID_DERIVATIONS: &[&str] = &["axiomatic", "observed", "inferred", "mutated"];
36
37#[derive(Debug, Clone, PartialEq, Serialize)]
39pub struct LambdaEnvelope {
40 pub c: f64,
42 pub tau: String,
44 pub rho: String,
46 pub delta: String,
48}
49
50impl LambdaEnvelope {
51 pub fn new(c: f64, tau: String, rho: String, delta: String) -> Self {
55 assert!(
56 (0.0..=1.0).contains(&c),
57 "LambdaEnvelope.c must be in [0.0, 1.0]; got {c}"
58 );
59 assert!(
60 VALID_DERIVATIONS.contains(&delta.as_str()),
61 "LambdaEnvelope.delta must be one of {VALID_DERIVATIONS:?}; got '{delta}'"
62 );
63 LambdaEnvelope { c, tau, rho, delta }
64 }
65
66 pub fn decayed(&self, to_certainty: f64) -> Self {
68 LambdaEnvelope::new(to_certainty, self.tau.clone(), self.rho.clone(), self.delta.clone())
69 }
70}
71
72pub fn now_iso() -> String {
74 Utc::now().to_rfc3339()
75}
76
77pub fn make_envelope(c: f64, rho: &str, delta: &str, tau: Option<String>) -> LambdaEnvelope {
79 LambdaEnvelope::new(
80 c,
81 tau.unwrap_or_else(now_iso),
82 rho.to_string(),
83 delta.to_string(),
84 )
85}
86
87pub const BLAME_CALLEE: &str = "CT-1";
93
94pub const BLAME_CALLER: &str = "CT-2";
97
98pub const BLAME_INFRASTRUCTURE: &str = "CT-3";
100
101#[derive(Debug)]
103pub struct HandlerError {
104 pub message: String,
105 pub blame: &'static str,
106 pub kind: HandlerErrorKind,
107 pub cause: Option<Box<dyn Error + Send + Sync + 'static>>,
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113pub enum HandlerErrorKind {
114 Callee,
116 Caller,
118 Infrastructure,
120 NetworkPartition,
122 LeaseExpired,
124 HandlerUnavailable,
126}
127
128impl HandlerError {
129 pub fn callee(msg: impl Into<String>) -> Self {
130 Self { message: msg.into(), blame: BLAME_CALLEE, kind: HandlerErrorKind::Callee, cause: None }
131 }
132
133 pub fn caller(msg: impl Into<String>) -> Self {
134 Self { message: msg.into(), blame: BLAME_CALLER, kind: HandlerErrorKind::Caller, cause: None }
135 }
136
137 pub fn infrastructure(msg: impl Into<String>) -> Self {
138 Self {
139 message: msg.into(),
140 blame: BLAME_INFRASTRUCTURE,
141 kind: HandlerErrorKind::Infrastructure,
142 cause: None,
143 }
144 }
145
146 pub fn network_partition(msg: impl Into<String>) -> Self {
147 Self {
148 message: msg.into(),
149 blame: BLAME_INFRASTRUCTURE,
150 kind: HandlerErrorKind::NetworkPartition,
151 cause: None,
152 }
153 }
154
155 pub fn lease_expired(msg: impl Into<String>) -> Self {
156 Self { message: msg.into(), blame: BLAME_CALLER, kind: HandlerErrorKind::LeaseExpired, cause: None }
157 }
158
159 pub fn handler_unavailable(msg: impl Into<String>) -> Self {
160 Self {
161 message: msg.into(),
162 blame: BLAME_INFRASTRUCTURE,
163 kind: HandlerErrorKind::HandlerUnavailable,
164 cause: None,
165 }
166 }
167
168 pub fn with_cause(mut self, cause: impl Error + Send + Sync + 'static) -> Self {
169 self.cause = Some(Box::new(cause));
170 self
171 }
172}
173
174impl fmt::Display for HandlerError {
175 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176 write!(f, "[{}] {}", self.blame, self.message)
177 }
178}
179
180impl Error for HandlerError {
181 fn source(&self) -> Option<&(dyn Error + 'static)> {
182 self.cause.as_deref().map(|b| b as &(dyn Error + 'static))
183 }
184}
185
186pub const VALID_OUTCOME_STATUSES: &[&str] = &["ok", "partial", "failed"];
192
193#[derive(Debug, Clone, Serialize)]
197pub struct HandlerOutcome {
198 pub operation: String,
199 pub target: String,
200 pub status: String,
201 pub envelope: LambdaEnvelope,
202 pub data: serde_json::Map<String, Value>,
203 pub handler: String,
204}
205
206impl HandlerOutcome {
207 pub fn new(
208 operation: impl Into<String>,
209 target: impl Into<String>,
210 status: impl Into<String>,
211 envelope: LambdaEnvelope,
212 handler: impl Into<String>,
213 ) -> Self {
214 let status = status.into();
215 assert!(
216 VALID_OUTCOME_STATUSES.contains(&status.as_str()),
217 "HandlerOutcome.status must be one of {VALID_OUTCOME_STATUSES:?}; got '{status}'"
218 );
219 HandlerOutcome {
220 operation: operation.into(),
221 target: target.into(),
222 status,
223 envelope,
224 data: serde_json::Map::new(),
225 handler: handler.into(),
226 }
227 }
228
229 pub fn with_data(mut self, data: serde_json::Map<String, Value>) -> Self {
230 self.data = data;
231 self
232 }
233}
234
235pub type Continuation<'a> = Box<dyn FnMut(HandlerOutcome) -> HandlerOutcome + 'a>;
241
242pub fn identity_continuation<'a>() -> Continuation<'a> {
244 Box::new(|o| o)
245}
246
247pub trait Handler {
253 fn name(&self) -> &str;
255
256 fn supports(&self, op: &IRIntentionOperation) -> bool {
258 matches!(op, IRIntentionOperation::Manifest(_) | IRIntentionOperation::Observe(_))
259 }
260
261 fn provision(
263 &mut self,
264 manifest: &IRManifest,
265 resources: &HashMap<String, IRResource>,
266 fabrics: &HashMap<String, IRFabric>,
267 continuation: &mut Continuation<'_>,
268 ) -> Result<HandlerOutcome, HandlerError>;
269
270 fn observe(
272 &mut self,
273 obs: &IRObserve,
274 manifest: &IRManifest,
275 continuation: &mut Continuation<'_>,
276 ) -> Result<HandlerOutcome, HandlerError>;
277
278 fn close(&mut self) {}
280
281 fn interpret(
283 &mut self,
284 tree: &IRIntentionTree,
285 resources: &HashMap<String, IRResource>,
286 fabrics: &HashMap<String, IRFabric>,
287 manifests: &HashMap<String, IRManifest>,
288 ) -> Result<Vec<HandlerOutcome>, HandlerError> {
289 let mut outcomes: Vec<HandlerOutcome> = Vec::with_capacity(tree.operations.len());
290 let mut pass_through: Continuation<'_> = identity_continuation();
291 for op in &tree.operations {
292 let outcome = match op {
293 IRIntentionOperation::Manifest(m) => {
294 self.provision(m, resources, fabrics, &mut pass_through)?
295 }
296 IRIntentionOperation::Observe(o) => {
297 let target = manifests.get(&o.target).ok_or_else(|| {
298 HandlerError::caller(format!(
299 "observe '{}' targets unknown manifest '{}' — \
300 did you forget a declaration?",
301 o.name, o.target
302 ))
303 })?;
304 self.observe(o, target, &mut pass_through)?
305 }
306 };
307 outcomes.push(outcome);
308 }
309 Ok(outcomes)
310 }
311
312 fn interpret_program(&mut self, program: &IRProgram) -> Result<Vec<HandlerOutcome>, HandlerError> {
314 let Some(tree) = program.intention_tree.as_ref() else {
315 return Ok(Vec::new());
316 };
317 let resources: HashMap<String, IRResource> = program
318 .resources
319 .iter()
320 .map(|r| (r.name.clone(), r.clone()))
321 .collect();
322 let fabrics: HashMap<String, IRFabric> = program
323 .fabrics
324 .iter()
325 .map(|f| (f.name.clone(), f.clone()))
326 .collect();
327 let manifests: HashMap<String, IRManifest> = program
328 .manifests
329 .iter()
330 .map(|m| (m.name.clone(), m.clone()))
331 .collect();
332 self.interpret(tree, &resources, &fabrics, &manifests)
333 }
334}
335
336pub struct HandlerRegistry {
344 handlers: HashMap<String, Box<dyn Handler + Send>>,
345}
346
347impl HandlerRegistry {
348 pub fn new() -> Self {
349 HandlerRegistry { handlers: HashMap::new() }
350 }
351
352 pub fn register(
353 &mut self,
354 handler: Box<dyn Handler + Send>,
355 replace: bool,
356 ) -> Result<(), HandlerError> {
357 let name = handler.name().to_string();
358 if self.handlers.contains_key(&name) && !replace {
359 return Err(HandlerError::callee(format!(
360 "handler '{name}' already registered; pass replace=true to override"
361 )));
362 }
363 self.handlers.insert(name, handler);
364 Ok(())
365 }
366
367 pub fn unregister(&mut self, name: &str) {
368 if let Some(mut handler) = self.handlers.remove(name) {
369 handler.close();
370 }
371 }
372
373 pub fn get(&mut self, name: &str) -> Result<&mut (dyn Handler + Send), HandlerError> {
374 let available = self.names().join(", ");
375 match self.handlers.get_mut(name) {
376 Some(h) => Ok(h.as_mut()),
377 None => Err(HandlerError::caller(format!(
378 "no handler registered with name '{name}'. Available: {}",
379 if available.is_empty() { "(none)" } else { &available }
380 ))),
381 }
382 }
383
384 pub fn names(&self) -> Vec<String> {
385 let mut names: Vec<String> = self.handlers.keys().cloned().collect();
386 names.sort();
387 names
388 }
389
390 pub fn contains(&self, name: &str) -> bool {
391 self.handlers.contains_key(name)
392 }
393
394 pub fn close_all(&mut self) {
395 for (_, mut handler) in self.handlers.drain() {
396 handler.close();
397 }
398 }
399}
400
401impl Default for HandlerRegistry {
402 fn default() -> Self {
403 Self::new()
404 }
405}
406
407impl Drop for HandlerRegistry {
408 fn drop(&mut self) {
409 self.close_all();
410 }
411}
412
413#[cfg(test)]
414mod tests {
415 use super::*;
417
418 #[test]
419 fn envelope_validates_certainty_range() {
420 let e = LambdaEnvelope::new(0.5, "t".into(), "r".into(), "observed".into());
421 assert_eq!(e.c, 0.5);
422 }
423
424 #[test]
425 #[should_panic(expected = "must be in [0.0, 1.0]")]
426 fn envelope_rejects_c_out_of_range() {
427 LambdaEnvelope::new(1.1, "t".into(), "r".into(), "observed".into());
428 }
429
430 #[test]
431 #[should_panic(expected = "delta must be one of")]
432 fn envelope_rejects_invalid_delta() {
433 LambdaEnvelope::new(1.0, "t".into(), "r".into(), "imagined".into());
434 }
435
436 #[test]
437 fn envelope_decayed_preserves_tau_rho_delta() {
438 let e = LambdaEnvelope::new(1.0, "T".into(), "R".into(), "observed".into());
439 let d = e.decayed(0.0);
440 assert_eq!(d.c, 0.0);
441 assert_eq!(d.tau, "T");
442 assert_eq!(d.rho, "R");
443 assert_eq!(d.delta, "observed");
444 }
445
446 #[test]
447 fn make_envelope_uses_supplied_or_current_tau() {
448 let fixed = make_envelope(1.0, "h", "observed", Some("FIXED".into()));
449 assert_eq!(fixed.tau, "FIXED");
450 let fresh = make_envelope(1.0, "h", "observed", None);
451 assert!(!fresh.tau.is_empty());
452 }
453
454 #[test]
455 fn handler_error_display_includes_blame_tag() {
456 let err = HandlerError::caller("oops");
457 assert_eq!(format!("{err}"), "[CT-2] oops");
458 }
459
460 #[test]
461 fn network_partition_is_ct3() {
462 let e = HandlerError::network_partition("partition");
463 assert_eq!(e.blame, BLAME_INFRASTRUCTURE);
464 assert_eq!(e.kind, HandlerErrorKind::NetworkPartition);
465 }
466
467 #[test]
468 fn lease_expired_is_ct2() {
469 let e = HandlerError::lease_expired("expired");
470 assert_eq!(e.blame, BLAME_CALLER);
471 assert_eq!(e.kind, HandlerErrorKind::LeaseExpired);
472 }
473
474 #[test]
475 fn outcome_rejects_invalid_status() {
476 let env = LambdaEnvelope::new(1.0, "t".into(), "h".into(), "observed".into());
477 let result = std::panic::catch_unwind(|| {
478 HandlerOutcome::new("provision", "M", "weird", env, "h")
479 });
480 assert!(result.is_err());
481 }
482
483 struct DummyHandler {
484 name: String,
485 provisions: u32,
486 observes: u32,
487 }
488
489 impl Handler for DummyHandler {
490 fn name(&self) -> &str { &self.name }
491
492 fn provision(
493 &mut self,
494 manifest: &IRManifest,
495 _resources: &HashMap<String, IRResource>,
496 _fabrics: &HashMap<String, IRFabric>,
497 _cont: &mut Continuation<'_>,
498 ) -> Result<HandlerOutcome, HandlerError> {
499 self.provisions += 1;
500 Ok(HandlerOutcome::new(
501 "provision",
502 manifest.name.clone(),
503 "ok",
504 make_envelope(1.0, &self.name, "observed", Some("T".into())),
505 &self.name,
506 ))
507 }
508
509 fn observe(
510 &mut self,
511 obs: &IRObserve,
512 _manifest: &IRManifest,
513 _cont: &mut Continuation<'_>,
514 ) -> Result<HandlerOutcome, HandlerError> {
515 self.observes += 1;
516 Ok(HandlerOutcome::new(
517 "observe",
518 obs.name.clone(),
519 "ok",
520 make_envelope(0.94, &self.name, "observed", Some("T".into())),
521 &self.name,
522 ))
523 }
524 }
525
526 fn fixture_program() -> IRProgram {
527 use crate::ir_generator::IRGenerator;
528 use crate::lexer::Lexer;
529 use crate::parser::Parser;
530
531 let source = r#"
532 resource Db { kind: postgres lifetime: linear }
533 fabric Vpc { provider: aws region: "us-east-1" zones: 1 }
534 manifest Prod { resources: [Db] fabric: Vpc }
535 observe Health from Prod { sources: [prom] quorum: 1 }
536 "#;
537 let tokens = Lexer::new(source, "h").tokenize().expect("lex ok");
538 let program = Parser::new(tokens).parse().expect("parse ok");
539 IRGenerator::new().generate(&program)
540 }
541
542 #[test]
543 fn dummy_handler_interprets_intention_tree_in_order() {
544 let program = fixture_program();
545 assert!(program.intention_tree.is_some());
546 let mut handler = DummyHandler { name: "dummy".into(), provisions: 0, observes: 0 };
547 let outcomes = handler.interpret_program(&program).expect("interpret ok");
548 assert_eq!(outcomes.len(), 2);
549 assert_eq!(outcomes[0].operation, "provision");
550 assert_eq!(outcomes[1].operation, "observe");
551 assert_eq!(handler.provisions, 1);
552 assert_eq!(handler.observes, 1);
553 }
554
555 #[test]
556 fn registry_register_then_get() {
557 let mut reg = HandlerRegistry::new();
558 reg.register(
559 Box::new(DummyHandler { name: "dummy".into(), provisions: 0, observes: 0 }),
560 false,
561 )
562 .expect("register ok");
563 assert!(reg.contains("dummy"));
564 assert_eq!(reg.names(), vec!["dummy".to_string()]);
565 let h = reg.get("dummy").expect("get ok");
566 assert_eq!(h.name(), "dummy");
567 }
568
569 #[test]
570 fn registry_refuses_duplicate_without_replace() {
571 let mut reg = HandlerRegistry::new();
572 reg.register(
573 Box::new(DummyHandler { name: "dup".into(), provisions: 0, observes: 0 }),
574 false,
575 )
576 .unwrap();
577 let err = reg
578 .register(
579 Box::new(DummyHandler { name: "dup".into(), provisions: 0, observes: 0 }),
580 false,
581 )
582 .unwrap_err();
583 assert_eq!(err.kind, HandlerErrorKind::Callee);
584 }
585
586 #[test]
587 fn registry_allows_replace_when_flagged() {
588 let mut reg = HandlerRegistry::new();
589 reg.register(
590 Box::new(DummyHandler { name: "r".into(), provisions: 0, observes: 0 }),
591 false,
592 )
593 .unwrap();
594 reg.register(
595 Box::new(DummyHandler { name: "r".into(), provisions: 0, observes: 0 }),
596 true,
597 )
598 .expect("replace ok");
599 }
600
601 #[test]
602 fn registry_get_unknown_is_caller_blame() {
603 let mut reg = HandlerRegistry::new();
604 match reg.get("ghost") {
605 Err(e) => assert_eq!(e.kind, HandlerErrorKind::Caller),
606 Ok(_) => panic!("registry.get on unknown name must error"),
607 }
608 }
609}