use async_trait::async_trait;
use std::cell::RefCell;
use std::collections::{BTreeMap, BTreeSet};
use std::path::{Path, PathBuf};
use crate::symbols::{extract_references, module_is_known, python_stdlib_modules, Lang};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Fabrication {
pub module: String,
pub line: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FileVerdict {
pub path: PathBuf,
pub fabrications: Vec<Fabrication>,
}
impl FileVerdict {
#[must_use]
pub fn is_clean(&self) -> bool {
self.fabrications.is_empty()
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct GateReport {
pub files: Vec<FileVerdict>,
}
impl GateReport {
#[must_use]
pub fn revert_set(&self) -> Vec<&Path> {
self.files
.iter()
.filter(|f| !f.is_clean())
.map(|f| f.path.as_path())
.collect()
}
#[must_use]
pub fn accept(&self) -> bool {
self.files.iter().all(FileVerdict::is_clean)
}
#[must_use]
pub fn fabrication_count(&self) -> usize {
self.files.iter().map(|f| f.fabrications.len()).sum()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SurfaceMatch {
#[default]
Exact,
Prefix,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum VerifyTier {
Off,
Advisory,
RevertOnce,
#[default]
RevertRetry,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VerifyAction {
Pass,
Warn,
Revert,
RevertAndRetry,
}
impl VerifyTier {
#[must_use]
pub fn action(self, report: &GateReport) -> VerifyAction {
if report.accept() {
return VerifyAction::Pass; }
match self {
Self::Off => VerifyAction::Pass,
Self::Advisory => VerifyAction::Warn,
Self::RevertOnce => VerifyAction::Revert,
Self::RevertRetry => VerifyAction::RevertAndRetry,
}
}
}
#[must_use]
pub fn turn_verdict_banner(reverted: &[String], gave_up: bool, cap_hit: bool) -> Option<String> {
if reverted.is_empty() && !cap_hit {
return None;
}
let mut parts = Vec::new();
if !reverted.is_empty() {
parts.push(format!(
"the verify gate reverted {} file(s) [{}]{}",
reverted.len(),
reverted.join(", "),
if gave_up {
" after exhausting retries"
} else {
""
}
));
}
if cap_hit {
parts.push("the tool-round cap was reached".to_string());
}
Some(format!(
"needs human review: {} — the turn did not finish cleanly.",
parts.join("; and ")
))
}
fn module_resolves(
module: &str,
surface: &BTreeSet<String>,
stdlib: &BTreeSet<String>,
mode: SurfaceMatch,
) -> bool {
let in_surface = match mode {
SurfaceMatch::Exact => surface.contains(module),
SurfaceMatch::Prefix => module_is_known(module, surface),
};
in_surface || module_is_known(module, stdlib)
}
#[must_use]
pub fn gate_python_source(
path: impl Into<PathBuf>,
source: &str,
surface: &BTreeSet<String>,
) -> FileVerdict {
gate_python_source_with(path, source, surface, SurfaceMatch::default())
}
#[must_use]
pub fn gate_python_source_with(
path: impl Into<PathBuf>,
source: &str,
surface: &BTreeSet<String>,
mode: SurfaceMatch,
) -> FileVerdict {
gate_inner(path, source, surface, &python_stdlib_modules(), mode)
}
fn gate_inner(
path: impl Into<PathBuf>,
source: &str,
surface: &BTreeSet<String>,
stdlib: &BTreeSet<String>,
mode: SurfaceMatch,
) -> FileVerdict {
let mut fabrications: Vec<Fabrication> = extract_references(source, Lang::Python)
.into_iter()
.filter(|r| !module_resolves(&r.module, surface, stdlib, mode))
.map(|r| Fabrication {
module: r.module,
line: r.line,
})
.collect();
fabrications.dedup_by(|a, b| a.module == b.module && a.line == b.line);
FileVerdict {
path: path.into(),
fabrications,
}
}
pub fn gate_python_workspace(
workspace: &Path,
surface: &BTreeSet<String>,
) -> std::io::Result<GateReport> {
gate_python_workspace_with(workspace, surface, SurfaceMatch::default())
}
pub fn gate_python_workspace_with(
workspace: &Path,
surface: &BTreeSet<String>,
mode: SurfaceMatch,
) -> std::io::Result<GateReport> {
let stdlib = python_stdlib_modules();
let mut py_files = Vec::new();
collect_py_files(workspace, &mut py_files)?;
py_files.sort();
let mut files = Vec::new();
for abs in py_files {
let source = std::fs::read_to_string(&abs)?;
let rel = abs.strip_prefix(workspace).unwrap_or(&abs).to_path_buf();
files.push(gate_inner(rel, &source, surface, &stdlib, mode));
}
Ok(GateReport { files })
}
const SKIP_DIRS: &[&str] = &[
".git",
".venv",
"venv",
"site-packages",
"node_modules",
"target",
"__pycache__",
".tox",
".mypy_cache",
];
fn collect_py_files(dir: &Path, out: &mut Vec<PathBuf>) -> std::io::Result<()> {
for entry in std::fs::read_dir(dir)? {
let entry = entry?;
let ft = entry.file_type()?;
if ft.is_symlink() {
continue;
}
let path = entry.path();
if ft.is_dir() {
let skip = path
.file_name()
.and_then(|n| n.to_str())
.is_some_and(|n| SKIP_DIRS.contains(&n));
if !skip {
collect_py_files(&path, out)?;
}
} else if path.extension().is_some_and(|e| e == "py") {
out.push(path);
}
}
Ok(())
}
#[derive(Debug, Default, Clone)]
pub struct WriteLedger {
entries: BTreeMap<PathBuf, Option<Vec<u8>>>,
}
impl WriteLedger {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn note_before_write(&mut self, path: impl Into<PathBuf>) {
let path = path.into();
if self.entries.contains_key(&path) {
return;
}
let prior = match std::fs::read(&path) {
Ok(bytes) => Some(bytes),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => None,
Err(_) => return, };
self.entries.insert(path, prior);
}
pub fn revert(&self, path: &Path) -> std::io::Result<bool> {
match self.entries.get(path) {
None => Ok(false),
Some(None) => match std::fs::remove_file(path) {
Ok(()) => Ok(true),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(true),
Err(e) => Err(e),
},
Some(Some(bytes)) => {
std::fs::write(path, bytes)?;
Ok(true)
}
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
#[must_use]
pub fn len(&self) -> usize {
self.entries.len()
}
}
#[async_trait(?Send)]
pub trait RetryRerun {
async fn rerun(&mut self, corrective_prompt: String) -> anyhow::Result<()>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RetryOutcome {
pub accepted: bool,
pub retries_used: u32,
pub reverted: Vec<PathBuf>,
pub outstanding_modules: Vec<String>,
}
impl RetryOutcome {
fn accepted(retries_used: u32) -> Self {
Self {
accepted: true,
retries_used,
reverted: Vec::new(),
outstanding_modules: Vec::new(),
}
}
}
fn outstanding_modules(report: &GateReport) -> Vec<String> {
report
.files
.iter()
.flat_map(|f| f.fabrications.iter().map(|x| x.module.clone()))
.collect::<BTreeSet<_>>()
.into_iter()
.collect()
}
#[must_use]
pub fn corrective_prompt(report: &GateReport, surface_block: &str) -> String {
let mut out = String::new();
out.push_str(
"Your previous attempt imported modules this project does not expose, so \
those files were reverted. Fix and rewrite them.\n\n",
);
for f in report.files.iter().filter(|f| !f.is_clean()) {
for fab in &f.fabrications {
out.push_str(&format!(
"- `{}` imported `{}` (line {}), which does not exist.\n",
f.path.display(),
fab.module,
fab.line
));
}
}
if !surface_block.is_empty() {
out.push_str("\nThe authoritative import surface is:\n\n");
out.push_str(surface_block);
out.push('\n');
}
out.push_str(
"\nRewrite the reverted file(s) using only modules from that surface. Do \
not import the modules listed above.",
);
out
}
pub struct RetrySurface<'a> {
pub modules: &'a BTreeSet<String>,
pub mode: SurfaceMatch,
pub block: &'a str,
}
fn path_within(abs: &Path, ws_canon: Option<&Path>) -> bool {
let Some(root) = ws_canon else {
return true;
};
if let Ok(c) = abs.canonicalize() {
return c.starts_with(root);
}
match (abs.parent(), abs.file_name()) {
(Some(parent), Some(name)) => match parent.canonicalize() {
Ok(pc) => pc.join(name).starts_with(root),
Err(_) => false,
},
_ => false,
}
}
pub async fn apply_revert_retry(
workspace: &Path,
surface: &RetrySurface<'_>,
ledger: &RefCell<WriteLedger>,
initial: GateReport,
max_retries: u32,
rerun: &mut dyn RetryRerun,
) -> anyhow::Result<RetryOutcome> {
let ws_canon = workspace.canonicalize().ok();
let mut report = initial;
let mut retries_used = 0u32;
loop {
if report.accept() {
return Ok(RetryOutcome::accepted(retries_used));
}
let mut reverted: Vec<PathBuf> = Vec::new();
{
let led = ledger.borrow();
for rel in report.revert_set() {
let abs = workspace.join(rel);
if !path_within(&abs, ws_canon.as_deref()) {
tracing::warn!(
path = %rel.display(),
"retry: refusing to revert a path outside the workspace"
);
continue;
}
match led.revert(&abs)? {
true => reverted.push(rel.to_path_buf()),
false => tracing::warn!(
path = %rel.display(),
"retry: gate-flagged path not in the write ledger — skipping (will not \
delete a file newt did not write)"
),
}
}
}
if retries_used >= max_retries {
return Ok(RetryOutcome {
accepted: false,
retries_used,
reverted,
outstanding_modules: outstanding_modules(&report),
});
}
let prompt = corrective_prompt(&report, surface.block);
rerun.rerun(prompt).await?;
retries_used += 1;
report = gate_python_workspace_with(workspace, surface.modules, surface.mode)?;
}
}
pub async fn revert_only(
workspace: &Path,
surface: &RetrySurface<'_>,
ledger: &RefCell<WriteLedger>,
report: GateReport,
) -> anyhow::Result<RetryOutcome> {
struct NoRerun;
#[async_trait(?Send)]
impl RetryRerun for NoRerun {
async fn rerun(&mut self, _prompt: String) -> anyhow::Result<()> {
Ok(())
}
}
apply_revert_retry(workspace, surface, ledger, report, 0, &mut NoRerun).await
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ffi_manifest::FfiManifest;
const NEWT_CORE_SRC: &str = r#"
#[pyclass(name = "Router", module = "newt_agent._newt_agent.core")]
pub struct PyRouter;
"#;
fn surface() -> BTreeSet<String> {
FfiManifest::from_sources([("newt-core", NEWT_CORE_SRC)]).known_modules()
}
#[test]
fn clean_file_has_no_fabrications() {
let v = gate_python_source(
"ok.py",
"from newt_agent._newt_agent.core import Router\nimport os\nimport os.path\n",
&surface(),
);
assert!(v.is_clean(), "fabrications: {:?}", v.fabrications);
}
#[test]
fn fabricated_import_is_flagged_with_line() {
let v = gate_python_source("bad.py", "import os\nimport newt_core\n", &surface());
assert_eq!(v.fabrications.len(), 1);
assert_eq!(v.fabrications[0].module, "newt_core");
assert_eq!(v.fabrications[0].line, 2); }
#[test]
fn report_revert_set_is_only_fabricating_files() {
let s = surface();
let report = GateReport {
files: vec![
gate_python_source(
"grounded.py",
"from newt_agent._newt_agent.core import Router\n",
&s,
),
gate_python_source("fab.py", "import newt_core\n", &s),
],
};
assert!(!report.accept());
assert_eq!(report.fabrication_count(), 1);
let revert = report.revert_set();
assert_eq!(revert.len(), 1);
assert_eq!(revert[0], Path::new("fab.py"));
}
#[test]
fn relative_imports_are_not_fabrications() {
let v = gate_python_source(
"pkg.py",
"from . import config\nfrom .helpers import load\nfrom ..util import x\n",
&surface(),
);
assert!(
v.is_clean(),
"relative imports flagged: {:?}",
v.fabrications
);
}
#[test]
fn future_import_is_not_a_fabrication() {
let v = gate_python_source(
"typed.py",
"from __future__ import annotations\nimport os\n",
&surface(),
);
assert!(v.is_clean(), "__future__ flagged: {:?}", v.fabrications);
}
#[test]
fn realistic_clean_file_is_accepted() {
let v = gate_python_source(
"real.py",
"from __future__ import annotations\n\
from . import config\n\
from .helpers import load\n\
import json\n\
from newt_agent._newt_agent.core import Router\n",
&surface(),
);
assert!(v.is_clean(), "clean file reverted: {:?}", v.fabrications);
}
#[test]
fn prefix_breadth_evasion_caught_in_exact_caught_lax_in_prefix() {
let src = "from newt_agent._newt_core import pyo3_module\n";
let exact = gate_python_source( "e.py", src, &surface());
assert_eq!(exact.fabrications.len(), 1, "Exact must catch it");
assert_eq!(exact.fabrications[0].module, "newt_agent._newt_core");
let lax = gate_python_source_with("p.py", src, &surface(), SurfaceMatch::Prefix);
assert!(lax.is_clean(), "Prefix is the documented lax knob");
}
#[test]
fn hyphen_fabrication_is_caught() {
let v = gate_python_source("h.py", "from newt-eval import pyo3_module\n", &surface());
assert_eq!(v.fabrications.len(), 1, "got: {:?}", v.fabrications);
assert_eq!(v.fabrications[0].module, "newt-eval");
}
#[test]
fn wildcard_fabrication_is_caught() {
let v = gate_python_source("w.py", "from newt_data.pyo3_module import *\n", &surface());
assert_eq!(v.fabrications.len(), 1, "got: {:?}", v.fabrications);
assert_eq!(v.fabrications[0].module, "newt_data.pyo3_module");
}
#[test]
fn grounded_wildcard_is_clean() {
let v = gate_python_source(
"gw.py",
"from newt_agent._newt_agent.core import *\n",
&surface(),
);
assert!(
v.is_clean(),
"grounded wildcard flagged: {:?}",
v.fabrications
);
}
#[test]
fn multiline_paren_fabricated_module_is_caught() {
let v = gate_python_source(
"evade.py",
"from newt_db import (\n Alpha,\n Beta,\n)\n",
&surface(),
);
assert_eq!(v.fabrications.len(), 1, "got: {:?}", v.fabrications);
assert_eq!(v.fabrications[0].module, "newt_db");
}
#[test]
fn one_fabricated_module_many_symbols_counts_once() {
let v = gate_python_source(
"multi.py",
"from newt_db import (Alpha, Beta, Gamma)\n",
&surface(),
);
assert_eq!(v.fabrications.len(), 1, "got: {:?}", v.fabrications);
}
#[test]
fn gate_workspace_walks_and_is_relative() {
let tmp = tempfile::tempdir().unwrap();
std::fs::create_dir_all(tmp.path().join("examples")).unwrap();
std::fs::write(
tmp.path().join("examples/grounded.py"),
"from newt_agent._newt_agent.core import Router\nimport json\n",
)
.unwrap();
std::fs::write(tmp.path().join("examples/fab.py"), "import newt_coder\n").unwrap();
let report = gate_python_workspace(tmp.path(), &surface()).unwrap();
assert_eq!(report.files.len(), 2);
assert!(!report.accept());
assert_eq!(report.revert_set(), vec![Path::new("examples/fab.py")]);
}
#[test]
fn ledger_restores_an_edited_file() {
let tmp = tempfile::tempdir().unwrap();
let f = tmp.path().join("edit.py");
std::fs::write(&f, "original\n").unwrap();
let mut led = WriteLedger::new();
led.note_before_write(&f); std::fs::write(&f, "fabricated\n").unwrap();
assert!(led.revert(&f).unwrap());
assert_eq!(std::fs::read_to_string(&f).unwrap(), "original\n");
}
#[test]
fn ledger_deletes_a_newly_created_file() {
let tmp = tempfile::tempdir().unwrap();
let f = tmp.path().join("new.py");
let mut led = WriteLedger::new();
led.note_before_write(&f); std::fs::write(&f, "import newt_core\n").unwrap();
assert!(led.revert(&f).unwrap());
assert!(!f.exists(), "a file that did not exist pre-turn is removed");
}
#[test]
fn ledger_first_write_wins_across_a_turn() {
let tmp = tempfile::tempdir().unwrap();
let f = tmp.path().join("multi.py");
std::fs::write(&f, "pre-turn\n").unwrap();
let mut led = WriteLedger::new();
led.note_before_write(&f); std::fs::write(&f, "intermediate\n").unwrap();
led.note_before_write(&f); std::fs::write(&f, "final\n").unwrap();
led.revert(&f).unwrap();
assert_eq!(
std::fs::read_to_string(&f).unwrap(),
"pre-turn\n",
"revert restores the pre-turn state, not an intermediate write"
);
assert_eq!(led.len(), 1);
}
#[test]
fn ledger_revert_reports_untracked_paths() {
let tmp = tempfile::tempdir().unwrap();
let f = tmp.path().join("untracked.py");
std::fs::write(&f, "x\n").unwrap();
let led = WriteLedger::new();
assert!(!led.revert(&f).unwrap(), "no entry → false, no delete");
assert!(f.exists(), "an untracked file is never silently removed");
}
#[test]
fn note_before_write_leaves_an_unreadable_path_untracked() {
let tmp = tempfile::tempdir().unwrap();
let p = tmp.path().join("not_a_file");
std::fs::create_dir(&p).unwrap();
let mut led = WriteLedger::new();
led.note_before_write(&p);
assert_eq!(led.len(), 0, "an unreadable path is not recorded");
assert!(!led.revert(&p).unwrap(), "untracked ⇒ revert is a no-op");
assert!(
p.exists(),
"revert must never remove an unreadable pre-existing path"
);
}
#[test]
fn corrective_prompt_names_files_modules_and_surface() {
let report = GateReport {
files: vec![FileVerdict {
path: "examples/bad.py".into(),
fabrications: vec![Fabrication {
module: "newt_core".into(),
line: 3,
}],
}],
};
let p = corrective_prompt(&report, "SURFACE-BLOCK-HERE");
assert!(p.contains("examples/bad.py"));
assert!(p.contains("newt_core"));
assert!(p.contains("line 3"));
assert!(p.contains("SURFACE-BLOCK-HERE"));
assert!(p.contains("Do not import"));
}
struct ScriptedRerun<'a> {
workspace: PathBuf,
file: PathBuf,
ledger: &'a RefCell<WriteLedger>,
contents: std::collections::VecDeque<String>,
calls: usize,
}
#[async_trait(?Send)]
impl RetryRerun for ScriptedRerun<'_> {
async fn rerun(&mut self, _prompt: String) -> anyhow::Result<()> {
self.calls += 1;
let content = self.contents.pop_front().unwrap_or_default();
let abs = self.workspace.join(&self.file);
self.ledger.borrow_mut().note_before_write(&abs);
std::fs::write(&abs, content)?;
Ok(())
}
}
fn seed_fabricating_turn(ws: &Path, file: &str, ledger: &RefCell<WriteLedger>) -> GateReport {
let abs = ws.join(file);
ledger.borrow_mut().note_before_write(&abs);
std::fs::write(&abs, "import newt_core\n").unwrap();
gate_python_workspace(ws, &surface()).unwrap()
}
#[tokio::test]
async fn retry_accepts_when_a_reattempt_grounds_the_file() {
let tmp = tempfile::tempdir().unwrap();
let ledger = RefCell::new(WriteLedger::new());
let initial = seed_fabricating_turn(tmp.path(), "bad.py", &ledger);
assert!(!initial.accept());
let mut rerun = ScriptedRerun {
workspace: tmp.path().to_path_buf(),
file: "bad.py".into(),
ledger: &ledger,
contents: ["from newt_agent._newt_agent.core import Router\n".to_string()].into(),
calls: 0,
};
let surf = surface();
let outcome = apply_revert_retry(
tmp.path(),
&RetrySurface {
modules: &surf,
mode: SurfaceMatch::Exact,
block: "SURFACE",
},
&ledger,
initial,
2,
&mut rerun,
)
.await
.unwrap();
assert!(outcome.accepted);
assert_eq!(outcome.retries_used, 1);
assert_eq!(rerun.calls, 1);
assert!(outcome.reverted.is_empty());
assert_eq!(
std::fs::read_to_string(tmp.path().join("bad.py")).unwrap(),
"from newt_agent._newt_agent.core import Router\n"
);
}
#[tokio::test]
async fn retry_gives_up_honestly_at_the_cap() {
let tmp = tempfile::tempdir().unwrap();
let ledger = RefCell::new(WriteLedger::new());
let initial = seed_fabricating_turn(tmp.path(), "bad.py", &ledger);
let mut rerun = ScriptedRerun {
workspace: tmp.path().to_path_buf(),
file: "bad.py".into(),
ledger: &ledger,
contents: ["import newt_core\n".into(), "import newt_core\n".into()].into(),
calls: 0,
};
let surf = surface();
let outcome = apply_revert_retry(
tmp.path(),
&RetrySurface {
modules: &surf,
mode: SurfaceMatch::Exact,
block: "SURFACE",
},
&ledger,
initial,
2,
&mut rerun,
)
.await
.unwrap();
assert!(!outcome.accepted);
assert_eq!(
outcome.retries_used, 2,
"ran exactly 1 + max_retries calls' worth"
);
assert_eq!(rerun.calls, 2);
assert_eq!(outcome.reverted, vec![PathBuf::from("bad.py")]);
assert_eq!(outcome.outstanding_modules, vec!["newt_core".to_string()]);
assert!(
!tmp.path().join("bad.py").exists(),
"give-up leaves the file reverted, not the last fabrication"
);
}
#[tokio::test]
async fn retry_is_a_no_op_when_the_initial_report_is_clean() {
let tmp = tempfile::tempdir().unwrap();
let ledger = RefCell::new(WriteLedger::new());
struct NeverRerun;
#[async_trait(?Send)]
impl RetryRerun for NeverRerun {
async fn rerun(&mut self, _p: String) -> anyhow::Result<()> {
panic!("rerun must not be called when the gate already accepts");
}
}
let clean = GateReport {
files: vec![FileVerdict {
path: "ok.py".into(),
fabrications: vec![],
}],
};
let surf = surface();
let outcome = apply_revert_retry(
tmp.path(),
&RetrySurface {
modules: &surf,
mode: SurfaceMatch::Exact,
block: "SURFACE",
},
&ledger,
clean,
2,
&mut NeverRerun,
)
.await
.unwrap();
assert!(outcome.accepted);
assert_eq!(outcome.retries_used, 0);
}
#[tokio::test]
async fn revert_only_touches_only_ledgered_files() {
let tmp = tempfile::tempdir().unwrap();
let ledger = RefCell::new(WriteLedger::new());
let mine = tmp.path().join("mine.py");
ledger.borrow_mut().note_before_write(&mine);
std::fs::write(&mine, "import newt_core\n").unwrap();
let theirs = tmp.path().join("theirs.py");
std::fs::write(&theirs, "import newt_core\n").unwrap();
let surf = surface();
let report = gate_python_workspace(tmp.path(), &surf).unwrap();
assert_eq!(report.revert_set().len(), 2, "both files fabricate");
let outcome = revert_only(
tmp.path(),
&RetrySurface {
modules: &surf,
mode: SurfaceMatch::Exact,
block: "",
},
&ledger,
report,
)
.await
.unwrap();
assert_eq!(outcome.reverted, vec![PathBuf::from("mine.py")]);
assert!(!mine.exists(), "newt's created fabrication is removed");
assert_eq!(
std::fs::read_to_string(&theirs).unwrap(),
"import newt_core\n",
"a file newt did not write is never touched"
);
}
#[cfg(unix)]
#[test]
fn gate_does_not_follow_symlinked_dirs() {
let outside = tempfile::tempdir().unwrap();
std::fs::write(outside.path().join("external.py"), "import newt_core\n").unwrap();
let ws = tempfile::tempdir().unwrap();
std::fs::write(ws.path().join("own.py"), "import newt_core\n").unwrap();
std::os::unix::fs::symlink(outside.path(), ws.path().join("linked")).unwrap();
let report = gate_python_workspace(ws.path(), &surface()).unwrap();
let paths: Vec<_> = report.files.iter().map(|f| f.path.clone()).collect();
assert_eq!(
paths,
vec![PathBuf::from("own.py")],
"only the workspace's own .py is gated; the symlinked external file is skipped"
);
}
}
#[cfg(test)]
mod tier_tests {
use super::*;
fn dirty() -> GateReport {
GateReport {
files: vec![FileVerdict {
path: PathBuf::from("fab.py"),
fabrications: vec![Fabrication {
module: "newt_core".to_string(),
line: 1,
}],
}],
}
}
#[test]
fn clean_report_passes_every_tier() {
let clean = GateReport::default();
for t in [
VerifyTier::Off,
VerifyTier::Advisory,
VerifyTier::RevertOnce,
VerifyTier::RevertRetry,
] {
assert_eq!(t.action(&clean), VerifyAction::Pass);
}
}
#[test]
fn dirty_report_maps_per_tier() {
let d = dirty();
assert_eq!(VerifyTier::Off.action(&d), VerifyAction::Pass);
assert_eq!(VerifyTier::Advisory.action(&d), VerifyAction::Warn);
assert_eq!(VerifyTier::RevertOnce.action(&d), VerifyAction::Revert);
assert_eq!(
VerifyTier::RevertRetry.action(&d),
VerifyAction::RevertAndRetry
);
}
#[test]
fn default_tier_is_revert_retry() {
assert_eq!(VerifyTier::default(), VerifyTier::RevertRetry);
let t: VerifyTier = serde_json::from_str("\"advisory\"").unwrap();
assert_eq!(t, VerifyTier::Advisory);
}
#[test]
fn banner_is_none_on_clean_finish() {
assert!(turn_verdict_banner(&[], false, false).is_none());
}
#[test]
fn banner_names_reverts_only() {
let b = turn_verdict_banner(&["a.py".to_string()], false, false).unwrap();
assert!(b.contains("reverted 1 file") && b.contains("a.py"));
assert!(!b.contains("tool-round cap"));
assert!(b.contains("needs human review"));
}
#[test]
fn banner_names_cap_only() {
let b = turn_verdict_banner(&[], false, true).unwrap();
assert!(b.contains("tool-round cap"));
assert!(!b.contains("reverted"));
}
#[test]
fn banner_names_both_in_one_line() {
let b = turn_verdict_banner(&["a.py".to_string(), "b.py".to_string()], true, true).unwrap();
assert!(b.contains("reverted 2 file"));
assert!(b.contains("exhausting retries"));
assert!(b.contains("tool-round cap"));
assert_eq!(b.lines().count(), 1, "one honest line");
}
}