use crate::parser::{DocumentContent, ParsedDocument};
use crate::rules::security::{line_of_offset, AIL203};
use crate::rules::{Rule, RuleContext, RuleId, Severity, Violation};
#[derive(Debug, Default)]
pub struct ToolConfirmationRequiredRule;
impl Rule for ToolConfirmationRequiredRule {
fn id(&self) -> RuleId {
AIL203
}
fn default_severity(&self) -> Severity {
Severity::Error
}
fn run(&self, doc: &ParsedDocument, ctx: &RuleContext<'_>) -> Vec<Violation> {
match &doc.content {
DocumentContent::Markdown(_) | DocumentContent::Text => {}
_ => return Vec::new(),
};
let destructive_phrases = [
"delete data",
"rm -rf",
"drop table",
"truncate table",
"irreversible",
"destructive action",
];
let confirmation_phrases = [
"ask",
"confirm",
"human",
"wait",
"permission",
"consent",
"approval",
];
let lower_raw = doc.raw.to_lowercase();
let mut violations = Vec::new();
for d in &destructive_phrases {
if let Some(idx) = lower_raw.find(d) {
if !confirmation_phrases.iter().any(|c| lower_raw.contains(c)) {
let line = line_of_offset(&doc.raw, idx);
let mut v = Violation::new(
AIL203,
ctx.severity,
doc.path.clone(),
format!("Document describes destructive actions ('{}') but lacks confirmation constraints.", d),
).at(line, 1);
v.fix_hint = Some("Include explicit phrasing like 'wait for human confirmation' or 'ask for permission' before destructive actions.".to_string());
violations.push(v);
break;
}
}
}
violations
}
}