use super::contract::{baseline_gates_nothing, CheckResult};
use super::session::{ContractProvenance, NoChangeKind};
pub const MAX_FINDING_TEXT: usize = 4096;
#[derive(Debug, Default)]
pub struct MutationLedger {
mutated: std::sync::atomic::AtomicBool,
}
impl MutationLedger {
pub fn new() -> Self {
Self::default()
}
pub fn record_mutation(&self) {
self.mutated
.store(true, std::sync::atomic::Ordering::SeqCst);
}
pub fn has_mutated(&self) -> bool {
self.mutated.load(std::sync::atomic::Ordering::SeqCst)
}
}
pub fn worktree_fingerprint(dir: &std::path::Path) -> Option<String> {
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
for part in observation(dir)? {
hasher.update(&part);
hasher.update([0u8]);
}
Some(format!("{:x}", hasher.finalize()))
}
fn observation(dir: &std::path::Path) -> Option<Vec<Vec<u8>>> {
let read = |args: &[&str]| -> Option<Vec<u8>> {
let out = std::process::Command::new("git")
.current_dir(dir)
.args(args)
.output()
.ok()?;
out.status.success().then_some(out.stdout)
};
Some(vec![
read(&["rev-parse", "HEAD"])?,
read(&["status", "--porcelain", "-uall"])?,
read(&["diff", "HEAD"])?,
read(&["ls-files", "-v"])?,
])
}
fn has_hiding_index_flags(ls_files_v: &[u8]) -> bool {
String::from_utf8_lossy(ls_files_v).lines().any(|line| {
line.chars()
.next()
.is_some_and(|c| c == 'S' || c.is_ascii_lowercase())
})
}
pub fn worktree_is_pristine(dir: &std::path::Path, expected_head: &str) -> Option<bool> {
let parts = observation(dir)?;
let head = String::from_utf8_lossy(&parts[0]).trim().to_string();
Some(
head == expected_head.trim()
&& parts[1].is_empty()
&& parts[2].is_empty()
&& !has_hiding_index_flags(&parts[3]),
)
}
pub fn head_commit(dir: &std::path::Path) -> Option<String> {
let out = std::process::Command::new("git")
.current_dir(dir)
.args(["rev-parse", "HEAD"])
.output()
.ok()?;
out.status
.success()
.then(|| String::from_utf8_lossy(&out.stdout).trim().to_string())
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NominationRefusal {
UnusableText(&'static str),
WorktreeWasMutated,
WorktreeNotClean,
BaselineIncomplete,
}
impl NominationRefusal {
pub fn message(&self) -> String {
match self {
Self::UnusableText(which) => format!(
"report_no_change refused: `{which}` must be non-empty and under \
{MAX_FINDING_TEXT} characters. State the conclusion and what you \
examined to reach it."
),
Self::WorktreeWasMutated => {
"report_no_change refused: this session already made a successful edit. \
Reverting does not restore eligibility — a no-change finding is only \
available to a session that never changed anything. Continue toward a \
diff, or start a fresh session to investigate."
.to_string()
}
Self::WorktreeNotClean => {
"report_no_change refused: the worktree differs from the baseline. \
Restore it before concluding that nothing should change."
.to_string()
}
Self::BaselineIncomplete => {
"report_no_change refused: the baseline evaluation did not complete, so \
there is nothing to conclude from. This is not something you can fix — \
the session needs more time or a working check environment."
.to_string()
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NominationVerdict {
Autonomous,
NeedsHuman,
Refused(NominationRefusal),
}
pub fn baseline_completed(results: &[CheckResult]) -> bool {
!results.is_empty() && !results.iter().any(|r| r.timed_out && r.deadline_clamped)
}
#[derive(Debug, Clone, Copy)]
pub struct NominationContext<'a> {
pub kind: NoChangeKind,
pub summary: &'a str,
pub evidence: &'a str,
pub baseline: &'a [CheckResult],
pub provenance: ContractProvenance,
pub worktree_clean: bool,
pub mutated: bool,
}
pub fn evaluate_nomination(ctx: NominationContext<'_>) -> NominationVerdict {
use NominationRefusal::*;
if ctx.mutated {
return NominationVerdict::Refused(WorktreeWasMutated);
}
if !ctx.worktree_clean {
return NominationVerdict::Refused(WorktreeNotClean);
}
if !baseline_completed(ctx.baseline) {
return NominationVerdict::Refused(BaselineIncomplete);
}
if !usable(ctx.summary) {
return NominationVerdict::Refused(UnusableText("summary"));
}
if !usable(ctx.evidence) {
return NominationVerdict::Refused(UnusableText("evidence"));
}
let autonomous = matches!(ctx.kind, NoChangeKind::PremiseWrong)
&& baseline_gates_nothing(ctx.baseline)
&& ctx.provenance.is_trusted();
if autonomous {
NominationVerdict::Autonomous
} else {
NominationVerdict::NeedsHuman
}
}
fn usable(text: &str) -> bool {
let trimmed = text.trim();
!trimmed.is_empty() && trimmed.len() <= MAX_FINDING_TEXT
}
#[cfg(test)]
mod tests {
use super::*;
fn check(name: &str, passed: bool) -> CheckResult {
CheckResult {
name: name.to_string(),
passed,
exit_code: Some(if passed { 0 } else { 1 }),
duration_ms: 1,
output_tail: String::new(),
timed_out: false,
deadline_clamped: false,
}
}
fn ctx<'a>(
kind: NoChangeKind,
baseline: &'a [CheckResult],
provenance: ContractProvenance,
) -> NominationContext<'a> {
NominationContext {
kind,
summary: "the code already handles this",
evidence: "read handler.rs and ran the suite",
baseline,
provenance,
worktree_clean: true,
mutated: false,
}
}
#[test]
fn the_narrow_path_terminates_autonomously() {
let green = vec![check("a", true), check("b", true)];
assert_eq!(
evaluate_nomination(ctx(
NoChangeKind::PremiseWrong,
&green,
ContractProvenance::OperatorSupplied
)),
NominationVerdict::Autonomous
);
}
#[test]
fn a_model_authored_contract_never_self_approves() {
let green = vec![check("a", true)];
assert_eq!(
evaluate_nomination(ctx(
NoChangeKind::PremiseWrong,
&green,
ContractProvenance::ModelDerived
)),
NominationVerdict::NeedsHuman
);
}
#[test]
fn a_runtime_generated_reproduction_is_trusted() {
let green = vec![check("repro", true)];
assert_eq!(
evaluate_nomination(ctx(
NoChangeKind::PremiseWrong,
&green,
ContractProvenance::RuntimeGenerated
)),
NominationVerdict::Autonomous
);
}
#[test]
fn the_two_judgement_shapes_always_reach_a_human() {
let green = vec![check("a", true)];
for kind in [
NoChangeKind::DeliberateBehavior,
NoChangeKind::NonCodeDecision,
] {
assert_eq!(
evaluate_nomination(ctx(kind, &green, ContractProvenance::OperatorSupplied)),
NominationVerdict::NeedsHuman,
"{kind:?} is not runtime-verifiable"
);
}
}
#[test]
fn a_red_baseline_proves_nothing_and_parks() {
let mixed = vec![check("a", true), check("b", false)];
assert_eq!(
evaluate_nomination(ctx(
NoChangeKind::PremiseWrong,
&mixed,
ContractProvenance::OperatorSupplied
)),
NominationVerdict::NeedsHuman
);
}
#[test]
fn editing_then_reverting_does_not_restore_eligibility() {
let green = vec![check("a", true)];
let mut c = ctx(
NoChangeKind::PremiseWrong,
&green,
ContractProvenance::OperatorSupplied,
);
c.worktree_clean = true;
c.mutated = true;
assert_eq!(
evaluate_nomination(c),
NominationVerdict::Refused(NominationRefusal::WorktreeWasMutated)
);
}
#[test]
fn a_dirty_worktree_is_refused() {
let green = vec![check("a", true)];
let mut c = ctx(
NoChangeKind::PremiseWrong,
&green,
ContractProvenance::OperatorSupplied,
);
c.worktree_clean = false;
assert_eq!(
evaluate_nomination(c),
NominationVerdict::Refused(NominationRefusal::WorktreeNotClean)
);
}
#[test]
fn a_starved_baseline_is_not_evidence() {
let mut killed = check("a", false);
killed.timed_out = true;
killed.deadline_clamped = true;
let results = vec![killed];
assert_eq!(
evaluate_nomination(ctx(
NoChangeKind::PremiseWrong,
&results,
ContractProvenance::OperatorSupplied
)),
NominationVerdict::Refused(NominationRefusal::BaselineIncomplete)
);
}
#[test]
fn an_empty_baseline_is_not_a_clean_one() {
assert!(!baseline_completed(&[]));
assert_eq!(
evaluate_nomination(ctx(
NoChangeKind::PremiseWrong,
&[],
ContractProvenance::OperatorSupplied
)),
NominationVerdict::Refused(NominationRefusal::BaselineIncomplete)
);
}
#[test]
fn empty_or_oversized_text_is_refused() {
let green = vec![check("a", true)];
let mut c = ctx(
NoChangeKind::PremiseWrong,
&green,
ContractProvenance::OperatorSupplied,
);
c.summary = " ";
assert_eq!(
evaluate_nomination(c),
NominationVerdict::Refused(NominationRefusal::UnusableText("summary"))
);
let huge = "x".repeat(MAX_FINDING_TEXT + 1);
let mut c2 = ctx(
NoChangeKind::PremiseWrong,
&green,
ContractProvenance::OperatorSupplied,
);
c2.evidence = &huge;
assert_eq!(
evaluate_nomination(c2),
NominationVerdict::Refused(NominationRefusal::UnusableText("evidence"))
);
}
#[test]
fn session_history_is_reported_before_content() {
let green = vec![check("a", true)];
let mut c = ctx(
NoChangeKind::PremiseWrong,
&green,
ContractProvenance::OperatorSupplied,
);
c.mutated = true;
c.summary = "";
assert_eq!(
evaluate_nomination(c),
NominationVerdict::Refused(NominationRefusal::WorktreeWasMutated)
);
}
fn git_repo() -> tempfile::TempDir {
let dir = tempfile::tempdir().unwrap();
let git = |args: &[&str]| {
std::process::Command::new("git")
.current_dir(dir.path())
.args(args)
.output()
.unwrap();
};
git(&["init", "-q"]);
git(&["config", "user.email", "t@t"]);
git(&["config", "user.name", "t"]);
std::fs::write(dir.path().join("f.txt"), "original\n").unwrap();
git(&["add", "-A"]);
git(&["commit", "-qm", "init"]);
dir
}
fn git(dir: &std::path::Path, args: &[&str]) {
std::process::Command::new("git")
.current_dir(dir)
.args(args)
.output()
.unwrap();
}
#[test]
fn a_committed_edit_is_not_a_pristine_tree() {
let dir = git_repo();
let start = head_commit(dir.path()).expect("HEAD readable");
let before = worktree_fingerprint(dir.path()).unwrap();
assert_eq!(worktree_is_pristine(dir.path(), &start), Some(true));
std::fs::write(dir.path().join("f.txt"), "mutated\n").unwrap();
git(dir.path(), &["add", "-A"]);
git(dir.path(), &["commit", "-qm", "sneak"]);
assert_ne!(
worktree_fingerprint(dir.path()).unwrap(),
before,
"a commit must move the fingerprint"
);
assert_eq!(
worktree_is_pristine(dir.path(), &start),
Some(false),
"HEAD moved, so the tree is not the one this session started on"
);
}
#[test]
fn an_assume_unchanged_flag_is_not_a_pristine_tree() {
let dir = git_repo();
let start = head_commit(dir.path()).expect("HEAD readable");
let before = worktree_fingerprint(dir.path()).unwrap();
git(dir.path(), &["update-index", "--assume-unchanged", "f.txt"]);
assert_ne!(
worktree_fingerprint(dir.path()).unwrap(),
before,
"setting the flag must itself register as a mutation"
);
assert_eq!(
worktree_is_pristine(dir.path(), &start),
Some(false),
"a tree that can hide later edits is not pristine"
);
std::fs::write(dir.path().join("f.txt"), "tampered\n").unwrap();
assert_eq!(worktree_is_pristine(dir.path(), &start), Some(false));
}
#[test]
fn skip_worktree_is_caught_too() {
let dir = git_repo();
let start = head_commit(dir.path()).expect("HEAD readable");
git(dir.path(), &["update-index", "--skip-worktree", "f.txt"]);
assert_eq!(worktree_is_pristine(dir.path(), &start), Some(false));
}
#[test]
fn an_ordinary_edit_still_shows() {
let dir = git_repo();
let start = head_commit(dir.path()).expect("HEAD readable");
std::fs::write(dir.path().join("f.txt"), "edited\n").unwrap();
assert_eq!(worktree_is_pristine(dir.path(), &start), Some(false));
}
#[test]
fn a_head_that_does_not_match_is_never_pristine() {
let dir = git_repo();
assert_eq!(
worktree_is_pristine(dir.path(), "0000000000000000000000000000000000000000"),
Some(false)
);
}
#[test]
fn the_ledger_is_one_way() {
let ledger = MutationLedger::new();
assert!(!ledger.has_mutated());
ledger.record_mutation();
ledger.record_mutation();
assert!(ledger.has_mutated());
}
#[test]
fn trusted_provenance_has_exactly_one_untrusted_member() {
assert!(ContractProvenance::OperatorSupplied.is_trusted());
assert!(ContractProvenance::HumanConfirmed.is_trusted());
assert!(ContractProvenance::RuntimeGenerated.is_trusted());
assert!(
!ContractProvenance::ModelDerived.is_trusted(),
"a contract the session's own model wrote is never trusted — widening \
this is how the gate stops working"
);
}
}