use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
pub struct LogicalTime(pub u64);
impl LogicalTime {
pub fn genesis() -> Self {
LogicalTime(0)
}
pub fn advance(&self) -> Self {
LogicalTime(self.0 + 1)
}
pub fn is_after(&self, other: &LogicalTime) -> bool {
self.0 > other.0
}
pub fn value(&self) -> u64 {
self.0
}
}
pub trait Timed {
fn logical_time(&self) -> LogicalTime;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimedObservation {
pub id: String,
pub data: String,
pub timestamp: LogicalTime,
}
impl Timed for TimedObservation {
fn logical_time(&self) -> LogicalTime {
self.timestamp
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimedSnapshot {
pub id: String,
pub content: String,
pub created_at: LogicalTime,
pub is_active: bool,
}
impl Timed for TimedSnapshot {
fn logical_time(&self) -> LogicalTime {
self.created_at
}
}
pub struct CausalEffect<T: Timed> {
object: T,
#[allow(dead_code)]
written_at: LogicalTime,
#[allow(dead_code)]
affects_at: LogicalTime,
}
impl<T: Timed> CausalEffect<T> {
pub fn new(
object: T, written_at: LogicalTime, affects_at: LogicalTime,
) -> Result<Self, String> {
if affects_at.0 < written_at.0 {
return Err(format!(
"Causality violation: trying to affect past (written at {}, affects at {})",
written_at.0, affects_at.0
));
}
Ok(CausalEffect {
object,
written_at,
affects_at,
})
}
pub fn object(&self) -> &T {
&self.object
}
}
pub struct MonitorPhaseComplete;
pub struct AnalyzePhaseComplete;
pub struct PlanPhaseComplete;
pub struct ExecutePhaseComplete;
pub struct KnowledgePhaseComplete;
#[derive(Debug, Clone)]
pub struct MAPEKCycle {
pub cycle_id: String,
pub started_at: u64,
pub logical_time: LogicalTime,
pub ticks_used: usize,
}
impl MAPEKCycle {
#[allow(clippy::new_ret_no_self)]
pub fn new(cycle_id: impl Into<String>) -> MonitorState {
MonitorState {
cycle: MAPEKCycle {
cycle_id: cycle_id.into(),
started_at: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
logical_time: LogicalTime::genesis(),
ticks_used: 0,
},
observations: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct MonitorState {
cycle: MAPEKCycle,
observations: Vec<TimedObservation>,
}
impl MonitorState {
pub fn add_observation(&mut self, obs: TimedObservation) {
self.observations.push(obs);
}
pub fn finalize(self) -> Result<AnalyzeState, String> {
if self.observations.is_empty() {
return Err("Cannot advance to Analyze without observations".to_string());
}
Ok(AnalyzeState {
cycle: self.cycle,
observations: self.observations,
findings: Vec::new(),
})
}
}
#[derive(Debug, Clone)]
pub struct AnalyzeState {
cycle: MAPEKCycle,
observations: Vec<TimedObservation>,
findings: Vec<String>, }
impl AnalyzeState {
pub fn add_finding(&mut self, finding: String) {
self.findings.push(finding);
}
pub fn consume_ticks(&mut self, ticks: usize) -> Result<(), String> {
self.cycle.ticks_used += ticks;
if self.cycle.ticks_used > 8 {
return Err(format!(
"Hot path tick budget exceeded: {} > 8",
self.cycle.ticks_used
));
}
Ok(())
}
pub fn finalize(self) -> Result<PlanState, String> {
if self.findings.is_empty() {
return Err("Cannot advance to Plan without findings".to_string());
}
Ok(PlanState {
cycle: self.cycle,
observations: self.observations,
findings: self.findings,
proposals: Vec::new(),
})
}
}
#[derive(Debug, Clone)]
pub struct PlanState {
cycle: MAPEKCycle,
observations: Vec<TimedObservation>,
findings: Vec<String>,
proposals: Vec<String>, }
impl PlanState {
pub fn add_proposal(&mut self, proposal: String) {
self.proposals.push(proposal);
}
pub fn finalize(self) -> Result<ExecuteState, String> {
if self.proposals.is_empty() {
return Err("Cannot advance to Execute without proposals".to_string());
}
Ok(ExecuteState {
cycle: self.cycle,
observations: self.observations,
findings: self.findings,
proposals: self.proposals,
results: Vec::new(),
})
}
}
#[derive(Debug, Clone)]
pub struct ExecuteState {
cycle: MAPEKCycle,
observations: Vec<TimedObservation>,
findings: Vec<String>,
proposals: Vec<String>,
results: Vec<String>, }
impl ExecuteState {
pub fn add_result(&mut self, result: String) {
self.results.push(result);
}
pub fn finalize(self) -> Result<KnowledgeState, String> {
Ok(KnowledgeState {
cycle: self.cycle,
observations: self.observations,
findings: self.findings,
proposals: self.proposals,
results: self.results,
learned: Vec::new(),
})
}
}
#[derive(Debug, Clone)]
pub struct KnowledgeState {
cycle: MAPEKCycle,
observations: Vec<TimedObservation>,
findings: Vec<String>,
proposals: Vec<String>,
results: Vec<String>,
learned: Vec<String>, }
impl KnowledgeState {
pub fn learn(&mut self, lesson: String) {
self.learned.push(lesson);
}
pub fn finalize(self) -> CompletedMAPEKCycle {
CompletedMAPEKCycle {
cycle_id: self.cycle.cycle_id,
logical_time: self.cycle.logical_time,
ticks_used: self.cycle.ticks_used,
observations_count: self.observations.len(),
findings_count: self.findings.len(),
proposals_count: self.proposals.len(),
execution_results: self.results.len(),
lessons_learned: self.learned.len(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompletedMAPEKCycle {
pub cycle_id: String,
pub logical_time: LogicalTime,
pub ticks_used: usize,
pub observations_count: usize,
pub findings_count: usize,
pub proposals_count: usize,
pub execution_results: usize,
pub lessons_learned: usize,
}
pub trait TickBudget {
const MAX_TICKS: usize;
fn check_budget(ticks_used: usize) -> Result<(), String> {
if ticks_used > Self::MAX_TICKS {
Err(format!(
"Tick budget exceeded: {} > {}",
ticks_used,
Self::MAX_TICKS
))
} else {
Ok(())
}
}
}
pub struct HotPathBudget;
impl TickBudget for HotPathBudget {
const MAX_TICKS: usize = 8;
}
pub struct WarmPathBudget;
impl TickBudget for WarmPathBudget {
const MAX_TICKS: usize = 100;
}
pub struct ColdPathBudget;
impl TickBudget for ColdPathBudget {
const MAX_TICKS: usize = usize::MAX;
}
pub struct TickBudgetedExecution<B: TickBudget> {
ticks_used: usize,
_budget: PhantomData<B>,
}
impl<B: TickBudget> Default for TickBudgetedExecution<B> {
fn default() -> Self {
Self::new()
}
}
impl<B: TickBudget> TickBudgetedExecution<B> {
pub fn new() -> Self {
Self {
ticks_used: 0,
_budget: PhantomData,
}
}
pub fn consume(&mut self, ticks: usize) -> Result<(), String> {
self.ticks_used += ticks;
B::check_budget(self.ticks_used)
}
pub fn remaining(&self) -> usize {
B::MAX_TICKS - self.ticks_used
}
pub fn verify(&self) -> Result<(), String> {
B::check_budget(self.ticks_used)
}
}
pub struct TemporalAfter<T: Timed> {
after: T,
before: T,
}
impl<T: Timed> TemporalAfter<T> {
pub fn new(before: T, after: T) -> Result<Self, String> {
if after.logical_time().is_after(&before.logical_time()) {
Ok(TemporalAfter { after, before })
} else {
Err("Temporal ordering violated: after is not after before".to_string())
}
}
pub fn after(&self) -> &T {
&self.after
}
pub fn before(&self) -> &T {
&self.before
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_logical_time_advance() {
let t0 = LogicalTime::genesis();
let t1 = t0.advance();
let t2 = t1.advance();
assert_eq!(t0.value(), 0);
assert_eq!(t1.value(), 1);
assert_eq!(t2.value(), 2);
assert!(t2.is_after(&t1));
assert!(t1.is_after(&t0));
}
#[test]
fn test_mape_k_typestate() {
let monitor = MAPEKCycle::new("cycle-1");
let mut monitor_state = MonitorState {
cycle: monitor.cycle,
observations: Vec::new(),
};
monitor_state.add_observation(TimedObservation {
id: "obs-1".to_string(),
data: "test".to_string(),
timestamp: LogicalTime(1),
});
let analyze = monitor_state.finalize();
assert!(analyze.is_ok());
let mut analyze_state = analyze.unwrap();
analyze_state.add_finding("finding-1".to_string());
let plan = analyze_state.finalize();
assert!(plan.is_ok());
let mut plan_state = plan.unwrap();
plan_state.add_proposal("proposal-1".to_string());
let execute = plan_state.finalize();
assert!(execute.is_ok());
let mut execute_state = execute.unwrap();
execute_state.add_result("result-1".to_string());
let knowledge = execute_state.finalize();
assert!(knowledge.is_ok());
let mut knowledge_state = knowledge.unwrap();
knowledge_state.learn("lesson-1".to_string());
let completed = knowledge_state.finalize();
assert_eq!(completed.observations_count, 1);
assert_eq!(completed.findings_count, 1);
assert_eq!(completed.proposals_count, 1);
}
#[test]
fn test_mape_k_phase_skip_prevention() {
let monitor = MAPEKCycle::new("cycle-2");
let monitor_state = MonitorState {
cycle: monitor.cycle,
observations: Vec::new(),
};
let result = monitor_state.finalize();
assert!(result.is_err());
}
#[test]
fn test_tick_budget_hot_path() {
let mut exec = TickBudgetedExecution::<HotPathBudget>::new();
assert!(exec.consume(5).is_ok());
assert_eq!(exec.remaining(), 3);
let result = exec.consume(5);
assert!(result.is_err());
}
#[test]
fn test_causal_effect() {
let written_at = LogicalTime(1);
let affects_at = LogicalTime(2);
let obs = TimedObservation {
id: "obs".to_string(),
data: "data".to_string(),
timestamp: affects_at,
};
let effect = CausalEffect::new(obs, written_at, affects_at);
assert!(effect.is_ok());
let obs2 = TimedObservation {
id: "obs2".to_string(),
data: "data".to_string(),
timestamp: LogicalTime(1),
};
let bad_effect = CausalEffect::new(obs2, LogicalTime(5), LogicalTime(2));
assert!(bad_effect.is_err());
}
#[test]
fn test_temporal_ordering() {
let before = TimedObservation {
id: "before".to_string(),
data: "d1".to_string(),
timestamp: LogicalTime(1),
};
let after = TimedObservation {
id: "after".to_string(),
data: "d2".to_string(),
timestamp: LogicalTime(2),
};
let ordering = TemporalAfter::new(before, after);
assert!(ordering.is_ok());
let before2 = TimedObservation {
id: "before2".to_string(),
data: "d1".to_string(),
timestamp: LogicalTime(5),
};
let after2 = TimedObservation {
id: "after2".to_string(),
data: "d2".to_string(),
timestamp: LogicalTime(3),
};
let bad_ordering = TemporalAfter::new(before2, after2);
assert!(bad_ordering.is_err());
}
}