#![allow(clippy::unwrap_used)]
use std::sync::Arc;
use polyc_agent::{RunTurnOptions, TurnResult};
use polyc_capability::{Capability, CapabilitySet};
use polyc_llm::Message;
use polyc_llm::turn::{STUB_TOOL_ARGS_ENV, STUB_TOOL_CALL_ENV, StubProvider};
use polyc_proto::proto::polychrome::agent::v1::content;
use polyc_tools::{CompositeRegistry, ToolRegistry};
const PROBE_CONTENTS: &str = "polychrome-2508-probe";
const SHELL_MARKER: &str = "polychrome-2508-ran";
fn tool_results(turn: &TurnResult) -> Vec<String> {
turn.messages
.iter()
.filter_map(
|message| match message.content.as_option().and_then(|c| c.r#type.as_ref()) {
Some(content::Type::ToolResult(result)) => Some(format!("{result:?}")),
_ => None,
},
)
.collect()
}
#[tokio::test]
async fn an_execution_grant_without_egress_refuses_shell_exec_but_not_a_local_read() {
let workspace = tempfile::tempdir().unwrap();
std::fs::write(workspace.path().join("probe.txt"), PROBE_CONTENTS).unwrap();
let root = workspace.path().to_path_buf();
let turn = temp_env::async_with_vars(
[
(STUB_TOOL_CALL_ENV, Some("file_read,shell_exec")),
(
STUB_TOOL_ARGS_ENV,
Some(
r#"{"file_read":{"path":"probe.txt"},"shell_exec":{"command":"echo polychrome-2508-ran"}}"#,
),
),
],
async move {
let tools = CompositeRegistry::new().with(Arc::new(ToolRegistry::rooted_at(root, None)));
let granted = CapabilitySet::of(Capability::LocalRead).with(Capability::LocalWrite);
polyc_turn_runner::run_turn_captured_under_grant(
&StubProvider,
&tools,
"stub",
vec![Message::user("read the probe, then run the shell")],
RunTurnOptions::default(),
granted,
)
.await
.expect("the stub provider never fails mid-stream")
},
)
.await;
let results = tool_results(&turn);
assert_eq!(
results.len(),
2,
"the turn puts both tools through the gate: {results:?}"
);
assert!(
results[0].contains(PROBE_CONTENTS),
"a LocalRead grant must still permit the workspace read: {}",
results[0]
);
assert!(
results[1].contains("Execution grant does not authorize shell_exec"),
"the grant must refuse shell_exec by name: {}",
results[1]
);
assert!(
results[1].contains("arbitrary-egress"),
"the refusal must name the missing egress authority: {}",
results[1]
);
assert!(
!results[1].contains(SHELL_MARKER),
"shell_exec must never reach process creation: {}",
results[1]
);
assert!(
turn.unattended_denials.is_empty(),
"an attended turn records no unattended denial: {:?}",
turn.unattended_denials
);
assert!(
turn.pending_approvals.is_empty(),
"no human approval can widen the grant, so nothing pauses: {:?}",
turn.pending_approvals
);
}