use super::types::{
Finding, FindingKind, OntologyOverlay, OverlayKind, OverlayProposal, OverlayProposer,
PolicyAction, PolicyRule, ValidationStatus,
};
use std::collections::HashMap;
pub struct PlanEngine {
policies: Vec<PolicyRule>,
overlay_counter: usize,
}
impl PlanEngine {
pub fn new() -> Self {
Self {
policies: Self::default_policies(),
overlay_counter: 0,
}
}
fn default_policies() -> Vec<PolicyRule> {
vec![
PolicyRule {
id: "policy-tick-budget".to_string(),
name: "Relax tick-budget pattern".to_string(),
trigger_condition: "TickBudgetViolation".to_string(),
action: PolicyAction::ProposeOverlay {
overlay_kind: "Modification".to_string(),
parameters: {
let mut m = HashMap::new();
m.insert("action".to_string(), serde_json::json!("break_into_steps"));
m.insert("priority".to_string(), serde_json::json!("High"));
m
},
},
priority: 100,
enabled: true,
},
PolicyRule {
id: "policy-guard-relax".to_string(),
name: "Relax failing guard".to_string(),
trigger_condition: "GuardFailureRate".to_string(),
action: PolicyAction::ProposeOverlay {
overlay_kind: "Modification".to_string(),
parameters: {
let mut m = HashMap::new();
m.insert("action".to_string(), serde_json::json!("relax_guard"));
m.insert("threshold_delta".to_string(), serde_json::json!(10.0));
m
},
},
priority: 90,
enabled: true,
},
PolicyRule {
id: "policy-slo-alert".to_string(),
name: "Alert on SLO breach".to_string(),
trigger_condition: "SLOBreach".to_string(),
action: PolicyAction::Alert {
severity: "High".to_string(),
message: "SLO breach detected".to_string(),
},
priority: 80,
enabled: true,
},
]
}
pub fn plan(&mut self, findings: &[Finding]) -> Vec<OverlayProposal> {
let mut proposals = Vec::new();
let policies = self.policies.clone();
for finding in findings {
for policy in &policies {
if !policy.enabled {
continue;
}
if self.policy_matches_finding(policy, finding) {
if let Some(proposal) = self.generate_proposal_from_policy(policy, finding) {
proposals.push(proposal);
}
}
}
}
proposals
}
fn policy_matches_finding(&self, policy: &PolicyRule, finding: &Finding) -> bool {
policy.trigger_condition == format!("{:?}", finding.kind)
}
fn generate_proposal_from_policy(
&mut self, policy: &PolicyRule, finding: &Finding,
) -> Option<OverlayProposal> {
match &policy.action {
PolicyAction::ProposeOverlay {
overlay_kind,
parameters,
} => {
self.overlay_counter += 1;
let overlay_id = format!("overlay-{:04}", self.overlay_counter);
let rdf_patch =
self.generate_rdf_patch(finding.kind, &finding.component, parameters);
let overlay = OntologyOverlay {
id: overlay_id.clone(),
base_snapshot_id: "current".to_string(),
rdf_patch,
overlay_kind: match overlay_kind.as_str() {
"Modification" => OverlayKind::Modification,
"Addition" => OverlayKind::Addition,
"Removal" => OverlayKind::Removal,
_ => OverlayKind::Modification,
},
guard_changes: vec![],
config_changes: HashMap::new(),
proposer: OverlayProposer::Policy,
related_finding: Some(finding.id.clone()),
created_at: get_timestamp(),
validation_status: ValidationStatus::Pending,
validation_results: vec![],
};
Some(OverlayProposal {
title: format!("Fix {}: {}", policy.name, finding.component),
description: finding.description.clone(),
overlay,
estimated_effort: self.estimate_effort(finding.kind),
expected_improvement: self.estimate_improvement(finding.kind),
risk_level: self.estimate_risk_level(finding.severity.clone()),
})
}
PolicyAction::Alert { .. } => {
None
}
_ => None,
}
}
fn generate_rdf_patch(
&self, finding_kind: FindingKind, component: &str,
parameters: &HashMap<String, serde_json::Value>,
) -> String {
match finding_kind {
FindingKind::TickBudgetViolation => {
format!(
r#"# Break {} into smaller steps
@prefix knhk: <http://ggen.ai/knhk#> .
knhk:Pattern_{} a knhk:Pattern ;
knhk:id "{}" ;
knhk:maxTicks 6 ;
knhk:strategy knhk:StepWise .
"#,
component, component, component
)
}
FindingKind::GuardFailureRate => {
format!(
r#"# Relax {} guard threshold
@prefix knhk: <http://ggen.ai/knhk#> .
knhk:Guard_{} a knhk:Guard ;
knhk:id "{}" ;
knhk:threshold {} .
"#,
component,
component,
component,
parameters
.get("threshold_delta")
.and_then(|v| v.as_f64())
.unwrap_or(10.0)
)
}
FindingKind::DriftDetected => {
format!(
r#"# Add sampling/monitoring for {}
@prefix knhk: <http://ggen.ai/knhk#> .
knhk:Monitor_{} a knhk:Monitor ;
knhk:component "{}" ;
knhk:samplingRate 0.1 .
"#,
component, component, component
)
}
_ => {
format!(
r#"# Placeholder overlay for {}
@prefix knhk: <http://ggen.ai/knhk#> .
knhk:Overlay a knhk:Overlay ;
knhk:component "{}" .
"#,
component, component
)
}
}
}
fn estimate_effort(&self, kind: FindingKind) -> f64 {
match kind {
FindingKind::TickBudgetViolation => 2.0,
FindingKind::GuardFailureRate => 1.0,
FindingKind::DriftDetected => 0.5,
FindingKind::SLOBreach => 1.5,
FindingKind::PatternDiscovered => 3.0,
FindingKind::OptimizationOpportunity => 0.5,
}
}
fn estimate_improvement(&self, kind: FindingKind) -> f64 {
match kind {
FindingKind::TickBudgetViolation => 5.0,
FindingKind::GuardFailureRate => 3.0,
FindingKind::DriftDetected => 2.0,
FindingKind::SLOBreach => 4.0,
FindingKind::PatternDiscovered => 6.0,
FindingKind::OptimizationOpportunity => 1.0,
}
}
fn estimate_risk_level(&self, severity: String) -> String {
match severity.as_str() {
"Critical" => "High".to_string(),
"High" => "Medium".to_string(),
"Medium" => "Low".to_string(),
_ => "Low".to_string(),
}
}
pub fn add_policy(&mut self, policy: PolicyRule) {
self.policies.push(policy);
}
pub fn policies(&self) -> &[PolicyRule] {
&self.policies
}
}
impl Default for PlanEngine {
fn default() -> Self {
Self::new()
}
}
fn get_timestamp() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_plan_from_findings() {
let mut planner = PlanEngine::new();
let finding = Finding {
id: "finding-1".to_string(),
kind: FindingKind::TickBudgetViolation,
severity: "High".to_string(),
description: "Pattern exceeds tick budget".to_string(),
component: "expensive_pattern".to_string(),
evidence: vec![],
suggested_action: None,
timestamp: 0,
metadata: HashMap::new(),
};
let proposals = planner.plan(&[finding]);
assert!(!proposals.is_empty());
let proposal = &proposals[0];
assert_eq!(proposal.overlay.proposer, OverlayProposer::Policy);
assert!(proposal.estimated_effort > 0.0);
}
#[test]
fn test_default_policies_loaded() {
let planner = PlanEngine::new();
assert!(planner.policies.len() >= 3);
}
#[test]
fn test_custom_policy() {
let mut planner = PlanEngine::new();
let policy = PolicyRule {
id: "test-policy".to_string(),
name: "Test Policy".to_string(),
trigger_condition: "TestFinding".to_string(),
action: PolicyAction::Alert {
severity: "Low".to_string(),
message: "Test alert".to_string(),
},
priority: 50,
enabled: true,
};
planner.add_policy(policy);
assert!(planner.policies.iter().any(|p| p.id == "test-policy"));
}
}