enact-core 0.0.2

Core agent runtime for Enact - Graph-Native AI agents
Documentation
//! PII Input Processor
//!
//! Detects PII in user input before execution.
//! Can block, warn, or allow based on configuration.

use super::input_processor::{InputProcessor, InputProcessorResult};
use super::PolicyContext;
use async_trait::async_trait;

/// PII detection mode
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PiiInputMode {
    /// Allow all input (no PII checking)
    Allow,
    /// Warn if PII detected but allow
    #[default]
    Warn,
    /// Block if Direct PII detected (email, SSN, etc.)
    BlockDirect,
    /// Block if any PII detected
    BlockAll,
}

/// PII Input Processor
///
/// Detects PII in user input before sending to LLM.
/// Behavior is configurable via `PiiInputMode`.
/// (Full PII detection requires the optional `enact-guardrails` crate and guardrails feature.)
pub struct PiiInputProcessor {
    mode: PiiInputMode,
}

impl PiiInputProcessor {
    /// Create a new PII input processor (no-op without enact-guardrails; always allows input)
    pub fn new() -> Self {
        Self {
            mode: PiiInputMode::Allow,
        }
    }

    /// Set the detection mode
    pub fn with_mode(mut self, mode: PiiInputMode) -> Self {
        self.mode = mode;
        self
    }
}

impl Default for PiiInputProcessor {
    fn default() -> Self {
        Self::new()
    }
}

#[async_trait]
impl InputProcessor for PiiInputProcessor {
    fn name(&self) -> &str {
        "pii-input"
    }

    fn priority(&self) -> u32 {
        50 // Run early in the pipeline
    }

    async fn process(
        &self,
        _input: &str,
        _ctx: &PolicyContext,
    ) -> anyhow::Result<InputProcessorResult> {
        // Without enact-guardrails, always pass
        Ok(InputProcessorResult::Pass)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::policy::PolicyAction;
    use std::collections::HashMap;

    fn test_context() -> PolicyContext {
        PolicyContext {
            tenant_id: None,
            user_id: None,
            action: PolicyAction::StartExecution { graph_id: None },
            metadata: HashMap::new(),
        }
    }

    #[tokio::test]
    async fn test_pii_input_processor_name() {
        let processor = PiiInputProcessor::new();
        assert_eq!(processor.name(), "pii-input");
    }

    #[tokio::test]
    async fn test_pii_input_processor_priority() {
        let processor = PiiInputProcessor::new();
        assert_eq!(processor.priority(), 50);
    }

    #[tokio::test]
    async fn test_pii_input_no_pii() {
        let processor = PiiInputProcessor::new();
        let ctx = test_context();

        // No PII should always pass
        let result = processor
            .process("Hello, how can I help?", &ctx)
            .await
            .unwrap();
        assert!(result.should_proceed());
    }
}