use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use super::convergence_state::{ConvergencePointType, SubstrateType};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmissionSchema {
pub templates: Vec<BoundedPointTemplate>,
pub triggers: Vec<EmissionTrigger>,
pub concurrency_limits: HashMap<String, usize>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BoundedPointTemplate {
pub name: String,
pub point_type: ConvergencePointType,
pub substrate: SubstrateType,
pub description: String,
pub preconditions: Vec<String>,
pub postconditions: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmissionTrigger {
pub template_name: String,
pub condition: TriggerCondition,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TriggerCondition {
Threshold { metric: String, value: f64 },
Schedule { cron: String },
Event { event_type: String },
Manual,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum InstantiationDecision {
Instantiate {
template_name: String,
params: HashMap<String, String>,
},
Defer { reason: String },
Escalate { reason: String },
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_emission_schema_serde() {
let schema = EmissionSchema {
templates: vec![BoundedPointTemplate {
name: "migration".into(),
point_type: ConvergencePointType::Transform,
substrate: SubstrateType::Compute,
description: "Migrate workload to cheaper substrate".into(),
preconditions: vec!["budget_approved".into()],
postconditions: vec!["workload_healthy".into()],
}],
triggers: vec![EmissionTrigger {
template_name: "migration".into(),
condition: TriggerCondition::Threshold {
metric: "cost_per_hour".into(),
value: 0.10,
},
}],
concurrency_limits: HashMap::from([("migration".into(), 2)]),
};
let json = serde_json::to_string(&schema).unwrap();
let parsed: EmissionSchema = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.templates.len(), 1);
assert_eq!(parsed.triggers.len(), 1);
}
#[test]
fn test_trigger_conditions() {
let threshold = TriggerCondition::Threshold {
metric: "cost".into(),
value: 0.5,
};
let schedule = TriggerCondition::Schedule {
cron: "0 * * * *".into(),
};
let event = TriggerCondition::Event {
event_type: "spot_price_change".into(),
};
let manual = TriggerCondition::Manual;
for condition in [threshold, schedule, event, manual] {
let json = serde_json::to_string(&condition).unwrap();
let _: TriggerCondition = serde_json::from_str(&json).unwrap();
}
}
#[test]
fn test_instantiation_decisions() {
let instantiate = InstantiationDecision::Instantiate {
template_name: "migration".into(),
params: HashMap::from([("target_node".into(), "node-2".into())]),
};
let defer = InstantiationDecision::Defer {
reason: "too many concurrent migrations".into(),
};
let escalate = InstantiationDecision::Escalate {
reason: "unknown trigger pattern".into(),
};
for decision in [instantiate, defer, escalate] {
let json = serde_json::to_string(&decision).unwrap();
let _: InstantiationDecision = serde_json::from_str(&json).unwrap();
}
}
}