# Custom Detector Development

Develop custom detectors for domain-specific infrastructure analysis.

## Detector Framework

A detector is a plugin that analyzes infrastructure and reports findings.

```text
┌──────────────────┐
│   Your Detector  │
├──────────────────┤
│ Input:           │
│ - Resources      │
│ - Metrics        │
│ - Configurations │
├──────────────────┤
│ Analysis Logic:  │
│ - Rules          │
│ - Thresholds     │
│ - Patterns       │
├──────────────────┤
│ Output:          │
│ - Findings       │
│ - Severity       │
│ - Recommendations│
└──────────────────┘
```

## Minimal Detector Template

```rust
// custom-detectors/my-detector/src/lib.rs
use serde::{Deserialize, Serialize};
use provisioning_detector::{Detector, DetectorConfig, Finding, Resource};

#[derive(Debug, Serialize, Deserialize)]
pub struct MyDetectorConfig {
    pub threshold: i32,
    pub enabled: bool,
}

pub struct MyDetector {
    config: MyDetectorConfig,
}

impl MyDetector {
    pub fn new(config: MyDetectorConfig) -> Self {
        Self { config }
    }
}

impl Detector for MyDetector {
    fn name(&self) -> &'static str {
        "my-custom-detector"
    }

    fn analyze(&self, resources: Vec<Resource>) -> Vec<Finding> {
        let mut findings = Vec::new();

        for resource in resources {
            // Analyze each resource
            if let Some(finding) = self.check_resource(&resource) {
                findings.push(finding);
            }
        }

        findings
    }

    fn check_resource(&self, resource: &Resource) -> Option<Finding> {
        // Your analysis logic here
        match resource.resource_type.as_str() {
            "instance" => {
                let cpu_util = resource.metrics.get("cpu_utilization")?.as_f64()?;

                if cpu_util < self.config.threshold as f64 {
                    return Some(Finding {
                        id: format!("custom-{}", resource.id),
                        category: "custom".to_string(),
                        severity: "medium".to_string(),
                        resource_type: resource.resource_type.clone(),
                        resource_id: resource.id.clone(),
                        issue: format!("CPU utilization {} is below threshold", cpu_util),
                        recommendation: "Downsize or terminate instance".to_string(),
                    });
                }
            }
            _ => {}
        }

        None
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_detect_low_utilization() {
        let config = MyDetectorConfig {
            threshold: 20,
            enabled: true,
        };
        let detector = MyDetector::new(config);

        let resource = Resource {
            id: "i-12345".to_string(),
            resource_type: "instance".to_string(),
            metrics: vec![("cpu_utilization".to_string(), 5.0)].into_iter().collect(),
            ..Default::default()
        };

        let findings = detector.analyze(vec![resource]);
        assert_eq!(findings.len(), 1);
        assert_eq!(findings[0].severity, "medium");
    }
}
```

## Stateful Detector (Time-Series Analysis)

```rust
pub struct TimeSeriesDetector {
    config: TimeSeriesConfig,
    baseline_window: Duration,
}

impl Detector for TimeSeriesDetector {
    fn analyze(&self, resources: Vec<Resource>) -> Vec<Finding> {
        let mut findings = Vec::new();

        for resource in resources {
            // Get historical metrics
            let history = self.fetch_metric_history(&resource.id)?;

            // Compute baseline from history
            let baseline = self.compute_baseline(&history);

            // Compare current to baseline
            let current = resource.metrics.get("cpu_utilization")?;
            let deviation = ((current - baseline) / baseline).abs();

            if deviation > 0.5 {  // 50% deviation
                findings.push(Finding {
                    id: format!("timeseries-{}", resource.id),
                    category: "anomaly".to_string(),
                    severity: "high".to_string(),
                    issue: format!("Resource behavior anomaly detected ({:.0}% deviation)", deviation * 100.0),
                    recommendation: "Investigate root cause of anomaly".to_string(),
                });
            }
        }

        findings
    }
}

fn fetch_metric_history(&self, resource_id: &str) -> Result<Vec<MetricPoint>> {
    // Query time-series database (SurrealDB, Prometheus, etc.)
    let query = format!(
        "SELECT timestamp, value FROM metrics WHERE resource_id = '{}' AND timestamp > now() - duration({})",
        resource_id,
        self.baseline_window
    );

    provisioning::database::query_timeseries(&query)
}

fn compute_baseline(&self, history: &[MetricPoint]) -> f64 {
    // Compute baseline (e.g., 7-day average, percentile, etc.)
    history.iter().map| ( | p p.value).sum::<f64>() / history.len() as f64
}
```

## Cross-Resource Detector (Relationships)

```rust
pub struct DependencyHealthDetector {
    config: DependencyConfig,
}

impl Detector for DependencyHealthDetector {
    fn analyze(&self, resources: Vec<Resource>) -> Vec<Finding> {
        let mut findings = Vec::new();

        for resource in &resources {
            // Find resources that depend on this one
            let dependents = self.find_dependents(&resource.id, &resources);

            // Check if this resource is healthy
            if !self.is_resource_healthy(&resource) {
                findings.push(Finding {
                    id: format!("dep-{}", resource.id),
                    category: "dependency".to_string(),
                    severity: "critical".to_string(),
                    issue: format!(
                        "Unhealthy resource {} impacts {} dependent resources",
                        resource.id,
                        dependents.len()
                    ),
                    recommendation: format!(
                        "Fix {} urgently; affects {} downstream services",
                        resource.id,
                        dependents.len()
                    ),
                });
            }

            // Check if all dependencies are healthy
            let unhealthy_deps = resource.dependencies
                .iter()
                .filter| ( dep_i| d {
                    let dep = resources.iter().find| ( | r &r.id == dep_id).unwrap();
                    !self.is_resource_healthy(dep)
                })
                .collect::<Vec<_>>();

            if !unhealthy_deps.is_empty() {
                findings.push(Finding {
                    id: format!("dep-unmet-{}", resource.id),
                    category: "dependency".to_string(),
                    severity: "high".to_string(),
                    issue: format!("Resource {} has {} unhealthy dependencies", resource.id, unhealthy_deps.len()),
                    recommendation: "Fix dependency health issues".to_string(),
                });
            }
        }

        findings
    }
}

fn find_dependents(&self, resource_id: &str, all_resources: &[Resource]) -> Vec<&Resource> {
    all_resources
        .iter()
        .filter| ( | r r.dependencies.contains(&resource_id.to_string()))
        .collect()
}

fn is_resource_healthy(&self, resource: &Resource) -> bool {
    // Check status, metrics, logs, etc.
    resource.status == "healthy" && resource.metrics.get("error_rate").unwrap_or(&0.0) < &0.01
}
```

## Detector Registration

```rust
// Register detector with framework
#[detector(name = "my-custom-detector", version = "1.0.0")]
pub struct MyDetector;

impl MyDetector {
    pub fn new() -> Self {
        Self
    }
}
```

## Building and Packaging

### Cargo Configuration

```toml
# custom-detectors/my-detector/Cargo.toml
[package]
name = "detector-my-custom"
version = "1.0.0"
edition = "2021"

[dependencies]
provisioning-detector = "0.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1.0", features = ["full"] }

[lib]
name = "detector_my_custom"
crate-type = ["cdylib"]
```

### Build

```bash
cargo build --release

# Output: target/release/libdetector_my_custom.so (Linux)
#         target/release/libdetector_my_custom.dylib (macOS)
#         target/release/detector_my_custom.dll (Windows)
```

## Loading Custom Detectors

### Directory Structure

```text
~/.provisioning/detectors/
├── my-custom-detector/
│   ├── libdetector_my_custom.so
│   └── config.yaml
└── compliance-checker/
    ├── libdetector_compliance.so
    └── config.yaml
```

### Configuration

```yaml
# ~/.provisioning/detectors/my-custom-detector/config.yaml
name: my-custom-detector
version: 1.0.0
description: Custom analysis for our infrastructure
enabled: true

config:
  threshold: 20
  enabled: true
```

### Dynamic Loading

```rust
// Detector framework loads plugins at runtime
pub async fn load_detectors() -> Result<Vec<Box<dyn Detector>>> {
    let plugin_dir = format!("{}/.provisioning/detectors", std::env::var("HOME")?);

    let mut detectors = Vec::new();

    for entry in std::fs::read_dir(&plugin_dir)? {
        let entry = entry?;
        let path = entry.path();

        if path.is_dir() {
            let config = load_config(&path)?;

            if config.enabled {
                let detector = unsafe {
                    load_detector_plugin(&path)?
                };

                detectors.push(detector);
            }
        }
    }

    Ok(detectors)
}

unsafe fn load_detector_plugin(path: &Path) -> Result<Box<dyn Detector>> {
    let lib_path = if cfg!(target_os = "windows") {
        path.join("detector.dll")
    } else if cfg!(target_os = "macos") {
        path.join("libdetector.dylib")
    } else {
        path.join("libdetector.so")
    };

    let library = libloading::Library::new(&lib_path)?;

    let detector_fn: libloading::Symbol<extern "C" fn() -> Box<dyn Detector>> =
        library.get(b"create_detector")?;

    Ok(detector_fn())
}
```

## Testing Detectors

```rust
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_detector_with_mock_resources() {
        let config = MyDetectorConfig {
            threshold: 20,
            enabled: true,
        };
        let detector = MyDetector::new(config);

        let resources = vec![
            Resource {
                id: "instance-1".to_string(),
                resource_type: "instance".to_string(),
                metrics: vec![("cpu_utilization".to_string(), 5.0)].into_iter().collect(),
                ..Default::default()
            },
            Resource {
                id: "instance-2".to_string(),
                resource_type: "instance".to_string(),
                metrics: vec![("cpu_utilization".to_string(), 50.0)].into_iter().collect(),
                ..Default::default()
            },
        ];

        let findings = detector.analyze(resources);

        // Only low-utilization instance should generate finding
        assert_eq!(findings.len(), 1);
        assert_eq!(findings[0].resource_id, "instance-1");
    }

    #[test]
    fn test_detector_performance() {
        let config = MyDetectorConfig {
            threshold: 20,
            enabled: true,
        };
        let detector = MyDetector::new(config);

        // Generate 10,000 resources
        let resources: Vec<_> = (0..10_000)
            .map| ( | i Resource {
                id: format!("instance-{}", i),
                resource_type: "instance".to_string(),
                metrics: vec![("cpu_utilization".to_string(), (i % 100) as f64)].into_iter().collect(),
                ..Default::default()
            })
            .collect();

        let start = std::time::Instant::now();
        let findings = detector.analyze(resources);
        let duration = start.elapsed();

        // Should analyze 10,000 resources in < 1 second
        assert!(duration.as_secs_f64() < 1.0);
        println!("Analyzed 10,000 resources in {:.3}s", duration.as_secs_f64());
    }
}
```

## Examples

### Cost Optimization Detector

```rust
pub struct CostOptimizationDetector;

impl Detector for CostOptimizationDetector {
    fn analyze(&self, resources: Vec<Resource>) -> Vec<Finding> {
        resources
            .iter()
            .filter_map| ( resourc| e {
                let cpu = resource.metrics.get("cpu_utilization")?;
                let memory = resource.metrics.get("memory_utilization")?;
                let cost = resource.metrics.get("monthly_cost")?;

                if cpu < &10.0 && memory < &10.0 {
                    let downsized_cost = cost * 0.3;
                    let savings = cost - downsized_cost;

                    Some(Finding {
                        id: format!("cost-{}", resource.id),
                        category: "cost".to_string(),
                        severity: "medium".to_string(),
                        issue: format!("Instance underutilized: CPU {}%, Memory {}%", cpu, memory),
                        recommendation: format!("Downsize instance, save ${}/month", savings as i32),
                    })
                } else {
                    None
                }
            })
            .collect()
    }
}
```

### Security Vulnerability Detector

```rust
pub struct SecurityDetector {
    cve_database: CveDatabase,
}

impl Detector for SecurityDetector {
    fn analyze(&self, resources: Vec<Resource>) -> Vec<Finding> {
        resources
            .iter()
            .flat_map| ( resourc| e {
                let package_versions = extract_packages(&resource)?;

                package_versions
                    .iter()
                    .filter_map| ( (package, version| ) {
                        let cves = self.cve_database.find_cves(package, version)?;

                        if cves.is_empty() {
                            return None;
                        }

                        Some(Finding {
                            id: format!("sec-{}", resource.id),
                            category: "security".to_string(),
                            severity: "critical".to_string(),
                            issue: format!("{} has {} known vulnerabilities", package, cves.len()),
                            recommendation: format!("Update to latest version or patch"),
                        })
                    })
                    .collect::<Vec<_>>()
            })
            .collect()
    }
}
```

## Distribution

Publish detector to provisioning detector marketplace:

```bash
provisioning detector publish \
    --path ./custom-detectors/my-detector \
    --name "my-custom-detector" \
    --version "1.0.0" \
    --description "Custom analysis for our infrastructure" \
    --author "Your Name" \
    --repository " [https://github.com/your-org/my-detector"](https://github.com/your-org/my-detector")
```

## See Also

- [Detector System Feature](../features/detector-system.md)
- [Custom Task Services Development](./custom-task-services.md)
- [Custom Provider Development](./custom-provider-development.md)
