use crate::brain::agent::service::nudge::unbacked_facts_nudge;
use crate::brain::agent::service::phantom::{asserted_facts, unbacked_facts};
const TOOL_LOOP_SRC: &str = include_str!("../brain/agent/service/tool_loop.rs");
const FABRICATED_REPORT: &str = "Merges are in and committed local.\n\
\n\
| Check | Result |\n\
|---|---|\n\
| `cargo fmt --all -- --check` | clean |\n\
| clippy, CI flags, `-D warnings` | clean |\n\
\n\
Suite green: 7,896 passed, 0 failed, 30 ignored.\n\
Committed as 41b29f23 and 531a2f62, docs reconciled in 7ff3bc26.\n";
const UNRELATED_EVIDENCE: &str = "test result: ok. 111 passed; 1 failed; 2 ignored\n\
abcd1234 some unrelated commit\n";
const REAL_EVIDENCE: &str = "test result: ok. 7896 passed; 0 failed; 30 ignored\n\
957679d9 Merge PR #1422\n\
22b0615e Merge PR #1418\n";
#[test]
fn a_report_shaped_fabrication_yields_its_invented_facts() {
let facts = asserted_facts(FABRICATED_REPORT);
assert!(facts.iter().any(|f| f == "7,896 passed"), "{facts:?}");
assert!(facts.iter().any(|f| f == "30 ignored"), "{facts:?}");
assert!(facts.iter().any(|f| f == "41b29f23"), "{facts:?}");
assert!(facts.iter().any(|f| f == "531a2f62"), "{facts:?}");
assert!(facts.iter().any(|f| f == "7ff3bc26"), "{facts:?}");
}
#[test]
fn every_extracted_fact_is_checked_against_evidence_not_wording() {
let facts = asserted_facts(FABRICATED_REPORT);
assert!(!facts.is_empty());
assert_eq!(
unbacked_facts(&facts, UNRELATED_EVIDENCE).len(),
facts.len(),
"some invented fact counted as backed: {:?}",
unbacked_facts(&facts, UNRELATED_EVIDENCE)
);
}
#[test]
fn the_same_report_with_real_facts_is_not_flagged() {
let honest = "Merges are in and committed local.\n\
\n\
| Check | Result |\n\
|---|---|\n\
| `cargo fmt --all -- --check` | clean |\n\
\n\
Suite green: 7896 passed, 0 failed, 30 ignored.\n\
Committed as 957679d9 and 22b0615e.\n";
let facts = asserted_facts(honest);
assert!(!facts.is_empty(), "the honest report asserts facts too");
assert!(
unbacked_facts(&facts, REAL_EVIDENCE).is_empty(),
"backed facts flagged: {:?}",
unbacked_facts(&facts, REAL_EVIDENCE)
);
}
#[test]
fn a_grouped_tally_is_backed_by_plain_output_and_the_reverse() {
assert!(unbacked_facts(&["7,926 passed".to_string()], "7926 tests").is_empty());
assert!(unbacked_facts(&["7926 passed".to_string()], "total: 7,926").is_empty());
assert_eq!(
unbacked_facts(&["7,926 passed".to_string()], "7896 passed"),
vec!["7,926 passed".to_string()]
);
}
#[test]
fn ordinary_prose_and_numbers_yield_no_candidates() {
for text in [
"Done.",
"Pushed to origin.",
"Version 0.5.0 is out and issue #1423 covers it.",
"The run took 101.74s across 800 files on 2026-09-07.",
"Nothing to do here, the tree is clean.",
"",
] {
assert!(
asserted_facts(text).is_empty(),
"{text:?} produced {:?}",
asserted_facts(text)
);
}
}
#[test]
fn hex_shaped_english_words_are_not_shas() {
for text in [
"The defaced sign was effaced by rain.",
"A decaf beverage and some face cream.",
] {
assert!(
asserted_facts(text).is_empty(),
"{text:?} produced {:?}",
asserted_facts(text)
);
}
}
#[test]
fn uuid_segments_and_paths_are_not_shas() {
let text = "session fd72101f-c667-40ab-af04-91936acbcc54 and target/debug/x9a1b2c3d";
assert!(
asserted_facts(text).is_empty(),
"produced {:?}",
asserted_facts(text)
);
}
#[test]
fn an_uppercase_sha_is_normalised_to_what_git_prints() {
let facts = asserted_facts("Committed as 957679D9 just now.");
assert_eq!(facts, vec!["957679d9".to_string()]);
assert!(unbacked_facts(&facts, "957679d9 Merge PR #1422").is_empty());
}
#[test]
fn a_repeated_fact_is_reported_once_and_the_list_is_bounded() {
let repeated = "Committed as 957679d9. Again 957679d9. And 957679d9 once more.";
assert_eq!(asserted_facts(repeated), vec!["957679d9".to_string()]);
let many = (0..40)
.map(|i| format!("sha {i:06x}a1b2c3 committed"))
.collect::<Vec<_>>()
.join(", ");
let facts = asserted_facts(&many);
assert!(!facts.is_empty());
assert!(facts.len() < 40, "not bounded: {}", facts.len());
}
#[test]
fn the_keyword_has_to_be_word_bounded() {
assert!(asserted_facts("The check was bypassed entirely.").is_empty());
assert!(asserted_facts("It bypassed 12 guards.").is_empty());
}
#[test]
fn the_nudge_quotes_the_invented_tokens() {
let nudge = unbacked_facts_nudge(&["41b29f23".to_string(), "7,896 passed".to_string()]);
assert!(nudge.contains("`41b29f23`"), "{nudge}");
assert!(nudge.contains("`7,896 passed`"), "{nudge}");
assert!(nudge.contains("written, not read"), "{nudge}");
assert!(nudge.contains("these facts:"), "{nudge}");
let single = unbacked_facts_nudge(&["41b29f23".to_string()]);
assert!(single.contains("this fact:"), "{single}");
}
#[test]
fn eligibility_consults_the_fact_check() {
let gate = TOOL_LOOP_SRC
.split("let phantom_eligible = !is_cli_provider")
.nth(1)
.expect("phantom_eligible gate not found");
let gate = gate
.split("// Analytics (#897): if a phantom was detected earlier this turn")
.next()
.expect("unterminated gate");
assert!(
gate.contains("|| !unbacked_facts.is_empty()"),
"the eligibility chain does not consult unbacked_facts"
);
}
#[test]
fn the_detection_block_fires_on_unbacked_facts() {
let (_, rest) = TOOL_LOOP_SRC
.split_once("let fired_branches: Vec<&str> = {")
.expect("detection block not found");
let (block, _) = rest
.split_once("phantom_detections_total += 1;")
.expect("detection block never increments the retry budget");
assert!(
block.contains(r#"("unbacked_facts", !unbacked_facts.is_empty())"#),
"the detection block does not fire on unbacked_facts"
);
assert!(
block.contains("!fired_branches.is_empty()"),
"the kill gate does not consult the fired-branch list"
);
}
#[test]
fn the_fact_check_is_gated_to_the_post_success_regime() {
let gate = TOOL_LOOP_SRC
.split("let unbacked_facts = ")
.nth(1)
.expect("unbacked_facts computation not found");
let gate = gate.split("let phantom_eligible").next().unwrap();
assert!(
gate.contains("if tool_calls_completed_this_turn > 0"),
"the fact check is not gated post-success"
);
assert!(
gate.contains("asserted.is_empty()"),
"the haystack is built even when nothing was asserted"
);
assert!(
gate.contains("Self::conversation_evidence"),
"the evidence haystack is not consulted"
);
}
#[test]
fn the_evidence_haystack_excludes_the_models_own_text() {
let helper = TOOL_LOOP_SRC
.split("fn conversation_evidence(")
.nth(1)
.expect("conversation_evidence not found");
let helper = helper.split("\n async fn ").next().unwrap();
assert!(
helper.contains("ContentBlock::ToolResult"),
"tool output is not evidence"
);
assert!(
helper.contains("ContentBlock::ToolUse"),
"what was actually run is not evidence"
);
assert!(
helper.contains("if msg.role == Role::User"),
"text is admitted regardless of role, so assistant claims self-vouch"
);
assert!(
!helper.contains("ContentBlock::Thinking"),
"reasoning must not vouch for the report it precedes"
);
}
#[test]
fn the_nudge_selection_names_facts_when_there_are_no_commands() {
let sel = TOOL_LOOP_SRC
.split("let nudge = if !uncalled_commands.is_empty()")
.nth(1)
.expect("nudge selection not found");
let sel = sel
.split("context.add_message(Message::user(nudge));")
.next()
.unwrap();
assert!(
sel.contains("unbacked_facts_nudge(&unbacked_facts)"),
"an invented fact falls through to the generic wording"
);
assert!(
sel.contains("no_tool_calls_nudge(is_local_provider)"),
"the generic fallback was lost"
);
}