use super::*;
use crate::config::{HookDecision, PreToolOutcome};
use crate::configfile::{resolve_with_layers, ConfigLayers, ResolveOptions};
use crate::permissions::{ApprovalOutcome, ApprovalRequest, PermissionsApprovalHandler};
const DENIED_COMMAND: &str = "rm -rf /tmp/bp10-never-runs";
#[derive(Debug)]
struct NeverCalledProvider;
#[async_trait::async_trait]
impl Provider for NeverCalledProvider {
async fn complete(
&self,
_req: &ChatRequest,
_on_delta: &(dyn for<'a> Fn(&'a str) + Send + Sync),
) -> crate::Result<(ChatMessage, crate::provider::Usage)> {
unreachable!("BP-10 tests never issue a request")
}
}
fn tmp(tag: &str) -> std::path::PathBuf {
let dir = std::env::temp_dir().join(format!(
"supercode-bp10-{tag}-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
std::fs::create_dir_all(&dir).unwrap();
dir
}
fn resolved(preset: &str, tag: &str, overrides: &[String]) -> Config {
let toml = crate::presets::lookup(preset).unwrap();
let layers = ConfigLayers {
overrides,
..Default::default()
};
let mut config = resolve_with_layers(toml, &layers, &ResolveOptions { strict: true })
.unwrap_or_else(|e| panic!("{preset} resolves: {e}"))
.config;
let dir = tmp(tag);
config.permissions_approval_store = Some(dir.join(".approvals.json"));
config.trust_store = Some(dir.join(".trust.json"));
config.cwd = dir;
config
}
fn plain(preset: &str, tag: &str) -> Config {
resolved(preset, tag, &[])
}
fn agent_for(config: Config) -> Agent {
Agent::with_provider(config, Box::new(NeverCalledProvider))
}
fn allows(agent: &Agent, tool: &str, args: &serde_json::Value) -> bool {
agent
.permissions_gate_denial(tool, args, HookDecision::Pass)
.is_none()
}
struct Answer(ApprovalOutcome);
impl PermissionsApprovalHandler for Answer {
fn ask(&self, _req: &ApprovalRequest) -> ApprovalOutcome {
self.0
}
}
struct RecordingDoor {
outcome: ApprovalOutcome,
seen: std::sync::Mutex<Vec<(String, String)>>,
}
impl RecordingDoor {
fn answering(outcome: ApprovalOutcome) -> std::sync::Arc<Self> {
std::sync::Arc::new(RecordingDoor {
outcome,
seen: std::sync::Mutex::new(Vec::new()),
})
}
fn seen(&self) -> Vec<(String, String)> {
self.seen.lock().unwrap().clone()
}
}
impl PermissionsApprovalHandler for RecordingDoor {
fn ask(&self, req: &ApprovalRequest) -> ApprovalOutcome {
self.seen
.lock()
.unwrap()
.push((req.tool.to_string(), req.raw_args.to_string()));
self.outcome
}
}
struct Shared(std::sync::Arc<RecordingDoor>);
impl PermissionsApprovalHandler for Shared {
fn ask(&self, req: &ApprovalRequest) -> ApprovalOutcome {
self.0.ask(req)
}
}
#[test]
fn domain_rules_are_part_of_the_rule_algebra_under_both_presets() {
for preset in ["cc-parity", "cx-parity"] {
let config = resolved(
preset,
"domain-deny",
&["capabilities.permissions.rules.deny=[\"domain(blocked.example)\"]".to_string()],
);
assert!(
config
.tool_deny_patterns
.iter()
.any(|p| p == "domain(blocked.example)"),
"{preset}: the domain rule must reach the ONE engine's deny tier"
);
let agent = agent_for(config);
let err = agent
.ctx
.check_network("https://blocked.example/page")
.expect_err("a domain deny rule must refuse the fetch");
assert!(err.to_string().contains("denied"), "{preset}: {err}");
assert!(
agent.ctx.check_network("https://ok.example/page").is_ok(),
"{preset}: an unnamed host is untouched — this is a rule, not a switch"
);
}
}
#[test]
fn a_domain_rule_globs_like_every_other_subject() {
let config = resolved(
"cc-parity",
"domain-glob",
&["capabilities.permissions.rules.deny=[\"domain(*.evil.example)\"]".to_string()],
);
let agent = agent_for(config);
assert!(agent
.ctx
.check_network("https://api.evil.example/x")
.is_err());
assert!(agent
.ctx
.check_network("https://evil.example.org/x")
.is_ok());
}
#[test]
fn a_domain_ask_resolves_on_the_one_approval_door() {
let config = resolved(
"cc-parity",
"domain-ask",
&["capabilities.permissions.rules.ask=[\"domain(maybe.example)\"]".to_string()],
);
let mut agent = agent_for(config);
assert!(
agent.ctx.check_network("https://maybe.example/x").is_err(),
"no door installed must fail closed"
);
agent.set_permissions_approval_handler(Answer(ApprovalOutcome::Allow));
assert!(agent.ctx.check_network("https://maybe.example/x").is_ok());
}
#[test]
fn an_approval_grant_survives_the_process_and_a_clear_re_asks() {
for preset in ["cc-parity", "cx-parity"] {
let ask_everything = vec!["capabilities.permissions.rules.ask=[\"bash(*)\"]".to_string()];
let first = resolved(preset, "approval-persist", &ask_everything);
let store = first.permissions_approval_store.clone().unwrap();
let cwd = first.cwd.clone();
assert!(
first.permissions_approvals_persist,
"{preset} must arm `capabilities.permissions.approvals.persist`"
);
{
let mut agent = agent_for(first);
agent.set_permissions_approval_handler(Answer(ApprovalOutcome::AllowForSession));
assert!(
allows(
&agent,
"bash",
&serde_json::json!({"command": "git status"})
),
"{preset}: the door approved it"
);
}
assert!(store.exists(), "{preset}: the grant is on disk");
let mut second = resolved(preset, "approval-persist-2", &ask_everything);
second.cwd = cwd;
second.permissions_approval_store = Some(store.clone());
let mut agent = agent_for(second);
agent.set_permissions_approval_handler(Answer(ApprovalOutcome::Deny));
assert!(
allows(
&agent,
"bash",
&serde_json::json!({"command": "git status"})
),
"{preset}: the remembered grant must skip the refusing door entirely"
);
assert!(
!allows(&agent, "bash", &serde_json::json!({"command": "git push"})),
"{preset}: the grant is per subject, not per tool"
);
agent.permissions_approval_cache().clear();
assert!(!store.exists(), "{preset}: clearing removes the store");
assert!(
!allows(
&agent,
"bash",
&serde_json::json!({"command": "git status"})
),
"{preset}: after a clear the refusing door is consulted again"
);
}
}
#[tokio::test]
async fn cx_parity_strips_a_secret_from_the_spawned_shell_and_cc_parity_does_not() {
std::env::set_var("BP10_FAKE_API_KEY", "super-secret");
for (preset, expected) in [("cx-parity", "[]"), ("cc-parity", "[super-secret]")] {
let config = plain(preset, "env");
let registry = crate::tools::ToolRegistry::from_config(&config);
let bash = registry.get("bash").expect("both presets register bash");
let (ctx, _cp, _lsp) = build_tool_context(&config);
let out = bash
.execute(
serde_json::json!({"command": "echo [$BP10_FAKE_API_KEY]"}),
&ctx,
)
.await
.expect("the command runs");
assert!(
out.contains(expected),
"{preset} must render `{expected}`: {out}"
);
}
std::env::remove_var("BP10_FAKE_API_KEY");
}
#[test]
fn a_bare_name_deny_hides_the_tool_from_the_model() {
for (preset, tool) in [("cc-parity", "write_file"), ("cx-parity", "apply_patch")] {
let visible = agent_for(plain(preset, "hide-a"));
assert!(
visible.tool_schemas().iter().any(|s| s.name == tool),
"{preset}: `{tool}` is advertised without the rule"
);
let hidden = agent_for(resolved(
preset,
"hide-b",
&[format!("capabilities.permissions.rules.deny=[\"{tool}\"]")],
));
assert!(
!hidden.tool_schemas().iter().any(|s| s.name == tool),
"{preset}: a bare-name deny must remove `{tool}` from the model's view"
);
assert!(
hidden.tool_schemas().iter().any(|s| s.name == "bash"),
"{preset}: unrelated tools stay advertised"
);
}
}
#[test]
fn a_subject_bearing_deny_refuses_without_hiding() {
let agent = agent_for(resolved(
"cc-parity",
"hide-subject",
&["capabilities.permissions.rules.deny=[\"bash(rm -rf*)\"]".to_string()],
));
assert!(
agent.tool_schemas().iter().any(|s| s.name == "bash"),
"a command-constrained rule must not hide the tool"
);
assert!(!allows(
&agent,
"bash",
&serde_json::json!({ "command": DENIED_COMMAND })
));
}
#[test]
fn an_extra_root_is_writable_and_still_carries_the_protected_path_floor() {
let mut config = plain("cx-parity", "add-dir");
let extra = tmp("add-dir-extra");
config.additional_dirs = vec![extra.clone()];
let agent = agent_for(config);
assert_eq!(
agent.ctx.extra_roots,
vec![extra.clone()],
"the grant reaches ToolContext"
);
assert_eq!(
agent.ctx.sandbox,
crate::tools::SandboxPolicy::WorkspaceWrite
);
assert!(
agent.ctx.check_write(&extra.join("notes.txt")).is_ok(),
"a granted root must be writable"
);
let stranger = tmp("add-dir-stranger");
assert!(
agent.ctx.check_write(&stranger.join("notes.txt")).is_err(),
"an ungranted directory is still outside the workspace"
);
let protected = extra.join(".git").join("config");
assert!(
!allows(
&agent,
"write_file",
&serde_json::json!({"path": protected.to_string_lossy(), "content": "x"})
),
"`.git/**` must still be a floor inside a granted root"
);
}
#[test]
fn a_model_requested_escalation_is_forced_to_the_ask_tier_with_its_justification() {
let door = RecordingDoor::answering(ApprovalOutcome::Deny);
let mut agent = agent_for(plain("cx-parity", "escalate"));
agent.set_permissions_approval_handler(Shared(door.clone()));
assert!(
allows(
&agent,
"bash",
&serde_json::json!({"command": "cargo build"})
),
"an ordinary command is not prompted under model_requested"
);
assert!(door.seen().is_empty(), "and the door was never consulted");
assert!(
!allows(
&agent,
"bash",
&serde_json::json!({
"command": "cargo build",
"with_escalated_permissions": true,
"justification": "the build writes outside the workspace",
})
),
"a refused escalation must stop the call"
);
let seen = door.seen();
assert_eq!(seen.len(), 1, "exactly one prompt: {seen:?}");
assert_eq!(seen[0].0, "bash");
assert!(
seen[0].1.contains("the build writes outside the workspace"),
"the door must see the model's own justification: {seen:?}"
);
}
#[test]
fn an_escalation_never_beats_a_deny_rule() {
let mut agent = agent_for(resolved(
"cx-parity",
"escalate-deny",
&["capabilities.permissions.rules.deny=[\"bash(rm -rf*)\"]".to_string()],
));
agent.set_permissions_approval_handler(Answer(ApprovalOutcome::Allow));
assert!(allows(
&agent,
"bash",
&serde_json::json!({"command": "echo ok", "with_escalated_permissions": true})
));
assert!(
!allows(
&agent,
"bash",
&serde_json::json!({
"command": DENIED_COMMAND,
"with_escalated_permissions": true,
"justification": "trust me",
})
),
"a deny rule is a floor an escalation cannot lift"
);
}
#[tokio::test]
async fn the_escalation_flag_is_refused_without_the_permissions_engine() {
let config = Config::builder().cwd(tmp("escalate-nogate")).build();
let registry = crate::tools::ToolRegistry::from_config(&config);
let bash = registry.get("bash").expect("bash is a default tool");
let ctx = crate::tools::ToolContext::new(config.cwd.clone());
let err = bash
.execute(
serde_json::json!({"command": "echo hi", "with_escalated_permissions": true}),
&ctx,
)
.await
.expect_err("with no engine there is no door to have granted it");
assert!(
err.to_string().contains("requires the permissions engine"),
"{err}"
);
}
#[test]
fn the_escalation_channel_is_advertised_under_cx_parity() {
let agent = agent_for(plain("cx-parity", "escalate-schema"));
let bash = agent
.tool_schemas()
.into_iter()
.find(|s| s.name == "bash")
.expect("cx-parity advertises bash");
let props = bash.parameters["properties"].as_object().unwrap();
assert!(props.contains_key("with_escalated_permissions"));
assert!(props.contains_key("justification"));
}
#[test]
fn a_hook_allow_answers_the_ask() {
let door = RecordingDoor::answering(ApprovalOutcome::Deny);
let mut agent = agent_for(plain("cc-parity", "hook-allow"));
agent.set_permissions_approval_handler(Shared(door.clone()));
let args = serde_json::json!({"command": "echo hi"});
assert!(
agent
.permissions_gate_denial("bash", &args, HookDecision::Allow)
.is_none(),
"a hook `allow` must satisfy the Ask tier without the door"
);
assert!(door.seen().is_empty(), "and the door is never consulted");
assert!(
!allows(&agent, "bash", &args),
"without the hook's answer the same call reaches the refusing door"
);
}
#[test]
fn a_hook_ask_forces_a_prompt_the_rules_would_not_have_asked_for() {
let door = RecordingDoor::answering(ApprovalOutcome::Deny);
let mut agent = agent_for(plain("cx-parity", "hook-ask"));
agent.set_permissions_approval_handler(Shared(door.clone()));
let args = serde_json::json!({"command": "echo hi"});
assert!(allows(&agent, "bash", &args), "the rules alone allow it");
assert!(
agent
.permissions_gate_denial("bash", &args, HookDecision::Ask)
.is_some(),
"the hook's `ask` must reach the door, which refused"
);
assert_eq!(door.seen().len(), 1, "and the door really was consulted");
}
#[test]
fn a_hook_allow_cannot_lift_a_deny_rule() {
let mut agent = agent_for(resolved(
"cc-parity",
"hook-floor",
&["capabilities.permissions.rules.deny=[\"bash(rm -rf*)\"]".to_string()],
));
agent.set_permissions_approval_handler(Answer(ApprovalOutcome::Allow));
assert!(agent
.permissions_gate_denial(
"bash",
&serde_json::json!({ "command": DENIED_COMMAND }),
HookDecision::Allow
)
.is_some());
}
#[test]
fn a_hook_rewrite_is_what_the_engine_judges_and_what_runs() {
let mut config = plain("cc-parity", "hook-rewrite");
config.pre_tool_hook = Some(Box::new(|_name, _args| {
PreToolOutcome::rewrite(serde_json::json!({"command": "echo REWRITTEN"}))
}));
let mut agent = agent_for(config);
agent.set_permissions_approval_handler(Answer(ApprovalOutcome::Allow));
let call = crate::message::ToolCall {
id: "c1".to_string(),
kind: "function".to_string(),
function: crate::message::FunctionCall {
name: "bash".to_string(),
arguments: serde_json::json!({"command": "echo ORIGINAL"}).to_string(),
},
};
match agent.prepare_tool_call(&call) {
PreparedCall::Ready { name, args } => {
assert_eq!(name, "bash");
assert_eq!(args["command"], "echo REWRITTEN");
}
_ => panic!("expected a rewritten Ready"),
}
let mut config = resolved(
"cc-parity",
"hook-rewrite-deny",
&["capabilities.permissions.rules.deny=[\"bash(rm -rf*)\"]".to_string()],
);
config.pre_tool_hook = Some(Box::new(|_n, _a| {
PreToolOutcome::rewrite(serde_json::json!({ "command": DENIED_COMMAND }))
}));
let mut agent = agent_for(config);
agent.set_permissions_approval_handler(Answer(ApprovalOutcome::Allow));
let call = crate::message::ToolCall {
id: "c2".to_string(),
kind: "function".to_string(),
function: crate::message::FunctionCall {
name: "bash".to_string(),
arguments: serde_json::json!({"command": "echo harmless"}).to_string(),
},
};
match agent.prepare_tool_call(&call) {
PreparedCall::Done((text, is_error)) => {
assert!(is_error);
assert!(text.contains("Deny"), "{text}");
}
_ => panic!("the gate must judge the REWRITTEN command, not the model's own"),
}
}
#[test]
fn a_hook_deny_still_refuses_with_its_reason() {
let mut config = plain("cc-parity", "hook-deny");
config.pre_tool_hook = Some(Box::new(|_n, _a| PreToolOutcome::deny("no shells today")));
let mut agent = agent_for(config);
agent.set_permissions_approval_handler(Answer(ApprovalOutcome::Allow));
let call = crate::message::ToolCall {
id: "c3".to_string(),
kind: "function".to_string(),
function: crate::message::FunctionCall {
name: "bash".to_string(),
arguments: serde_json::json!({"command": "echo hi"}).to_string(),
},
};
match agent.prepare_tool_call(&call) {
PreparedCall::Done((text, is_error)) => {
assert!(is_error);
assert!(text.contains("no shells today"), "{text}");
}
_ => panic!("expected a denial"),
}
}
#[test]
fn a_refused_trust_prompt_keeps_project_instructions_out_of_the_prompt() {
const MARKER: &str = "BP10-PROJECT-INSTRUCTION-MARKER";
for preset in ["cc-parity", "cx-parity"] {
let mut config = plain(preset, "trust-refuse");
std::fs::write(config.cwd.join("CLAUDE.md"), MARKER).unwrap();
std::fs::write(config.cwd.join("AGENTS.md"), MARKER).unwrap();
assert!(config.trust_enabled, "{preset} enables the trust module");
let blob = assemble_project_instructions(&config);
assert!(
blob.contains(MARKER),
"{preset}: with no door to ask through, the text still loads"
);
let door = RecordingDoor::answering(ApprovalOutcome::Deny);
config.trust_handler = Some(door.clone());
let blob = assemble_project_instructions(&config);
assert!(
!blob.contains(MARKER),
"{preset}: a refused workspace must not splice its own instructions"
);
assert!(
blob.contains("this workspace is not trusted"),
"{preset}: and it must say so, not fail silently"
);
assert_eq!(door.seen().len(), 1, "{preset}: the door was asked once");
assert_eq!(
door.seen()[0].0,
"trust",
"{preset}: on the one engine's own door"
);
}
}
#[test]
fn a_remembered_trust_answer_survives_and_is_revocable() {
const MARKER: &str = "BP10-PROJECT-INSTRUCTION-MARKER";
let mut config = plain("cc-parity", "trust-remember");
std::fs::write(config.cwd.join("CLAUDE.md"), MARKER).unwrap();
let door = RecordingDoor::answering(ApprovalOutcome::AllowForSession);
config.trust_handler = Some(door.clone());
assert!(assemble_project_instructions(&config).contains(MARKER));
assert_eq!(door.seen().len(), 1);
let refusing = RecordingDoor::answering(ApprovalOutcome::Deny);
config.trust_handler = Some(refusing.clone());
assert!(assemble_project_instructions(&config).contains(MARKER));
assert!(
refusing.seen().is_empty(),
"the recorded answer skipped the door"
);
crate::trust::revoke(&config);
assert!(!assemble_project_instructions(&config).contains(MARKER));
assert_eq!(refusing.seen().len(), 1, "after a revoke it asks again");
}
#[test]
fn the_same_decision_gates_plugins_hooks_and_instructions() {
let mut config = plain("cx-parity", "trust-code");
config.trust_handler = Some(RecordingDoor::answering(ApprovalOutcome::Deny));
for surface in [
crate::trust::TrustSurface::Plugins,
crate::trust::TrustSurface::Hooks,
crate::trust::TrustSurface::Instructions,
] {
assert!(
!crate::trust::is_trusted(&config, surface),
"a refused workspace gates {surface:?} too"
);
}
assert!(!crate::plugins::is_trusted(&config));
}
#[test]
fn a_named_permission_profile_is_selectable_per_run() {
let base = plain("cx-parity", "profile-base");
assert_eq!(
base.sandbox,
crate::tools::SandboxPolicy::WorkspaceWrite,
"cx-parity's own posture stands when no profile is named"
);
let picked = resolved(
"cx-parity",
"profile-ro",
&["capabilities.permissions.profile=read-only".to_string()],
);
assert_eq!(picked.sandbox, crate::tools::SandboxPolicy::ReadOnly);
assert_eq!(picked.approval, base.approval);
}
#[test]
fn a_profile_extends_its_ancestor_and_its_rules_join_the_one_engine() {
let config = resolved(
"cx-parity",
"profile-locked",
&["capabilities.permissions.profile=locked-down".to_string()],
);
assert_eq!(
config.sandbox,
crate::tools::SandboxPolicy::ReadOnly,
"inherited from `read-only`"
);
assert!(
config.tool_deny_patterns.iter().any(|p| p == "bash"),
"and its own deny rules are in the engine's deny tier"
);
let agent = agent_for(config);
assert!(
!agent.tool_schemas().iter().any(|s| s.name == "bash"),
"a bare-name deny in a profile hides the tool, like any other"
);
}
#[test]
fn a_profile_cannot_remove_a_floor_the_base_layer_set() {
let config = resolved(
"cx-parity",
"profile-monotonic",
&[
"capabilities.permissions.rules.deny=[\"bash(curl*)\"]".to_string(),
"capabilities.permissions.profile=locked-down".to_string(),
],
);
assert!(
config.tool_deny_patterns.iter().any(|p| p == "bash(curl*)"),
"the base layer's own deny rule survives the bundle: {:?}",
config.tool_deny_patterns
);
assert!(config.tool_deny_patterns.iter().any(|p| p == "bash"));
}
#[test]
fn an_unknown_profile_name_warns_and_changes_nothing() {
let toml = crate::presets::lookup("cx-parity").unwrap();
let overrides = vec!["capabilities.permissions.profile=nope".to_string()];
let layers = ConfigLayers {
overrides: &overrides,
..Default::default()
};
let r = resolve_with_layers(toml, &layers, &ResolveOptions { strict: true }).unwrap();
assert_eq!(
r.config.sandbox,
crate::tools::SandboxPolicy::WorkspaceWrite
);
assert!(
r.warnings.iter().any(|w| w.contains("names no")),
"{:?}",
r.warnings
);
}
#[test]
fn the_presets_state_their_network_posture() {
let cx = plain("cx-parity", "net-cx");
assert!(
cx.network_policy.as_ref().expect("cx sets it").enabled,
"workspace_write cuts subprocess network"
);
let cc = plain("cc-parity", "net-cc");
assert!(
!cc.network_policy.as_ref().expect("cc states it").enabled,
"CC's `/sandbox` (and its proxy) is opt-in"
);
}
#[test]
fn an_ask_with_no_door_still_denies_under_both_presets() {
for preset in ["cc-parity", "cx-parity"] {
let config = resolved(
preset,
"default-deny",
&["capabilities.permissions.rules.ask=[\"bash(*)\"]".to_string()],
);
assert!(config.permissions_enabled, "{preset}");
let agent = agent_for(config);
assert!(
!allows(&agent, "bash", &serde_json::json!({"command": "echo hi"})),
"{preset}: no handler installed must deny an Ask"
);
}
}
#[test]
fn every_new_gate_still_loses_to_one_deny_rule() {
let mut agent = agent_for(resolved(
"cx-parity",
"one-engine",
&["capabilities.permissions.rules.deny=[\"bash(rm -rf*)\"]".to_string()],
));
agent.set_permissions_approval_handler(Answer(ApprovalOutcome::AllowForSession));
assert!(allows(
&agent,
"bash",
&serde_json::json!({"command": "echo hi"})
));
assert!(agent
.permissions_gate_denial(
"bash",
&serde_json::json!({
"command": DENIED_COMMAND,
"with_escalated_permissions": true,
"justification": "please",
}),
HookDecision::Allow
)
.is_some());
}