mirror of
https://github.com/openai/codex.git
synced 2026-04-24 22:54:54 +00:00
introduce variant typing to policy result
This commit is contained in:
@@ -4,6 +4,7 @@ use std::path::Path;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use anyhow::bail;
|
||||
use codex_execpolicy2::Evaluation;
|
||||
use codex_execpolicy2::PolicyParser;
|
||||
use codex_execpolicy2::load_default_policy;
|
||||
|
||||
@@ -49,14 +50,14 @@ fn cmd_check(policy_path: Option<String>, args: Vec<String>) -> Result<()> {
|
||||
let policy = load_policy(policy_path)?;
|
||||
|
||||
match policy.evaluate(&args) {
|
||||
Some(eval) => {
|
||||
eval @ Evaluation::Match { .. } => {
|
||||
let json = serde_json::to_string_pretty(&eval)?;
|
||||
println!("{json}");
|
||||
}
|
||||
None => {
|
||||
Evaluation::NoMatch => {
|
||||
println!("no match");
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::decision::Decision;
|
||||
use crate::rule::Rule;
|
||||
use crate::rule::RuleMatch;
|
||||
use multimap::MultiMap;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
@@ -10,9 +11,12 @@ pub struct Policy {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Evaluation {
|
||||
pub decision: Decision,
|
||||
pub matched_rules: Vec<crate::rule::RuleMatch>,
|
||||
pub enum Evaluation {
|
||||
NoMatch,
|
||||
Match {
|
||||
decision: Decision,
|
||||
matched_rules: Vec<RuleMatch>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Policy {
|
||||
@@ -24,12 +28,15 @@ impl Policy {
|
||||
&self.rules_by_program
|
||||
}
|
||||
|
||||
pub fn evaluate(&self, cmd: &[String]) -> Option<Evaluation> {
|
||||
let first = cmd.first()?;
|
||||
let Some(rules) = self.rules_by_program.get_vec(first) else {
|
||||
return None;
|
||||
pub fn evaluate(&self, cmd: &[String]) -> Evaluation {
|
||||
let rules = match cmd.first() {
|
||||
Some(first) => match self.rules_by_program.get_vec(first) {
|
||||
Some(rules) => rules,
|
||||
None => return Evaluation::NoMatch,
|
||||
},
|
||||
None => return Evaluation::NoMatch,
|
||||
};
|
||||
let mut matched_rules: Vec<crate::rule::RuleMatch> = Vec::new();
|
||||
let mut matched_rules: Vec<RuleMatch> = Vec::new();
|
||||
let mut best_decision: Option<Decision> = None;
|
||||
for rule in rules {
|
||||
if let Some(matched) = rule.matches(cmd) {
|
||||
@@ -47,9 +54,12 @@ impl Policy {
|
||||
matched_rules.push(matched);
|
||||
}
|
||||
}
|
||||
best_decision.map(|decision| Evaluation {
|
||||
decision,
|
||||
matched_rules,
|
||||
})
|
||||
match best_decision {
|
||||
Some(decision) => Evaluation::Match {
|
||||
decision,
|
||||
matched_rules,
|
||||
},
|
||||
None => Evaluation::NoMatch,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,10 @@ impl PrefixPattern {
|
||||
self.rest.len() + 1
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
pub fn matches_prefix(&self, cmd: &[String]) -> Option<Vec<String>> {
|
||||
if cmd.len() < self.len() || cmd[0] != self.first {
|
||||
return None;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use codex_execpolicy2::Decision;
|
||||
use codex_execpolicy2::Evaluation;
|
||||
use codex_execpolicy2::PolicyParser;
|
||||
use codex_execpolicy2::RuleMatch;
|
||||
use codex_execpolicy2::rule::PatternToken;
|
||||
@@ -19,10 +20,16 @@ prefix_rule(
|
||||
.parse()
|
||||
.expect("parse policy");
|
||||
let cmd = tokens(&["git", "status"]);
|
||||
let eval = policy.evaluate(&cmd).expect("match");
|
||||
assert_eq!(eval.decision, Decision::Allow);
|
||||
let Evaluation::Match {
|
||||
decision,
|
||||
matched_rules,
|
||||
} = policy.evaluate(&cmd)
|
||||
else {
|
||||
panic!("expected match");
|
||||
};
|
||||
assert_eq!(decision, Decision::Allow);
|
||||
assert_eq!(
|
||||
eval.matched_rules,
|
||||
matched_rules,
|
||||
vec![RuleMatch {
|
||||
rule_id: "git_status".to_string(),
|
||||
matched_prefix: tokens(&["git", "status"]),
|
||||
@@ -54,8 +61,10 @@ prefix_rule(
|
||||
),
|
||||
(tokens(&["sh", "-l", "echo", "hi"]), tokens(&["sh", "-l"])),
|
||||
] {
|
||||
let eval = policy.evaluate(&cmd).expect("match");
|
||||
assert_eq!(eval.matched_rules[0].matched_prefix, prefix);
|
||||
let Evaluation::Match { matched_rules, .. } = policy.evaluate(&cmd) else {
|
||||
panic!("expected match");
|
||||
};
|
||||
assert_eq!(matched_rules[0].matched_prefix, prefix);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +94,7 @@ prefix_rule(
|
||||
tokens(&["npm", "i", "--legacy-peer-deps"]),
|
||||
tokens(&["npm", "install", "--no-save", "leftpad"]),
|
||||
] {
|
||||
assert!(policy.evaluate(&cmd).is_some());
|
||||
assert!(matches!(policy.evaluate(&cmd), Evaluation::Match { .. }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,12 +110,14 @@ prefix_rule(
|
||||
"#;
|
||||
let parser = PolicyParser::new("test.policy", policy_src);
|
||||
let policy = parser.parse().expect("parse policy");
|
||||
assert!(policy.evaluate(&tokens(&["git", "status"])).is_some());
|
||||
assert!(
|
||||
policy
|
||||
.evaluate(&tokens(&["git", "reset", "--hard"]))
|
||||
.is_none()
|
||||
);
|
||||
assert!(matches!(
|
||||
policy.evaluate(&tokens(&["git", "status"])),
|
||||
Evaluation::Match { .. }
|
||||
));
|
||||
assert!(matches!(
|
||||
policy.evaluate(&tokens(&["git", "reset", "--hard"])),
|
||||
Evaluation::NoMatch
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -132,10 +143,16 @@ prefix_rule(
|
||||
let policy = parser.parse().expect("parse policy");
|
||||
|
||||
let status = tokens(&["git", "status"]);
|
||||
let status_eval = policy.evaluate(&status).expect("match");
|
||||
assert_eq!(status_eval.decision, Decision::Prompt);
|
||||
let Evaluation::Match {
|
||||
decision: status_decision,
|
||||
matched_rules: status_matches,
|
||||
} = policy.evaluate(&status)
|
||||
else {
|
||||
panic!("expected status to match");
|
||||
};
|
||||
assert_eq!(status_decision, Decision::Prompt);
|
||||
assert_eq!(
|
||||
status_eval.matched_rules,
|
||||
status_matches,
|
||||
vec![
|
||||
RuleMatch {
|
||||
rule_id: "allow_git_status".to_string(),
|
||||
@@ -151,10 +168,16 @@ prefix_rule(
|
||||
);
|
||||
|
||||
let commit = tokens(&["git", "commit", "-m", "hi"]);
|
||||
let commit_eval = policy.evaluate(&commit).expect("match");
|
||||
assert_eq!(commit_eval.decision, Decision::Forbidden);
|
||||
let Evaluation::Match {
|
||||
decision: commit_decision,
|
||||
matched_rules: commit_matches,
|
||||
} = policy.evaluate(&commit)
|
||||
else {
|
||||
panic!("expected commit to match");
|
||||
};
|
||||
assert_eq!(commit_decision, Decision::Forbidden);
|
||||
assert_eq!(
|
||||
commit_eval.matched_rules,
|
||||
commit_matches,
|
||||
vec![
|
||||
RuleMatch {
|
||||
rule_id: "prompt_git".to_string(),
|
||||
|
||||
Reference in New Issue
Block a user