use crate::edit_control::{ModifiableEdit, ValidationResult, ValidationSeverity, ApprovalLevel};
use crate::classification::edit_classifier_working::{EditClassificationSystem, ClassificationRecommendation};
use std::collections::{HashMap, HashSet};
use std::path::Path;
use anyhow::{Result, anyhow};
#[derive(Debug)]
pub struct FileProtectionSystem {
protection_rules: HashMap<String, FileProtectionRule>,
function_constraints: HashMap<String, FunctionConstraint>,
config: ProtectionConfig,
classification_system: EditClassificationSystem,
}
#[derive(Debug, Clone)]
pub struct ProtectionConfig {
pub strict_mode: bool,
pub emergency_bypass_enabled: bool,
pub emergency_bypass_level: ApprovalLevel,
pub audit_logging: bool,
pub default_protection_level: ProtectionLevel,
}
impl Default for ProtectionConfig {
fn default() -> Self {
Self {
strict_mode: false,
emergency_bypass_enabled: true,
emergency_bypass_level: ApprovalLevel::Critical,
audit_logging: true,
default_protection_level: ProtectionLevel::Standard,
}
}
}
#[derive(Debug, Clone)]
pub struct FileProtectionRule {
pub pattern: String,
pub protection_level: ProtectionLevel,
pub allowed_edit_types: HashSet<EditType>,
pub forbidden_patterns: Vec<String>,
pub required_patterns: Vec<String>,
pub max_lines_modified: Option<usize>,
pub reason: String,
}
#[derive(Debug, Clone)]
pub struct FunctionConstraint {
pub file_pattern: String,
pub function_pattern: String,
pub protection_level: ProtectionLevel,
pub documentation_required: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum ProtectionLevel {
None,
Standard,
High,
Critical,
ReadOnly,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum EditType {
ContentModification,
FileRename,
FileDeletion,
CommentModification,
DocumentationUpdate,
}
#[derive(Debug, Clone)]
pub struct ProtectionResult {
pub allowed: bool,
pub applied_protection: ProtectionLevel,
pub validation_results: Vec<ValidationResult>,
pub required_approval_level: Option<ApprovalLevel>,
pub applied_constraints: Vec<String>,
pub recommendations: Vec<String>,
}
impl FileProtectionSystem {
pub fn new() -> Self {
Self::with_config(ProtectionConfig::default())
}
pub fn with_config(config: ProtectionConfig) -> Self {
Self {
protection_rules: Self::default_protection_rules(),
function_constraints: Self::default_function_constraints(),
config,
classification_system: EditClassificationSystem::new(),
}
}
pub fn validate_edit(&self, edit: &ModifiableEdit) -> Result<ProtectionResult> {
let file_path = Path::new(&edit.base_edit.file);
let applicable_rules = self.find_applicable_rules(file_path);
let file_results = self.validate_file_constraints(edit, &applicable_rules)?;
let function_results = self.validate_function_constraints(edit)?;
let classification_results = self.validate_classification_constraints(edit)?;
let all_results = [file_results, function_results, classification_results].concat();
self.determine_protection_result(edit, &applicable_rules, all_results)
}
pub fn add_protection_rule(&mut self, pattern: String, rule: FileProtectionRule) {
self.protection_rules.insert(pattern, rule);
}
pub fn add_function_constraint(&mut self, name: String, constraint: FunctionConstraint) {
self.function_constraints.insert(name, constraint);
}
pub fn config(&self) -> &ProtectionConfig {
&self.config
}
fn find_applicable_rules(&self, file_path: &Path) -> Vec<&FileProtectionRule> {
let mut applicable = Vec::new();
for (pattern, rule) in &self.protection_rules {
if self.matches_pattern(file_path, pattern) {
applicable.push(rule);
}
}
applicable.sort_by(|a, b| b.protection_level.cmp(&a.protection_level));
applicable
}
fn matches_pattern(&self, file_path: &Path, pattern: &str) -> bool {
let path_str = file_path.to_string_lossy();
if pattern.contains("*") {
let prefix = pattern.split("*").next().unwrap_or("");
let suffix = pattern.split("*").last().unwrap_or("");
path_str.starts_with(prefix) && path_str.ends_with(suffix)
} else {
path_str == pattern
}
}
fn validate_file_constraints(
&self,
edit: &ModifiableEdit,
rules: &[&FileProtectionRule]
) -> Result<Vec<ValidationResult>> {
let mut results = Vec::new();
for rule in rules {
let edit_type = self.determine_edit_type(edit);
if !rule.allowed_edit_types.contains(&edit_type) {
results.push(ValidationResult {
validator_name: "file_protection".to_string(),
passed: false,
message: format!("Edit type {:?} not allowed for file {}", edit_type, edit.base_edit.file),
severity: ValidationSeverity::Error,
});
}
for forbidden in &rule.forbidden_patterns {
if edit.base_edit.new_code.contains(forbidden) {
results.push(ValidationResult {
validator_name: "forbidden_pattern".to_string(),
passed: false,
message: format!("Forbidden pattern '{}' found in edit", forbidden),
severity: ValidationSeverity::Critical,
});
}
}
for required in &rule.required_patterns {
if !edit.compute_final_code().contains(required) {
results.push(ValidationResult {
validator_name: "required_pattern".to_string(),
passed: false,
message: format!("Required pattern '{}' missing after edit", required),
severity: ValidationSeverity::Error,
});
}
}
if let Some(max_lines) = rule.max_lines_modified {
let lines_modified = edit.base_edit.new_code.lines().count();
if lines_modified > max_lines {
results.push(ValidationResult {
validator_name: "line_count_limit".to_string(),
passed: false,
message: format!("Edit modifies {} lines, maximum allowed is {}", lines_modified, max_lines),
severity: ValidationSeverity::Warning,
});
}
}
}
Ok(results)
}
fn validate_function_constraints(&self, edit: &ModifiableEdit) -> Result<Vec<ValidationResult>> {
let mut results = Vec::new();
let file_path = Path::new(&edit.base_edit.file);
for (_constraint_name, constraint) in &self.function_constraints {
if self.matches_pattern(file_path, &constraint.file_pattern) {
if self.edit_affects_function(edit, &constraint.function_pattern) {
let function_results = self.validate_function_constraint(edit, constraint)?;
results.extend(function_results);
}
}
}
Ok(results)
}
fn validate_classification_constraints(&self, edit: &ModifiableEdit) -> Result<Vec<ValidationResult>> {
let classified = self.classification_system.classify_edit(edit)
.map_err(|e| anyhow!("Classification failed: {}", e))?;
let mut results = Vec::new();
if classified.risk_assessment.overall_score > 0.8 {
match classified.recommendation {
ClassificationRecommendation::RequireReview { concerns } => {
results.push(ValidationResult {
validator_name: "classification_protection".to_string(),
passed: false,
message: format!("High-risk edit requires review: {:?}", concerns),
severity: ValidationSeverity::Error,
});
}
ClassificationRecommendation::Escalate { target_level, reason } => {
results.push(ValidationResult {
validator_name: "classification_protection".to_string(),
passed: false,
message: format!("Edit requires escalation to {}: {}", target_level, reason),
severity: ValidationSeverity::Critical,
});
}
_ => {}
}
}
Ok(results)
}
fn determine_protection_result(
&self,
_edit: &ModifiableEdit,
rules: &[&FileProtectionRule],
validation_results: Vec<ValidationResult>
) -> Result<ProtectionResult> {
let has_critical_failures = validation_results.iter()
.any(|r| !r.passed && r.severity == ValidationSeverity::Critical);
let has_errors = validation_results.iter()
.any(|r| !r.passed && r.severity == ValidationSeverity::Error);
let applied_protection = rules.first()
.map(|r| r.protection_level.clone())
.unwrap_or(self.config.default_protection_level.clone());
let allowed = if self.config.strict_mode {
!has_critical_failures && !has_errors
} else {
!has_critical_failures
};
let required_approval_level = if has_critical_failures {
Some(ApprovalLevel::Critical)
} else if has_errors {
Some(ApprovalLevel::High)
} else {
None
};
Ok(ProtectionResult {
allowed,
applied_protection,
validation_results,
required_approval_level,
applied_constraints: rules.iter().map(|r| r.reason.clone()).collect(),
recommendations: self.generate_recommendations(rules),
})
}
fn determine_edit_type(&self, edit: &ModifiableEdit) -> EditType {
if edit.base_edit.new_code.contains("//") || edit.base_edit.new_code.contains("/*") {
EditType::CommentModification
} else if edit.base_edit.file.ends_with(".md") || edit.base_edit.file.ends_with(".txt") {
EditType::DocumentationUpdate
} else {
EditType::ContentModification
}
}
fn edit_affects_function(&self, edit: &ModifiableEdit, function_pattern: &str) -> bool {
let simple_pattern = function_pattern.replace("*", "");
edit.compute_final_code().contains(&format!("fn {}", simple_pattern))
}
fn validate_function_constraint(&self, edit: &ModifiableEdit, constraint: &FunctionConstraint) -> Result<Vec<ValidationResult>> {
let mut results = Vec::new();
if constraint.documentation_required {
let has_docs = edit.compute_final_code().contains("///") || edit.compute_final_code().contains("/**");
results.push(ValidationResult {
validator_name: "function_documentation".to_string(),
passed: has_docs,
message: if has_docs {
"Function documentation found".to_string()
} else {
"Function documentation required".to_string()
},
severity: if has_docs { ValidationSeverity::Info } else { ValidationSeverity::Warning },
});
}
Ok(results)
}
fn generate_recommendations(&self, rules: &[&FileProtectionRule]) -> Vec<String> {
let mut recommendations = Vec::new();
for rule in rules {
if rule.protection_level >= ProtectionLevel::High {
recommendations.push(format!("Consider elevated approval for protected file: {}", rule.reason));
}
}
if recommendations.is_empty() {
recommendations.push("No specific recommendations for this edit".to_string());
}
recommendations
}
fn default_protection_rules() -> HashMap<String, FileProtectionRule> {
let mut rules = HashMap::new();
rules.insert("src/security/*".to_string(), FileProtectionRule {
pattern: "src/security/*".to_string(),
protection_level: ProtectionLevel::Critical,
allowed_edit_types: [EditType::DocumentationUpdate, EditType::CommentModification].iter().cloned().collect(),
forbidden_patterns: vec!["password".to_string(), "hardcoded".to_string()],
required_patterns: vec!["#[cfg(test)]".to_string()],
max_lines_modified: Some(10),
reason: "Security-critical code requires maximum protection".to_string(),
});
rules.insert("Cargo.toml".to_string(), FileProtectionRule {
pattern: "Cargo.toml".to_string(),
protection_level: ProtectionLevel::High,
allowed_edit_types: [EditType::ContentModification, EditType::DocumentationUpdate].iter().cloned().collect(),
forbidden_patterns: vec!["unsafe".to_string()],
required_patterns: vec![],
max_lines_modified: Some(5),
reason: "Dependency changes require careful review".to_string(),
});
rules
}
fn default_function_constraints() -> HashMap<String, FunctionConstraint> {
let mut constraints = HashMap::new();
constraints.insert("auth_*".to_string(), FunctionConstraint {
file_pattern: "src/security/*".to_string(),
function_pattern: "auth_*".to_string(),
protection_level: ProtectionLevel::Critical,
documentation_required: true,
});
constraints
}
}
impl Default for FileProtectionSystem {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::agents::gpt4_agent::ProposedEdit;
fn create_test_edit(file: &str, content: &str, reason: &str) -> ModifiableEdit {
let proposed = ProposedEdit {
file: file.to_string(),
line_range: (1, 5),
new_code: content.to_string(),
reason: reason.to_string(),
confidence: 0.8,
};
ModifiableEdit::from_proposed_edit(proposed)
}
#[test]
fn test_protection_system_creation() {
let system = FileProtectionSystem::new();
assert_eq!(system.config.default_protection_level, ProtectionLevel::Standard);
assert!(system.config.emergency_bypass_enabled);
}
#[test]
fn test_file_pattern_matching() {
let system = FileProtectionSystem::new();
let path = Path::new("src/security/auth.rs");
let matches = system.matches_pattern(path, "src/security/*");
assert!(matches);
let no_match = system.matches_pattern(path, "src/public/*");
assert!(!no_match);
}
#[test]
fn test_security_file_protection() {
let system = FileProtectionSystem::new();
let edit = create_test_edit(
"src/security/auth.rs",
"let password = \"hardcoded123\";",
"Add authentication"
);
let result = system.validate_edit(&edit).unwrap();
assert!(!result.allowed);
assert_eq!(result.applied_protection, ProtectionLevel::Critical);
assert!(result.validation_results.iter().any(|r| !r.passed));
}
#[test]
fn test_documentation_edit_allowed() {
let system = FileProtectionSystem::new();
let edit = create_test_edit(
"README.md",
"# Updated Documentation\nThis is safe content.",
"Update documentation"
);
let result = system.validate_edit(&edit).unwrap();
assert!(result.allowed);
assert_eq!(result.applied_protection, ProtectionLevel::Standard);
}
}