mirror of
https://github.com/hanzoai/codex.git
synced 2026-10-30 14:59:26 +00:00
## What changed - Extend `GuardianModelPolicy` with controls for uncategorized tools, unscored actions, the initial computer-use call allowance, and sandboxed command coverage. - Add `GuardianPolicyLoader` in `codex-config` to translate legacy settings, preserve catalog policy precedence, and enforce reviewer requirements. Apply live model review requirements through `ConfigRequirements::constrain_guardian_policy`. - Use the shared model policy for Guardian scoring and approval, replacing the extension-local policy wrapper while retaining legacy defaults. ## Testing Add configuration tests for catalog precedence, legacy scope fallback, required-model constraints, and legacy computer-use opt-in and feature gating. Adapt existing extension tests to consume the shared policy. GitOrigin-RevId: 6fed1c3a831964ad28ea7de5cb19e74e323c1204
125 lines
4.7 KiB
Rust
125 lines
4.7 KiB
Rust
use super::*;
|
|
use crate::FeatureRequirementsToml;
|
|
use crate::RequirementSource;
|
|
use crate::Sourced;
|
|
use pretty_assertions::assert_eq;
|
|
use serde_json::json;
|
|
use std::collections::BTreeSet;
|
|
|
|
#[test]
|
|
fn explicit_catalog_policy_preserves_precedence_over_the_legacy_switch() {
|
|
let catalog: GuardianModelPolicy = serde_json::from_value(json!({
|
|
"computer_use": "adaptive", "shell": "disabled"
|
|
}))
|
|
.unwrap();
|
|
for legacy in [json!(false), json!({"enabled": false})] {
|
|
let legacy = serde_json::from_value(legacy).unwrap();
|
|
for managed in [None, Some(false), Some(true)] {
|
|
let requirements = ConfigRequirements {
|
|
feature_requirements: managed.map(|enabled| {
|
|
Sourced::new(
|
|
FeatureRequirementsToml {
|
|
entries: [("guardianv2".to_owned(), enabled)].into(),
|
|
},
|
|
RequirementSource::Unknown,
|
|
)
|
|
}),
|
|
..Default::default()
|
|
};
|
|
let mut model = test_model();
|
|
model.guardian = Some(catalog.clone());
|
|
assert_eq!(
|
|
GuardianPolicyLoader::new(Some(&legacy), &requirements).resolve(Some(&model)),
|
|
catalog
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn legacy_scope_applies_only_to_models_without_catalog_policy() {
|
|
let legacy = serde_json::from_value(json!({
|
|
"enabled": true,
|
|
"review_scope": {"computer_use_only": false, "sandboxed_exec_commands": true}
|
|
}))
|
|
.unwrap();
|
|
let loader = GuardianPolicyLoader::new(Some(&legacy), &ConfigRequirements::default());
|
|
let expected = GuardianModelPolicy {
|
|
computer_use: Some(GuardianReviewMode::Adaptive),
|
|
shell: Some(GuardianReviewMode::Adaptive),
|
|
file_changes: Some(GuardianReviewMode::Adaptive),
|
|
mcp: Some(GuardianReviewMode::Adaptive),
|
|
network: Some(GuardianReviewMode::Adaptive),
|
|
permissions: Some(GuardianReviewMode::Adaptive),
|
|
other_tools: GuardianReviewMode::Adaptive,
|
|
unscored_action: GuardianUnscoredAction::AgeScore,
|
|
initial_cua_call: Some(false),
|
|
sandboxed_exec_commands: Some(true),
|
|
};
|
|
assert_eq!(loader.resolve(/*model*/ None), expected);
|
|
let catalog: GuardianModelPolicy =
|
|
serde_json::from_value(json!({"computer_use": "adaptive"})).unwrap();
|
|
let mut model = test_model();
|
|
model.guardian = Some(catalog.clone());
|
|
assert_eq!(loader.resolve(Some(&model)), catalog);
|
|
}
|
|
|
|
#[test]
|
|
fn required_models_preserve_cua_allowance_and_disable_all_tools_scoring() {
|
|
let requirements = ConfigRequirements {
|
|
auto_review_required_models: Some(Sourced::new(
|
|
BTreeSet::from(["protected-model".to_owned()]),
|
|
RequirementSource::Unknown,
|
|
)),
|
|
..Default::default()
|
|
};
|
|
let legacy = serde_json::from_value(json!({
|
|
"enabled": true, "review_scope": {"computer_use_only": false}
|
|
}))
|
|
.unwrap();
|
|
let cua_only = FeatureToml::Enabled(true);
|
|
for legacy in [Some(&cua_only), Some(&legacy)] {
|
|
let loader = GuardianPolicyLoader::new(legacy, &requirements);
|
|
let ordinary = loader.resolve(/*model*/ None);
|
|
let mut expected = ordinary.clone();
|
|
expected.disable_scoring();
|
|
if ordinary.allows_initial_cua_call() {
|
|
expected.computer_use = ordinary.computer_use;
|
|
}
|
|
let mut actual = ordinary;
|
|
requirements.constrain_guardian_policy(&mut actual, "provider/protected-model");
|
|
assert_eq!(actual, expected);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn legacy_cua_opt_in_and_disabled_feature_preserve_scoring_behavior() {
|
|
for legacy in [
|
|
json!(false),
|
|
json!({"review_scope": {"computer_use_only": true}}),
|
|
json!(true),
|
|
] {
|
|
let enabled = legacy == json!(true);
|
|
let legacy = serde_json::from_value(legacy).unwrap();
|
|
let loader = GuardianPolicyLoader::new(Some(&legacy), &ConfigRequirements::default());
|
|
for required in [false, true] {
|
|
let mut model = test_model();
|
|
model.node_repl_auto_review_required = required;
|
|
let policy = loader.resolve(Some(&model));
|
|
assert_eq!(policy.scoring_enabled(), enabled && required);
|
|
assert_eq!(model.computer_use_review_required(), required);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn test_model() -> ModelInfo {
|
|
serde_json::from_value(json!({
|
|
"slug": "test-model", "display_name": "test-model",
|
|
"supported_reasoning_levels": [], "shell_type": "shell_command",
|
|
"visibility": "list", "supported_in_api": true, "priority": 0,
|
|
"support_verbosity": false, "truncation_policy": {"mode": "bytes", "limit": 10000},
|
|
"experimental_supported_tools": []
|
|
}))
|
|
.unwrap()
|
|
}
|