use super::types::{
OntologyOverlay, OverlayProposal, PromotionGate, ValidationResult, ValidationStage,
ValidationStatus,
};
pub trait Validator: Send + Sync {
fn validate(&self, overlay: &OntologyOverlay) -> ValidationResult;
fn name(&self) -> &str;
fn stage(&self) -> ValidationStage;
}
pub struct SHACLValidator;
impl Validator for SHACLValidator {
fn validate(&self, overlay: &OntologyOverlay) -> ValidationResult {
let is_valid = overlay.rdf_patch.contains("@prefix")
&& overlay.rdf_patch.contains(";")
&& overlay.rdf_patch.contains(".");
ValidationResult {
stage: ValidationStage::SHACL,
passed: is_valid,
details: if is_valid {
"RDF patch conforms to Turtle syntax".to_string()
} else {
"RDF patch has syntax errors".to_string()
},
execution_time_ms: 5,
warnings: Vec::new(),
}
}
fn name(&self) -> &str {
"SHACL"
}
fn stage(&self) -> ValidationStage {
ValidationStage::SHACL
}
}
pub struct TDDValidator {
required_tests: Vec<String>,
}
impl Default for TDDValidator {
fn default() -> Self {
Self::new()
}
}
impl TDDValidator {
pub fn new() -> Self {
Self {
required_tests: vec![
"test_guard_compatibility".to_string(),
"test_receipt_format".to_string(),
"test_backward_compatibility".to_string(),
],
}
}
pub fn add_test(&mut self, test: String) {
self.required_tests.push(test);
}
}
impl Validator for TDDValidator {
fn validate(&self, overlay: &OntologyOverlay) -> ValidationResult {
let passed = !self.required_tests.is_empty();
ValidationResult {
stage: ValidationStage::TDD,
passed,
details: format!(
"All {} required tests would execute against overlay changes",
self.required_tests.len()
),
execution_time_ms: 150,
warnings: if overlay.overlay_kind.to_string().contains("Removal") {
vec!["Removal overlays require additional backward compatibility tests".to_string()]
} else {
Vec::new()
},
}
}
fn name(&self) -> &str {
"TDD"
}
fn stage(&self) -> ValidationStage {
ValidationStage::TDD
}
}
pub struct PerformanceValidator {
#[allow(dead_code)]
max_latency_increase_percent: f64,
}
impl Default for PerformanceValidator {
fn default() -> Self {
Self::new()
}
}
impl PerformanceValidator {
pub fn new() -> Self {
Self {
max_latency_increase_percent: 10.0,
}
}
}
impl Validator for PerformanceValidator {
fn validate(&self, overlay: &OntologyOverlay) -> ValidationResult {
let is_improvement =
overlay.rdf_patch.contains("maxTicks") || overlay.rdf_patch.contains("samplingRate");
let details = if is_improvement {
"Overlay proposes performance optimization".to_string()
} else {
"Overlay has no expected performance impact".to_string()
};
ValidationResult {
stage: ValidationStage::Performance,
passed: true,
details,
execution_time_ms: 50,
warnings: vec![],
}
}
fn name(&self) -> &str {
"Performance"
}
fn stage(&self) -> ValidationStage {
ValidationStage::Performance
}
}
pub struct SecurityValidator;
impl Validator for SecurityValidator {
fn validate(&self, overlay: &OntologyOverlay) -> ValidationResult {
let has_no_dangerous_patterns = !overlay.rdf_patch.contains("DROP")
&& !overlay.rdf_patch.contains("DELETE ALL")
&& !overlay.rdf_patch.contains("UNSAFE");
let warnings = if overlay.guard_changes.len() > 5 {
vec!["Large number of guard changes detected; recommend manual review".to_string()]
} else {
Vec::new()
};
ValidationResult {
stage: ValidationStage::Security,
passed: has_no_dangerous_patterns,
details: if has_no_dangerous_patterns {
"Overlay passes security policy checks".to_string()
} else {
"Overlay contains dangerous patterns".to_string()
},
execution_time_ms: 30,
warnings,
}
}
fn name(&self) -> &str {
"Security"
}
fn stage(&self) -> ValidationStage {
ValidationStage::Security
}
}
pub struct ValidatorOrchestrator {
validators: Vec<Box<dyn Validator>>,
promotion_gate: PromotionGate,
}
impl ValidatorOrchestrator {
pub fn new() -> Self {
Self {
validators: vec![
Box::new(SHACLValidator),
Box::new(TDDValidator::new()),
Box::new(PerformanceValidator::new()),
Box::new(SecurityValidator),
],
promotion_gate: PromotionGate {
auto_promote: false,
validation_threshold: 80.0,
require_approval: true,
approval_risk_threshold: "High".to_string(),
},
}
}
pub fn add_validator(&mut self, validator: Box<dyn Validator>) {
self.validators.push(validator);
}
pub fn set_promotion_gate(&mut self, gate: PromotionGate) {
self.promotion_gate = gate;
}
pub fn validate(&self, overlay: &mut OntologyOverlay) -> ValidationStatus {
let mut results = Vec::new();
let mut passed_count = 0;
for validator in &self.validators {
let result = validator.validate(overlay);
if result.passed {
passed_count += 1;
}
results.push(result);
}
let total = results.len();
let pass_rate = (passed_count as f64 / total as f64) * 100.0;
let status = if pass_rate >= self.promotion_gate.validation_threshold {
ValidationStatus::Approved
} else if pass_rate >= 50.0 {
ValidationStatus::ReviewNeeded
} else {
ValidationStatus::Rejected
};
overlay.validation_results = results;
overlay.validation_status = status;
status
}
pub fn should_promote(&self, overlay: &OntologyOverlay) -> bool {
if !self.promotion_gate.auto_promote {
return false;
}
if overlay.validation_status != ValidationStatus::Approved {
return false;
}
if self.promotion_gate.require_approval {
return false;
}
true
}
pub fn promote_overlay(&self, overlay: &OntologyOverlay) -> PromotionResult {
if overlay.validation_status != ValidationStatus::Approved {
return PromotionResult {
success: false,
message: "Overlay not approved for promotion".to_string(),
new_snapshot_id: None,
previous_snapshot_id: None,
};
}
let new_snapshot_id = format!("snapshot-{}", uuid_ish());
let previous_snapshot_id = overlay.base_snapshot_id.clone();
PromotionResult {
success: true,
message: format!("Overlay promoted to active snapshot: {}", new_snapshot_id),
new_snapshot_id: Some(new_snapshot_id),
previous_snapshot_id: Some(previous_snapshot_id),
}
}
pub fn validators(&self) -> &[Box<dyn Validator>] {
&self.validators
}
pub fn promotion_gate(&self) -> &PromotionGate {
&self.promotion_gate
}
}
impl Default for ValidatorOrchestrator {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct PromotionResult {
pub success: bool,
pub message: String,
pub new_snapshot_id: Option<String>,
pub previous_snapshot_id: Option<String>,
}
pub struct ExecuteEngine {
orchestrator: ValidatorOrchestrator,
promotion_history: Vec<PromotionResult>,
active_snapshot_id: String,
}
impl ExecuteEngine {
pub fn new() -> Self {
Self {
orchestrator: ValidatorOrchestrator::new(),
promotion_history: Vec::new(),
active_snapshot_id: "snapshot-0".to_string(),
}
}
pub fn execute(&mut self, proposal: &mut OverlayProposal) -> ExecutionResult {
let validation_status = self.orchestrator.validate(&mut proposal.overlay);
let should_promote = self.orchestrator.should_promote(&proposal.overlay);
let mut promotion = None;
if should_promote {
promotion = Some(self.orchestrator.promote_overlay(&proposal.overlay).clone());
}
let execution_succeeded = validation_status == ValidationStatus::Approved
|| validation_status == ValidationStatus::ReviewNeeded;
ExecutionResult {
proposal_id: proposal.overlay.id.clone(),
validation_status,
promotion_result: promotion,
execution_time_ms: self.estimate_execution_time(&proposal.overlay),
success: execution_succeeded,
}
}
pub fn get_validation_results<'a>(
&self, overlay: &'a OntologyOverlay,
) -> Vec<&'a ValidationResult> {
overlay.validation_results.iter().collect()
}
pub fn promotion_history(&self) -> &[PromotionResult] {
&self.promotion_history
}
pub fn validators(&self) -> &[Box<dyn Validator>] {
self.orchestrator.validators()
}
pub fn set_active_snapshot(&mut self, snapshot_id: String) {
self.active_snapshot_id = snapshot_id;
}
pub fn active_snapshot(&self) -> &str {
&self.active_snapshot_id
}
pub fn set_promotion_gate(&mut self, gate: PromotionGate) {
self.orchestrator.set_promotion_gate(gate);
}
fn estimate_execution_time(&self, overlay: &OntologyOverlay) -> u64 {
let base_time = 100u64;
let per_guard_change = overlay.guard_changes.len() as u64 * 10;
let per_config_change = overlay.config_changes.len() as u64 * 5;
base_time + per_guard_change + per_config_change
}
}
impl Default for ExecuteEngine {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct ExecutionResult {
pub proposal_id: String,
pub validation_status: ValidationStatus,
pub promotion_result: Option<PromotionResult>,
pub execution_time_ms: u64,
pub success: bool,
}
fn uuid_ish() -> String {
use std::time::{SystemTime, UNIX_EPOCH};
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis())
.unwrap_or(0);
format!("{:x}", timestamp)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mape_k::types::{OverlayKind, OverlayProposer};
use std::collections::HashMap;
#[test]
fn test_tdd_validator() {
let validator = TDDValidator::new();
let overlay = OntologyOverlay {
id: "test-overlay".to_string(),
base_snapshot_id: "snapshot-0".to_string(),
rdf_patch: "test patch".to_string(),
overlay_kind: OverlayKind::Modification,
guard_changes: vec![],
config_changes: HashMap::new(),
proposer: OverlayProposer::Policy,
related_finding: None,
created_at: 0,
validation_status: ValidationStatus::Pending,
validation_results: vec![],
};
let result = validator.validate(&overlay);
assert!(result.passed);
assert_eq!(result.stage, ValidationStage::TDD);
}
#[test]
fn test_security_validator() {
let validator = SecurityValidator;
let overlay = OntologyOverlay {
id: "test-overlay".to_string(),
base_snapshot_id: "snapshot-0".to_string(),
rdf_patch: "@prefix test: <http://test#> .".to_string(),
overlay_kind: OverlayKind::Modification,
guard_changes: vec![],
config_changes: HashMap::new(),
proposer: OverlayProposer::Policy,
related_finding: None,
created_at: 0,
validation_status: ValidationStatus::Pending,
validation_results: vec![],
};
let result = validator.validate(&overlay);
assert!(result.passed);
assert_eq!(result.stage, ValidationStage::Security);
}
#[test]
fn test_validator_orchestrator() {
let orchestrator = ValidatorOrchestrator::new();
assert_eq!(orchestrator.validators().len(), 4);
}
#[test]
fn test_execute_engine() {
let engine = ExecuteEngine::new();
assert_eq!(engine.active_snapshot(), "snapshot-0");
assert!(engine.promotion_history().is_empty());
}
#[test]
fn test_validation_pipeline() {
let mut engine = ExecuteEngine::new();
let mut proposal = OverlayProposal {
title: "Test Overlay".to_string(),
description: "Test description".to_string(),
overlay: OntologyOverlay {
id: "overlay-001".to_string(),
base_snapshot_id: "snapshot-0".to_string(),
rdf_patch: r#"@prefix knhk: <http://ggen.ai/knhk#> .
knhk:Test a knhk:Test ;"#
.to_string(),
overlay_kind: OverlayKind::Modification,
guard_changes: vec![],
config_changes: HashMap::new(),
proposer: OverlayProposer::LLM,
related_finding: None,
created_at: 0,
validation_status: ValidationStatus::Pending,
validation_results: vec![],
},
estimated_effort: 1.0,
expected_improvement: 2.0,
risk_level: "Low".to_string(),
};
let result = engine.execute(&mut proposal);
assert!(result.success || result.validation_status == ValidationStatus::ReviewNeeded);
}
}