Files
zesdex/src/app/harness.rs
T

128 lines
3.5 KiB
Rust
Raw Normal View History

#[derive(Debug, Clone, PartialEq)]
pub enum Verdict {
Allow,
#[allow(dead_code)]
Block(String),
}
pub struct Harness;
impl Harness {
pub fn gate_tool_call(
tool_name: &str,
_args: &serde_json::Value,
_workspace_roots: &[&std::path::Path],
) -> Verdict {
if !crate::tool::tool_is_risky(tool_name) {
return Verdict::Allow;
}
Self::classify(tool_name)
}
fn classify(_cmd: &str) -> Verdict {
Verdict::Allow
}
}
impl Default for Harness {
fn default() -> Self {
Harness
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn parse_verdict(text: &str) -> Option<Verdict> {
let trimmed = text.trim();
if let Ok(v) = serde_json::from_str::<serde_json::Value>(trimmed) {
if let Some(verdict) = v.get("verdict").and_then(|v| v.as_str()) {
return match verdict.to_lowercase().as_str() {
"allow" => Some(Verdict::Allow),
"block" => Some(Verdict::Block(
v.get("reason").and_then(|r| r.as_str()).unwrap_or("blocked").to_string()
)),
_ => None,
};
}
}
for line in trimmed.lines() {
let l = line.trim().to_lowercase();
if l.starts_with("verdict: allow") {
return Some(Verdict::Allow);
}
if l.starts_with("verdict: block") {
let reason = line.split_once(':').map(|x| x.1).unwrap_or("blocked").trim().to_string();
return Some(Verdict::Block(reason));
}
}
if trimmed.to_lowercase().contains("allow") {
return Some(Verdict::Allow);
}
if trimmed.to_lowercase().contains("block") {
return Some(Verdict::Block("blocked by classifier".to_string()));
}
None
}
#[test]
fn test_classify_always_allows() {
assert_eq!(Harness::classify("write"), Verdict::Allow);
}
#[test]
fn test_gate_tool_non_risky_always_allows() {
let roots: &[&std::path::Path] = &[];
let result = Harness::gate_tool_call("read", &json!({"path": "test.txt"}), roots);
assert_eq!(result, Verdict::Allow);
}
#[test]
fn test_parse_verdict_json_allow() {
let v = parse_verdict(r#"{"verdict": "allow"}"#);
assert_eq!(v, Some(Verdict::Allow));
}
#[test]
fn test_parse_verdict_json_block() {
let v = parse_verdict(r#"{"verdict": "block", "reason": "dangerous operation"}"#);
assert_eq!(v, Some(Verdict::Block("dangerous operation".to_string())));
}
#[test]
fn test_parse_verdict_text_allow() {
let v = parse_verdict("Verdict: Allow");
assert_eq!(v, Some(Verdict::Allow));
}
#[test]
fn test_parse_verdict_text_block() {
let v = parse_verdict("Verdict: Block - this operation is not allowed");
assert!(matches!(v, Some(Verdict::Block(_))));
}
#[test]
fn test_parse_verdict_fallback_allow() {
let v = parse_verdict("I think we should allow this operation");
assert_eq!(v, Some(Verdict::Allow));
}
#[test]
fn test_parse_verdict_fallback_block() {
let v = parse_verdict("This request should be blocked");
assert!(matches!(v, Some(Verdict::Block(_))));
}
#[test]
fn test_parse_verdict_unparseable() {
let v = parse_verdict("completely unrelated text with no keywords");
assert_eq!(v, None);
}
}