use super::*;
#[tokio::test]
async fn openai_unverified_run_command_blocker_gets_ground_truth_retry() {
let server = MockServer::start().await;
let round = Arc::new(AtomicUsize::new(0));
Mock::given(method("POST"))
.and(path("/v1/chat/completions"))
.respond_with(ScriptedOpenAi {
round: round.clone(),
script: vec![
serde_json::json!({
"content": "I hit a capability wall: run_command is permission-denied; exec not granted."
}),
serde_json::json!({
"content": "I will inspect an actual run_command result before reporting a denial."
}),
],
})
.mount(&server)
.await;
let messages = msgs();
let caveats = Caveats::top();
let uri = server.uri();
let mut c = ctx(&uri, &messages, &caveats);
c.kind = BackendKind::Openai;
c.task = "you should have a \"gh\" command ... test \"gh auth status\" now to tell me if you can use it?";
c.max_tool_rounds = 2;
let (reply, _s, _u, _h) = chat_complete(c, &mut NoMcp).await.expect("dispatch");
assert_eq!(round.load(Ordering::SeqCst), 2, "one corrective retry");
assert!(
reply.contains("actual run_command result"),
"final reply: {reply}"
);
let requests = server.received_requests().await.expect("recorded requests");
let second = body_json(&requests[1]).to_string();
assert!(
second.contains("no returned run_command result this turn contains an exec denial"),
"corrective request: {second}"
);
assert!(
second.contains("Report a denial only when an actual returned result contains one"),
"corrective request: {second}"
);
}
async fn run_openai_script_in(
script: Vec<serde_json::Value>,
workspace: &str,
task: &str,
) -> (String, usize) {
let server = MockServer::start().await;
let round = Arc::new(AtomicUsize::new(0));
Mock::given(method("POST"))
.and(path("/v1/chat/completions"))
.respond_with(ScriptedOpenAi {
round: round.clone(),
script,
})
.mount(&server)
.await;
let messages = msgs();
let caveats = Caveats::top();
let uri = server.uri();
let mut c = ctx(&uri, &messages, &caveats);
c.kind = BackendKind::Openai;
c.workspace = workspace;
c.task = task;
let (reply, _s, _u, _h) = chat_complete(c, &mut NoMcp).await.expect("dispatch");
(reply, round.load(Ordering::SeqCst))
}
#[tokio::test]
#[serial_test::serial(newt_self_verify_env)]
async fn an_armed_self_verify_gate_adds_a_round_when_the_workspace_ships_a_check() {
struct Restore([(&'static str, Option<std::ffi::OsString>); 1]);
impl Drop for Restore {
fn drop(&mut self) {
for (key, value) in &self.0 {
match value {
Some(v) => std::env::set_var(key, v),
None => std::env::remove_var(key),
}
}
}
}
let _restore = Restore(["NEWT_SELF_VERIFY"].map(|k| (k, std::env::var_os(k))));
std::env::set_var("NEWT_SELF_VERIFY", "1");
let script = vec![
serde_json::json!({ "content": "Done — the fix is in place." }),
serde_json::json!({ "content": "Still done." }),
serde_json::json!({ "content": "Confirmed complete." }),
];
let (_, rounds) =
run_openai_script_in(script.clone(), ".", "Run `cargo test` to verify.").await;
assert_eq!(
rounds, 3,
"an unverified conclusion in a workspace shipping `cargo test` costs a round per nudge, capped at SELF_VERIFY_CAP = 2 — that is #1943"
);
let (_, rounds) =
run_openai_script_in(script, NO_CHECKS_WORKSPACE, "Complete the request.").await;
assert_eq!(
rounds, 1,
"with nothing to verify the gate must stay silent — otherwise the assertion above is measuring some other nudge"
);
}
#[tokio::test]
async fn explain_turn_request_user_input_dispatches_and_completes() {
let server = MockServer::start().await;
let round = Arc::new(AtomicUsize::new(0));
Mock::given(method("POST"))
.and(path("/v1/chat/completions"))
.respond_with(ScriptedOpenAi {
round: round.clone(),
script: vec![
serde_json::json!({
"content": null,
"tool_calls": [{
"id": "c1", "type": "function",
"function": { "name": "request_user_input",
"arguments": "{\"question\":\"Which directory should I size?\"}" }
}]
}),
serde_json::json!({
"content": null,
"tool_calls": [{
"id": "c2", "type": "function",
"function": { "name": "run_command",
"arguments": "{\"command\":\"du -sh .\"}" }
}]
}),
serde_json::json!({ "content": "Understood — proceeding with the workspace root." }),
],
})
.mount(&server)
.await;
let messages = msgs();
let caveats = Caveats {
exec: crate::Scope::none(),
..Caveats::top()
};
let uri = server.uri();
let mut c = ctx(&uri, &messages, &caveats);
c.kind = BackendKind::Openai;
c.prompt_disposition = PromptDisposition::Explain;
c.exec_floor = Some(&caveats.exec);
let (reply, _s, _u, _h) = chat_complete(c, &mut NoMcp)
.await
.expect("an Explain turn asking the human completes, never errors");
assert!(
reply.contains("proceeding with the workspace root"),
"the turn ends on the final answer: {reply}"
);
let requests = server.received_requests().await.expect("recorded");
let bodies: Vec<String> = requests
.iter()
.map(|r| String::from_utf8_lossy(&r.body).into_owned())
.collect();
let all = bodies.join("\n---\n");
assert!(
all.contains("no human available this session"),
"request_user_input must dispatch (headless => the recoverable no-human message): {all}"
);
assert!(
!all.contains("Tool `request_user_input` is not available for this request"),
"request_user_input must NOT be disposition-refused in an Explain turn"
);
assert!(
all.contains("capability denied")
&& !all.contains("Tool `run_command` is not available for this request"),
"run_command must respect the session exec denial regardless of Explain style: {all}"
);
}
#[tokio::test]
async fn question_turns_are_never_nudged() {
let server = MockServer::start().await;
let round = Arc::new(AtomicUsize::new(0));
Mock::given(method("POST"))
.and(path("/v1/chat/completions"))
.respond_with(ScriptedOpenAi {
round: round.clone(),
script: vec![
serde_json::json!({ "content": "Let me look into the harness next." }),
serde_json::json!({ "content": "SHOULD NEVER BE REQUESTED" }),
],
})
.mount(&server)
.await;
let question =
"Give me your top 5 improvements to make LLM effectiveness better inside this harness please?";
let messages = vec![
MemMessage::system("you are a test"),
MemMessage::user(question),
];
let caveats = Caveats::top();
let uri = server.uri();
let mut c = ctx(&uri, &messages, &caveats);
c.kind = BackendKind::Openai;
c.task = question;
c.narration_nudge_cap = 2; let (reply, _s, _u, _h) = chat_complete(c, &mut NoMcp).await.expect("dispatch");
assert_eq!(
round.load(Ordering::SeqCst),
1,
"a question turn must never consume a rescue round"
);
assert!(
reply.contains("look into"),
"narration IS the answer: {reply}"
);
}
#[tokio::test]
async fn stale_file_blocker_nudges_ground_truth_check_and_continues() {
let blocker = "\
Summary
What happened: The lib.rs file I was editing grew from ~9400 to ~16808 lines \
between reads — likely modified concurrently by another agent or tool. This \
means my old edit contexts are stale.
Why I'm blocked: I cannot safely use edit_file on lib.rs because the file has \
been modified out from under me. My old line references and context are invalid \
for an 8400-line larger file.
Final Answer / Recommendation
The operator should restore lib.rs to a known-good state (e.g., git checkout \
newt-tui/src/lib.rs).";
let (reply, rounds) = run_openai_script(vec![
serde_json::json!({ "content": blocker }),
serde_json::json!({ "content": "Ground truth checked; lib.rs is clean, so I am continuing." }),
])
.await;
assert_eq!(
rounds, 2,
"stale-file blocker should get one verification nudge"
);
assert!(
reply.contains("lib.rs is clean"),
"returns the post-nudge answer: {reply}"
);
assert!(
!reply.contains("git checkout"),
"must not accept the unverified revert recommendation: {reply}"
);
}
#[test]
fn looks_like_unverified_stale_file_blocker_requires_file_stale_and_blocker_cues() {
assert!(looks_like_unverified_stale_file_blocker(
"The lib.rs file I was editing grew from ~9400 to ~16808 lines between reads. \
Why I'm blocked: I cannot safely use edit_file because the file has been \
modified out from under me. The operator should restore lib.rs."
));
assert!(looks_like_unverified_stale_file_blocker(
"My old line references are invalid and the context is stale. Any edit could \
land in the wrong place and corrupt the code; recommendation: restore the file."
));
assert!(!looks_like_unverified_stale_file_blocker(
"The cache entry is stale, so I refreshed it and continued."
));
assert!(!looks_like_unverified_stale_file_blocker(
"I checked git diff and the file is clean, so I can continue from the verified contents."
));
}
#[test]
fn stale_file_ground_truth_nudge_names_read_only_checks_and_revert_guard() {
let nudge = stale_file_ground_truth_nudge();
assert!(nudge.contains("git status --short"), "{nudge}");
assert!(nudge.contains("git diff -- <file>"), "{nudge}");
assert!(nudge.contains("wc -l <file>"), "{nudge}");
assert!(nudge.contains("re-read the exact target range"), "{nudge}");
assert!(
nudge.contains("Never recommend git checkout/revert"),
"{nudge}"
);
}
#[cfg(unix)]
#[tokio::test]
#[serial_test::serial(newt_self_verify_env)]
async fn responses_smart_gate_sees_a_check_the_model_already_ran() {
use crate::agentic::smart_harness::{AdjudicationSettings, SmartHarness};
struct Restore(Option<std::ffi::OsString>);
impl Drop for Restore {
fn drop(&mut self) {
match self.0.take() {
Some(v) => std::env::set_var("NEWT_SELF_VERIFY", v),
None => std::env::remove_var("NEWT_SELF_VERIFY"),
}
}
}
let _restore = Restore(std::env::var_os("NEWT_SELF_VERIFY"));
std::env::set_var("NEWT_SELF_VERIFY", "1");
const CHECK: &str = "sh -c true";
for ran_check in [true, false] {
let server = MockServer::start().await;
let calls = Arc::new(AtomicUsize::new(0));
Mock::given(method("POST"))
.respond_with(move |_: &Request| {
let first = calls.fetch_add(1, Ordering::SeqCst) == 0;
ResponseTemplate::new(200).set_body_json(if first && ran_check {
serde_json::json!({"id":"resp_1","status":"completed","output":[{
"type":"function_call","id":"fc_1","call_id":"call_1","name":"run_command",
"arguments":serde_json::json!({"command": CHECK}).to_string()}]})
} else {
serde_json::json!({"id":"resp_1","status":"completed","model":"test-model",
"output":[{"type":"message","id":"msg_1","role":"assistant","status":"completed",
"content":[{"type":"output_text","text":"done","annotations":[]}]}]})
})
})
.mount(&server)
.await;
let harness = SmartHarness::new(
agent_harness::Session::new(crate::test_guard::unbudgeted_session_config()).unwrap(),
Arc::new(|_| Box::pin(async { Ok(("\"answer\"".to_string(), None)) })),
AdjudicationSettings::default(),
)
.unwrap();
let ws = tempfile::TempDir::new().unwrap();
let workspace = ws.path().to_string_lossy().into_owned();
let task = format!("Fix the bug. You can run `{CHECK}` to verify.");
let (uri, messages, caveats) = (server.uri(), msgs(), Caveats::top());
let mut context = ctx(&uri, &messages, &caveats);
context.smart_harness = Some(&harness);
context.kind = BackendKind::Openai;
context.workspace = &workspace;
context.task = &task;
openai_responses_complete(context, &mut NoMcp)
.await
.expect("the turn completes");
let nudged = server
.received_requests()
.await
.unwrap()
.iter()
.any(|r| String::from_utf8_lossy(&r.body).contains("Before you finish"));
assert_eq!(
nudged, !ran_check,
"ran_check={ran_check}: the gate nudges exactly when the check was not attempted"
);
}
}