use agent_toolchain::native_git::CommitPolicy;
pub(crate) const MAX_REVIEWED_COMMITS: usize = 16;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct Expected {
lines: Vec<String>,
newt_tags: Vec<String>,
required: Vec<String>,
vouched: bool,
}
impl Expected {
pub(crate) fn from_policy(policy: Option<&dyn CommitPolicy>) -> Self {
let mut newt_tags = vec![format!("<{}>", crate::agent_identity::DEFAULT_AGENT_EMAIL)];
let Some(policy) = policy else {
return Self {
newt_tags,
..Self::default()
};
};
if let Some(email) = policy.agent_email() {
let tag = format!("<{email}>");
if !newt_tags.contains(&tag) {
newt_tags.push(tag);
}
}
let Ok(rendered) = policy.finalize_message("") else {
return Self {
newt_tags,
..Self::default()
};
};
let lines: Vec<String> = rendered
.lines()
.map(str::trim_end)
.filter(|l| !l.is_empty())
.map(str::to_owned)
.collect();
let operator = policy.operator_line();
let required = lines
.iter()
.filter(|l| {
crate::attribution::is_newt_provenance(l) || operator.as_deref() == Some(l.as_str())
})
.cloned()
.collect();
Self {
lines,
newt_tags,
required,
vouched: true,
}
}
fn claims_newt(&self, line: &str) -> bool {
(line.starts_with("Co-authored-by: ")
&& self
.newt_tags
.iter()
.any(|tag| line.ends_with(tag.as_str())))
|| crate::attribution::is_newt_provenance(line)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Finding {
Forged(String),
Missing(String),
Unvouched(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HeadError {
ReadFailed,
}
impl std::fmt::Display for HeadError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "HEAD read failed")
}
}
impl std::error::Error for HeadError {}
pub trait CommitReader: Send + Sync {
fn head(&self) -> std::result::Result<Option<String>, HeadError>;
fn landed(&self, before: Option<&str>, head: &str, limit: usize) -> Vec<(String, String)>;
fn stat(&self, oid: &str) -> String;
}
pub(crate) type SharedReader = std::sync::Arc<dyn CommitReader>;
pub trait ReaderSource: Send + Sync {
fn open(
&self,
cwd: &std::path::Path,
read_scope: &crate::Scope<String>,
) -> Option<SharedReader>;
}
pub(crate) struct NoOpRepo;
impl CommitReader for NoOpRepo {
fn head(&self) -> std::result::Result<Option<String>, HeadError> {
Ok(None)
}
fn landed(&self, _: Option<&str>, _: &str, _: usize) -> Vec<(String, String)> {
Vec::new()
}
fn stat(&self, _: &str) -> String {
String::new()
}
}
pub struct HostReaders;
impl ReaderSource for HostReaders {
fn open(
&self,
cwd: &std::path::Path,
read_scope: &crate::Scope<String>,
) -> Option<SharedReader> {
crate::agentic::check_git_read_scope("metadata", read_scope).ok()?;
Some(std::sync::Arc::new(HostRepo {
cwd: cwd.to_path_buf(),
read_scope: read_scope.clone(),
}))
}
}
pub struct NoOpReaders;
impl ReaderSource for NoOpReaders {
fn open(&self, _: &std::path::Path, _: &crate::Scope<String>) -> Option<SharedReader> {
None
}
}
pub(crate) const SNAPSHOT_DEADLINE: std::time::Duration = std::time::Duration::from_secs(5);
pub(crate) const FINISH_DEADLINE: std::time::Duration = std::time::Duration::from_secs(15);
async fn off_executor<T: Send + 'static>(
deadline: std::time::Duration,
read: impl FnOnce() -> T + Send + 'static,
) -> Option<T> {
tokio::time::timeout(deadline, tokio::task::spawn_blocking(read))
.await
.ok()?
.ok()
}
pub(crate) struct Review {
pending: Pending,
reader: SharedReader,
}
impl Review {
pub(crate) async fn begin(
reader: SharedReader,
policy: Option<&dyn CommitPolicy>,
) -> Option<Self> {
Self::begin_within(reader, policy, SNAPSHOT_DEADLINE).await
}
async fn begin_within(
reader: SharedReader,
policy: Option<&dyn CommitPolicy>,
deadline: std::time::Duration,
) -> Option<Self> {
let expected = Expected::from_policy(policy);
let snapshot = reader.clone();
let before = match off_executor(deadline, move || snapshot.head()).await {
Some(Ok(before)) => before,
Some(Err(HeadError::ReadFailed)) | None => return None,
};
Some(Self {
pending: Pending { before, expected },
reader,
})
}
pub(crate) async fn conclude(self) -> (Option<String>, Vec<(String, String)>) {
self.conclude_within(FINISH_DEADLINE).await
}
async fn conclude_within(
self,
deadline: std::time::Duration,
) -> (Option<String>, Vec<(String, String)>) {
let Self { pending, reader } = self;
off_executor(deadline, move || pending.finish(reader.as_ref()))
.await
.unwrap_or_default()
}
}
#[cfg(test)]
pub(crate) fn review_landed(
reader: &dyn CommitReader,
before: Option<&str>,
expected: &Expected,
) -> Option<String> {
let Ok(Some(head)) = reader.head() else {
return None;
};
if before == Some(head.as_str()) {
return None;
}
review_commits(
&reader.landed(before, &head, MAX_REVIEWED_COMMITS),
expected,
)
}
fn review_commits(commits: &[(String, String)], expected: &Expected) -> Option<String> {
let mut out = String::new();
for (oid, message) in commits {
let findings = check_trailers(message, expected);
let leaks = leak_findings(message);
let short = &oid[..oid.len().min(12)];
if !leaks.is_empty() {
out.push_str(&format!("commit review: {short} message content\n"));
for leak in leaks {
out.push_str(&format!(" - {leak}\n"));
}
}
if findings.is_empty() {
continue;
}
out.push_str(&format!(
"commit review: {short} provenance does not match the attribution ledger\n"
));
for finding in findings {
let (label, line) = match finding {
Finding::Forged(l) => ("not rendered by the ledger", l),
Finding::Missing(l) => ("missing from the commit", l),
Finding::Unvouched(l) => ("no ledger in this session vouches for", l),
};
out.push_str(&format!(" - {label}: {line}\n"));
}
}
if out.is_empty() {
return None;
}
if out.contains("provenance does not match") {
out.push_str(
" Attribution is harness-owned: `/byline` shows the exact block; amend the \
message to match it or drop the newt-address lines.\n",
);
}
Some(out)
}
pub(crate) struct Pending {
before: Option<String>,
expected: Expected,
}
impl Pending {
#[cfg(test)]
pub(crate) fn start(reader: &dyn CommitReader, policy: Option<&dyn CommitPolicy>) -> Self {
Self {
before: reader.head().unwrap_or_default(),
expected: Expected::from_policy(policy),
}
}
pub(crate) fn finish(
&self,
reader: &dyn CommitReader,
) -> (Option<String>, Vec<(String, String)>) {
let Ok(Some(head)) = reader.head() else {
return Default::default();
};
if self.before.as_deref() == Some(head.as_str()) {
return Default::default();
}
let commits = reader.landed(self.before.as_deref(), &head, MAX_REVIEWED_COMMITS);
let prompts = commits
.iter()
.take(MAX_CONTENT_REVIEWS)
.map(|(oid, message)| (oid.clone(), content_prompt(message, &reader.stat(oid))))
.collect();
(review_commits(&commits, &self.expected), prompts)
}
}
pub(crate) fn render_content(oid: &str, findings: &[String]) -> Option<String> {
if findings.is_empty() {
return None;
}
let short = &oid[..oid.len().min(12)];
let mut out = format!("commit review: {short} content (model, advisory)\n");
for finding in findings {
out.push_str(&format!(" - {finding}\n"));
}
Some(out)
}
pub struct HostRepo {
pub(crate) cwd: std::path::PathBuf,
pub(crate) read_scope: crate::Scope<String>,
}
const GIT_READ_DEADLINE: std::time::Duration = std::time::Duration::from_secs(3);
const GIT_OUTPUT_CAP: usize = 256 * 1024;
impl HostRepo {
fn run(&self, args: &[&str]) -> std::result::Result<Option<Vec<u8>>, HeadError> {
let mut child = crate::git_hardening::metadata_git(&self.cwd, args, &self.read_scope)
.map_err(|_| HeadError::ReadFailed)?
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.map_err(|_| HeadError::ReadFailed)?;
let started = std::time::Instant::now();
let stdout_rx = child
.stdout
.take()
.map(|stdout| read_bounded(stdout, GIT_OUTPUT_CAP, GIT_READ_DEADLINE));
let stderr_rx = child
.stderr
.take()
.map(|stderr| read_bounded(stderr, GIT_OUTPUT_CAP, GIT_READ_DEADLINE));
loop {
match (
child.try_wait().map_err(|_| HeadError::ReadFailed)?,
started.elapsed() < GIT_READ_DEADLINE,
) {
(Some(status), _) => {
let remaining = GIT_READ_DEADLINE.saturating_sub(started.elapsed());
let stdout = collect_output(stdout_rx, remaining)?;
let stderr = collect_output(stderr_rx, remaining)?;
return classify(args, status.code(), stdout, stderr);
}
(None, true) => {
std::thread::sleep(std::time::Duration::from_millis(5));
}
(None, false) => {
let _ = child.kill();
let _ = child.wait();
return Err(HeadError::ReadFailed);
}
}
}
}
}
fn classify(
args: &[&str],
exit_code: Option<i32>,
stdout: Vec<u8>,
stderr: Vec<u8>,
) -> std::result::Result<Option<Vec<u8>>, HeadError> {
if exit_code == Some(0) {
return Ok(Some(stdout));
}
if args == ["rev-parse", "--verify", "-q", "HEAD"] && exit_code == Some(1) && stderr.is_empty()
{
return Ok(None);
}
Err(HeadError::ReadFailed)
}
fn read_bounded(
source: impl std::io::Read + Send + 'static,
cap: usize,
_deadline: std::time::Duration,
) -> std::sync::mpsc::Receiver<Option<Vec<u8>>> {
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
let mut buf = Vec::new();
let limit = u64::try_from(cap).unwrap_or(u64::MAX).saturating_add(1);
let ok =
std::io::Read::read_to_end(&mut std::io::Read::take(source, limit), &mut buf).is_ok();
let _ = tx.send(ok.then_some(buf).filter(|buf| buf.len() <= cap));
});
rx
}
fn collect_output(
rx: Option<std::sync::mpsc::Receiver<Option<Vec<u8>>>>,
remaining: std::time::Duration,
) -> std::result::Result<Vec<u8>, HeadError> {
match rx {
Some(rx) => rx
.recv_timeout(remaining)
.map_err(|_| HeadError::ReadFailed)?
.ok_or(HeadError::ReadFailed),
None => Ok(Vec::new()),
}
}
fn rev_list_args(before: Option<&str>, head: &str, limit: usize) -> Vec<String> {
let range = before.map_or_else(|| head.to_owned(), |b| format!("{b}..{head}"));
vec!["rev-list".into(), format!("--max-count={limit}"), range]
}
impl CommitReader for HostRepo {
fn head(&self) -> std::result::Result<Option<String>, HeadError> {
let out = self.run(&["rev-parse", "--verify", "-q", "HEAD"])?;
let Some(out) = out else {
return Ok(None);
};
let oid = String::from_utf8(out)
.ok()
.ok_or(HeadError::ReadFailed)?
.trim()
.to_owned();
Ok((!oid.is_empty()).then_some(oid))
}
fn landed(&self, before: Option<&str>, head: &str, limit: usize) -> Vec<(String, String)> {
let args = rev_list_args(before, head, limit);
let args: Vec<&str> = args.iter().map(String::as_str).collect();
let list = match self.run(&args) {
Ok(Some(list)) => list,
_ => return Vec::new(),
};
String::from_utf8_lossy(&list)
.lines()
.filter_map(|oid| {
let raw = self.run(&["cat-file", "commit", oid]).ok().flatten()?;
let at = raw.windows(2).position(|w| w == b"\n\n")?;
Some((
oid.to_owned(),
String::from_utf8_lossy(&raw[at + 2..]).into_owned(),
))
})
.collect()
}
fn stat(&self, oid: &str) -> String {
self.run(&[
"show",
"--stat",
"--format=",
"--no-color",
"--no-textconv",
"--no-ext-diff",
oid,
])
.ok()
.flatten()
.map(|out| String::from_utf8_lossy(&out).into_owned())
.unwrap_or_default()
}
}
const LEAK_PATTERNS: &[(&str, &str)] = &[
("/home/", "absolute local path"),
("/users/", "absolute local path"),
("claude.ai/code/session", "harness session link"),
("claude-session:", "harness session link"),
];
pub(crate) fn leak_findings(message: &str) -> Vec<String> {
let lower = message.to_ascii_lowercase();
let mut out: Vec<String> = Vec::new();
for (needle, label) in LEAK_PATTERNS {
let mut from = 0;
while let Some(at) = lower[from..].find(needle).map(|i| i + from) {
from = at + needle.len();
let token_end = message[at..]
.find(char::is_whitespace)
.map_or(message.len(), |i| at + i);
let token = &message[at..token_end];
if needle.ends_with('/') && token.len() <= needle.len() {
continue;
}
let finding = format!("leak ({label}): {token}");
if !out.contains(&finding) {
out.push(finding);
}
}
}
out
}
pub(crate) const MAX_CONTENT_REVIEWS: usize = 4;
const CONTENT_INPUT_CAP: usize = 8 * 1024;
const CONTENT_KINDS: &[&str] = &["overclaim", "inaccurate", "leak"];
fn capped(text: &str) -> &str {
let mut end = text.len().min(CONTENT_INPUT_CAP);
while !text.is_char_boundary(end) {
end -= 1;
}
&text[..end]
}
pub(crate) fn content_prompt(message: &str, stat: &str) -> String {
let evidence =
serde_json::json!({"commit_message": capped(message), "diff_stat": capped(stat)});
format!(
"Review this commit message against its diff stat. Flag only: claims the \
stat cannot support (overclaim), statements contradicted by the stat \
(inaccurate), and private data such as local paths, hostnames or \
session links (leak). Attribution trailers are checked elsewhere; ignore \
them. Reply with JSON only: {{\"findings\":[{{\"kind\":\"overclaim|inaccurate|leak\",\
\"detail\":\"...\"}}]}} — an empty list when nothing is wrong.\n{evidence}"
)
}
pub(crate) fn parse_content_findings(raw: &str) -> Vec<String> {
#[derive(serde::Deserialize)]
struct Item {
kind: String,
detail: String,
}
#[derive(serde::Deserialize)]
struct Reply {
findings: Vec<Item>,
}
let (Some(start), Some(end)) = (raw.find('{'), raw.rfind('}')) else {
return Vec::new();
};
let Ok(reply) = serde_json::from_str::<Reply>(raw.get(start..=end).unwrap_or_default()) else {
return Vec::new();
};
reply
.findings
.into_iter()
.filter(|item| {
CONTENT_KINDS.contains(&item.kind.as_str()) && !item.detail.trim().is_empty()
})
.map(|item| format!("{}: {}", item.kind, item.detail.trim()))
.collect()
}
pub(crate) fn check_trailers(message: &str, expected: &Expected) -> Vec<Finding> {
let lines: Vec<&str> = message.lines().map(str::trim_end).collect();
let claims: Vec<&str> = lines
.iter()
.copied()
.filter(|l| expected.claims_newt(l))
.collect();
if !expected.vouched {
return claims
.into_iter()
.map(|l| Finding::Unvouched(l.into()))
.collect();
}
let mut findings: Vec<Finding> = claims
.iter()
.filter(|l| !expected.lines.iter().any(|e| e == *l))
.map(|l| Finding::Forged((*l).into()))
.collect();
if !claims.is_empty() {
findings.extend(
expected
.required
.iter()
.filter(|e| !lines.contains(&e.as_str()))
.map(|e| Finding::Missing(e.clone())),
);
}
findings
}
#[cfg(test)]
mod tests {
use super::*;
const NEWT: &str = crate::agent_identity::DEFAULT_AGENT_EMAIL;
struct LedgerPolicy(crate::attribution::CommitAttribution);
impl CommitPolicy for LedgerPolicy {
fn finalize_message(&self, message: &str) -> Result<String, String> {
Ok(self.0.finalize_message(message))
}
fn agent_email(&self) -> Option<String> {
Some(self.0.agent_email.clone())
}
fn operator_line(&self) -> Option<String> {
self.0.operator_trailer()
}
fn signing_required(&self) -> bool {
false
}
fn sign_commit(&self, _: &[u8]) -> Result<String, String> {
Err("unused".into())
}
fn committed(&self) {}
}
fn ledger(agent_email: &str) -> crate::attribution::CommitAttribution {
let mut a = crate::attribution::CommitAttribution::from_runtime(
"claude-opus-5-5",
Some("Op Erator".into()),
agent_email,
);
a.harness_name = "newt-agent".into();
a.harness_version = "0.8.0".into();
a.harness_build_revision = "cad38c1394ef".into();
a.operator_email = Some("op@example.com".into());
a
}
fn expected(agent_email: &str) -> Expected {
Expected::from_policy(Some(&LedgerPolicy(ledger(agent_email))))
}
#[test]
fn host_shell_route_wrong_build_trailer_is_flagged() {
let message =
format!("fix: thing\n\nCo-authored-by: claude-opus-5-5 (v0.8.0 9ba3d8e3) <{NEWT}>\n");
let findings = check_trailers(&message, &expected(NEWT));
assert!(
findings.contains(&Finding::Forged(format!(
"Co-authored-by: claude-opus-5-5 (v0.8.0 9ba3d8e3) <{NEWT}>"
))),
"{findings:?}"
);
assert!(
findings.contains(&Finding::Missing(
"Co-authored-by: Op Erator <op@example.com>".into()
)),
"{findings:?}"
);
}
#[test]
fn governed_route_wrong_build_trailer_is_flagged() {
let custom = "bot@example.org";
let typed = format!("Co-authored-by: claude-opus-5-5 (v0.8.0 9ba3d8e3) <{NEWT}>");
let message = ledger(custom).finalize_message(&format!("fix: thing\n\n{typed}\n"));
assert!(message.contains(&typed), "precondition: finalizer kept it");
let findings = check_trailers(&message, &expected(custom));
assert_eq!(findings, vec![Finding::Forged(typed)]);
}
#[test]
fn canonical_governed_message_passes() {
let message = ledger(NEWT).finalize_message("fix: thing\n\nbody\n");
assert_eq!(check_trailers(&message, &expected(NEWT)), Vec::new());
}
#[test]
fn unattributed_host_commit_is_not_a_provenance_finding() {
let message = "fix: thing\n\nCo-authored-by: Someone <s@example.com>\n";
assert_eq!(check_trailers(message, &expected(NEWT)), Vec::new());
}
#[test]
fn forged_provenance_line_is_flagged() {
let canonical = ledger(NEWT).finalize_message("fix: thing\n");
let forged = canonical.replace("(cad38c1394ef)", "(9ba3d8e3)");
let findings = check_trailers(&forged, &expected(NEWT));
assert!(
findings
.iter()
.any(|f| matches!(f, Finding::Forged(l) if l.starts_with("Harness: "))),
"{findings:?}"
);
}
#[test]
fn without_a_ledger_newt_lines_are_unvouched() {
let line = format!("Co-authored-by: m (v0.8.0 abc) <{NEWT}>");
let findings = check_trailers(&format!("x\n\n{line}\n"), &Expected::from_policy(None));
assert_eq!(findings, vec![Finding::Unvouched(line)]);
}
struct FakeRepo(Vec<(String, String)>);
impl CommitReader for FakeRepo {
fn head(&self) -> std::result::Result<Option<String>, HeadError> {
Ok(self.0.first().map(|(oid, _)| oid.clone()))
}
fn landed(&self, before: Option<&str>, _: &str, limit: usize) -> Vec<(String, String)> {
self.0
.iter()
.take_while(|(oid, _)| Some(oid.as_str()) != before)
.take(limit)
.cloned()
.collect()
}
fn stat(&self, oid: &str) -> String {
format!(" {oid}.rs | 1 +")
}
}
#[test]
fn review_landed_reports_only_new_bad_commits() {
let good = ledger(NEWT).finalize_message("good\n");
let bad = format!("bad\n\nCo-authored-by: m (v0.8.0 9ba3d8e3) <{NEWT}>\n");
let repo = FakeRepo(vec![
("oid-three".into(), bad),
("oid-two".into(), good),
(
"oid-one".into(),
"old\n\nCo-authored-by: x (v0) <".to_string() + NEWT + ">\n",
),
]);
let report = review_landed(&repo, Some("oid-one"), &expected(NEWT)).expect("flagged");
assert!(report.contains("commit review: oid-three"), "{report}");
assert!(
!report.contains("oid-two") && !report.contains("oid-one"),
"{report}"
);
assert!(
report.contains("9ba3d8e3") && report.contains("/byline"),
"{report}"
);
}
#[test]
fn review_landed_is_silent_when_head_did_not_move_or_is_canonical() {
let repo = FakeRepo(vec![("c1".into(), ledger(NEWT).finalize_message("x\n"))]);
assert_eq!(review_landed(&repo, Some("c1"), &expected(NEWT)), None);
assert_eq!(review_landed(&repo, None, &expected(NEWT)), None);
assert_eq!(
review_landed(&FakeRepo(vec![]), None, &expected(NEWT)),
None
);
}
#[test]
fn review_landed_is_bounded() {
let bad = format!("b\n\nCo-authored-by: m (v0 x) <{NEWT}>\n");
let repo = FakeRepo((0..40).map(|i| (format!("c{i:02}"), bad.clone())).collect());
let report = review_landed(&repo, None, &expected(NEWT)).unwrap();
assert_eq!(
report.matches("commit review:").count(),
MAX_REVIEWED_COMMITS
);
}
#[test]
fn pending_review_captures_before_and_flags_what_landed() {
let policy = LedgerPolicy(ledger(NEWT));
let before = FakeRepo(vec![("oid-base".into(), "base\n".into())]);
let pending = Pending::start(&before, Some(&policy));
let after = FakeRepo(vec![
(
"oid-new".into(),
format!("x\n\nCo-authored-by: m (v0.8.0 9ba3d8e3) <{NEWT}>\n"),
),
("oid-base".into(), "base\n".into()),
]);
assert!(pending.finish(&after).0.unwrap().contains("oid-new"));
}
#[test]
fn chained_commit_without_consumed_contributors_passes() {
let mut with_history = ledger(NEWT);
with_history.contributors = vec![crate::attribution::Attribution::new(
"older",
"newt-agent",
"0.7.9",
NEWT,
)];
let second = ledger(NEWT).finalize_message("second\n");
let expected = Expected::from_policy(Some(&LedgerPolicy(with_history)));
assert_eq!(check_trailers(&second, &expected), Vec::new());
}
#[test]
fn chained_commit_with_consumed_foreign_contributor_passes() {
let mut with_history = ledger(NEWT);
with_history.contributors = vec![
crate::attribution::Attribution::new(
"previous-model",
"Claude Code",
"2.1",
"noreply@anthropic.com",
),
crate::attribution::Attribution::new(
"codex",
"Codex CLI",
"0.47.0",
"codex@openai.com",
),
];
let expected = Expected::from_policy(Some(&LedgerPolicy(with_history.clone())));
let second = with_history.finalize_message_with("second commit", &[]);
assert_eq!(check_trailers(&second, &expected), Vec::new());
let no_operator = second.replace("Co-authored-by: Op Erator <op@example.com>\n", "");
assert_eq!(
check_trailers(&no_operator, &expected),
vec![Finding::Missing(
"Co-authored-by: Op Erator <op@example.com>".into()
)]
);
}
#[test]
fn unborn_start_walks_history_from_the_root_under_the_bound() {
assert_eq!(
rev_list_args(None, "h", MAX_REVIEWED_COMMITS),
vec!["rev-list", "--max-count=16", "h"]
);
assert_eq!(
rev_list_args(Some("b"), "h", 4),
vec!["rev-list", "--max-count=4", "b..h"]
);
}
#[test]
fn leak_scan_flags_local_paths_and_session_links() {
let msg = "fix\n\nsee /home/alice/src/x.rs and https://claude.ai/code/session_abc\n\
Claude-Session: https://claude.ai/code/session_abc\n";
let found = leak_findings(msg);
assert!(found.iter().any(|f| f.contains("/home/alice")), "{found:?}");
assert!(
found.iter().any(|f| f.contains("session link")),
"{found:?}"
);
assert!(leak_findings("fix /Users/bob/x")
.iter()
.any(|f| f.contains("/Users/bob")));
}
#[test]
fn leak_scan_passes_repo_relative_text() {
assert_eq!(
leak_findings("fix newt-core/src/x.rs; /home/ alone; Co-authored-by: a <b>"),
Vec::<String>::new()
);
}
#[test]
fn content_prompt_carries_message_and_stat_and_is_capped() {
let p = content_prompt("fix the parser", " src/a.rs | 2 +-");
assert!(
p.contains("fix the parser") && p.contains("src/a.rs | 2"),
"{p}"
);
assert!(p.contains("\"findings\""), "asks for the JSON shape: {p}");
let huge = "x".repeat(100_000);
assert!(content_prompt(&huge, &huge).len() < 3 * CONTENT_INPUT_CAP);
}
#[test]
fn parse_content_findings_accepts_json_and_drops_garbage() {
let raw = r#"noise {"findings":[{"kind":"overclaim","detail":"says tests added; stat shows none"}]} tail"#;
assert_eq!(
parse_content_findings(raw),
vec!["overclaim: says tests added; stat shows none".to_string()]
);
assert_eq!(
parse_content_findings(r#"{"findings":[]}"#),
Vec::<String>::new()
);
assert_eq!(
parse_content_findings("looks fine to me"),
Vec::<String>::new()
);
assert_eq!(
parse_content_findings(r#"{"findings":[{"kind":"bogus","detail":"x"}]}"#),
Vec::<String>::new()
);
}
#[test]
fn review_landed_reports_leaks_without_a_byline_hint() {
let repo = FakeRepo(vec![("oid-new".into(), "fix /home/alice/x\n".into())]);
let report = review_landed(&repo, None, &expected(NEWT)).expect("leak flagged");
assert!(
report.contains("oid-new message content") && report.contains("/home/alice/x"),
"{report}"
);
assert!(!report.contains("/byline"), "{report}");
}
#[test]
fn content_prompts_cover_landed_commits_bounded() {
let before = FakeRepo(vec![("oid-base".into(), "b\n".into())]);
let pending = Pending::start(&before, None);
let mut history: Vec<(String, String)> = (0..10)
.map(|i| (format!("oid-{i}"), format!("msg {i}\n")))
.collect();
history.push(("oid-base".into(), "b\n".into()));
let prompts = pending.finish(&FakeRepo(history)).1;
assert_eq!(prompts.len(), MAX_CONTENT_REVIEWS);
assert_eq!(prompts[0].0, "oid-0");
assert!(
prompts[0].1.contains("msg 0") && prompts[0].1.contains("oid-0.rs | 1 +"),
"{}",
prompts[0].1
);
assert!(
pending.finish(&before) == (None, Vec::new()),
"HEAD did not move"
);
}
struct HangingRepo;
impl CommitReader for HangingRepo {
fn head(&self) -> std::result::Result<Option<String>, HeadError> {
std::thread::sleep(std::time::Duration::from_secs(1));
Ok(None)
}
fn landed(&self, _: Option<&str>, _: &str, _: usize) -> Vec<(String, String)> {
std::thread::sleep(std::time::Duration::from_secs(1));
Vec::new()
}
fn stat(&self, _: &str) -> String {
std::thread::sleep(std::time::Duration::from_secs(1));
String::new()
}
}
#[tokio::test]
async fn a_hanging_snapshot_never_hangs_the_turn() {
let short = std::time::Duration::from_millis(50);
let reader: SharedReader = std::sync::Arc::new(HangingRepo);
let begun = tokio::time::timeout(
std::time::Duration::from_secs(5),
Review::begin_within(reader, None, short),
)
.await
.expect("begin returned within its deadline");
assert!(begun.is_none(), "an unknown `before` reviews nothing");
}
#[tokio::test]
async fn a_failed_head_read_skips_review() {
struct ErrorThenOk(std::sync::atomic::AtomicBool);
impl CommitReader for ErrorThenOk {
fn head(&self) -> std::result::Result<Option<String>, HeadError> {
if self.0.swap(true, std::sync::atomic::Ordering::SeqCst) {
Ok(Some("oid-head".into()))
} else {
Err(HeadError::ReadFailed)
}
}
fn landed(&self, _: Option<&str>, _: &str, _: usize) -> Vec<(String, String)> {
vec![("oid-old".into(), "old\n".into())]
}
fn stat(&self, _: &str) -> String {
String::new()
}
}
let reader: SharedReader =
std::sync::Arc::new(ErrorThenOk(std::sync::atomic::AtomicBool::new(false)));
assert!(
Review::begin(reader, None).await.is_none(),
"a failed initial HEAD snapshot must skip review"
);
}
#[tokio::test]
async fn an_unborn_repository_still_reviews_landed_commits() {
struct EmptyThenFull(std::sync::Mutex<Vec<(String, String)>>);
impl CommitReader for EmptyThenFull {
fn head(&self) -> std::result::Result<Option<String>, HeadError> {
Ok(self.0.lock().unwrap().first().map(|(oid, _)| oid.clone()))
}
fn landed(&self, before: Option<&str>, _: &str, limit: usize) -> Vec<(String, String)> {
self.0
.lock()
.unwrap()
.iter()
.take_while(|(oid, _)| Some(oid.as_str()) != before)
.take(limit)
.cloned()
.collect()
}
fn stat(&self, oid: &str) -> String {
format!(" {oid}.rs | 1 +")
}
}
let repo = std::sync::Arc::new(EmptyThenFull(std::sync::Mutex::new(Vec::new())));
let reader: SharedReader = repo.clone();
let review = Review::begin(reader, None)
.await
.expect("unborn branch starts a review");
repo.0.lock().unwrap().push((
"oid-new".into(),
format!("x\n\nCo-authored-by: m (v0.8.0 9ba3d8e3) <{NEWT}>\n"),
));
let (report, _) = review.conclude().await;
assert!(
report.as_ref().is_some_and(|r| r.contains("oid-new")),
"unborn start should still review what landed: {report:?}"
);
}
struct HangAfterHead(std::sync::atomic::AtomicBool);
impl CommitReader for HangAfterHead {
fn head(&self) -> std::result::Result<Option<String>, HeadError> {
if self.0.swap(true, std::sync::atomic::Ordering::SeqCst) {
std::thread::sleep(std::time::Duration::from_secs(1));
}
Ok(Some("oid-base".into()))
}
fn landed(&self, _: Option<&str>, _: &str, _: usize) -> Vec<(String, String)> {
Vec::new()
}
fn stat(&self, _: &str) -> String {
String::new()
}
}
#[tokio::test]
async fn a_hanging_readback_concludes_empty() {
let short = std::time::Duration::from_millis(50);
let reader: SharedReader = std::sync::Arc::new(HangAfterHead(Default::default()));
let review = Review::begin_within(reader, None, short)
.await
.expect("snapshot");
let concluded = tokio::time::timeout(
std::time::Duration::from_secs(5),
review.conclude_within(short),
)
.await
.expect("conclude returned within its deadline");
assert_eq!(concluded, (None, Vec::new()));
}
#[test]
fn bounded_read_rejects_oversized_and_stalled_output() {
let long = std::time::Duration::from_secs(5);
assert_eq!(
read_bounded(std::io::Cursor::new(b"abc".to_vec()), 3, long)
.recv_timeout(long)
.ok()
.flatten(),
Some(b"abc".to_vec())
);
assert_eq!(
read_bounded(std::io::Cursor::new(b"abcd".to_vec()), 3, long)
.recv_timeout(long)
.ok()
.flatten(),
None
);
struct Stalled;
impl std::io::Read for Stalled {
fn read(&mut self, _: &mut [u8]) -> std::io::Result<usize> {
std::thread::sleep(std::time::Duration::from_secs(1));
Ok(0)
}
}
let started = std::time::Instant::now();
assert_eq!(
read_bounded(Stalled, 3, std::time::Duration::from_millis(50))
.recv_timeout(std::time::Duration::from_millis(100))
.ok()
.flatten(),
None
);
assert!(started.elapsed() < long);
}
#[test]
fn fatal_git_exit_is_a_read_failure_not_unborn() {
assert_eq!(
classify(
&["rev-parse", "--verify", "-q", "HEAD"],
Some(1),
vec![],
vec![]
),
Ok(None)
);
assert_eq!(
classify(
&["rev-parse", "--verify", "-q", "HEAD"],
Some(128),
vec![],
b"fatal: not a git repository".to_vec()
),
Err(HeadError::ReadFailed)
);
assert_eq!(
classify(
&["rev-parse", "--verify", "-q", "HEAD"],
Some(1),
vec![],
b"error: bogus".to_vec()
),
Err(HeadError::ReadFailed)
);
}
#[test]
fn host_repo_requires_unrestricted_read_authority() {
let repo = HostRepo {
cwd: std::path::PathBuf::from("repo/child"),
read_scope: crate::Scope::only(["repo/child".to_string()]),
};
assert_eq!(repo.head(), Err(HeadError::ReadFailed));
assert!(repo.landed(None, "h", 4).is_empty());
assert!(repo.stat("abc").is_empty());
}
#[test]
fn oversized_collector_is_read_failure_not_unborn() {
let oversized = vec![b'x'; GIT_OUTPUT_CAP + 1];
let rx = read_bounded(
std::io::Cursor::new(oversized),
GIT_OUTPUT_CAP,
GIT_READ_DEADLINE,
);
assert_eq!(
collect_output(Some(rx), GIT_READ_DEADLINE),
Err(HeadError::ReadFailed)
);
}
#[test]
fn disconnected_collector_is_read_failure() {
let (tx, rx) = std::sync::mpsc::channel::<Option<Vec<u8>>>();
drop(tx);
assert_eq!(
collect_output(Some(rx), GIT_READ_DEADLINE),
Err(HeadError::ReadFailed)
);
}
#[test]
fn render_content_is_flag_only_and_silent_when_clean() {
assert_eq!(render_content("oid-x", &[]), None);
let r = render_content("oid-x", &["overclaim: no tests in stat".into()]).unwrap();
assert!(
r.contains("commit review: oid-x content (model, advisory)") && r.contains("overclaim"),
"{r}"
);
}
}