use std::path::{Component, Path, PathBuf};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::models::{
VerificationEvidenceRecord, VerificationStatus, verification_evidence_beads_summary,
};
pub const COMPLETION_AUDIT_CHECKLIST_SCHEMA_V1: &str = "ee.completion_audit.checklist.v1";
pub const COMPLETION_AUDIT_REPORT_SCHEMA_V2: &str = "ee.completion_audit.report.v2";
const COMPLETION_AUDIT_REQUIRED_REMOTE_WRAPPER: &str = "scripts/rch_verify.sh -- <cargo command>";
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RequirementKind {
DocumentationRead,
CodeInvestigation,
Coordination,
Tracker,
Verification,
Command,
FileArtifact,
SkillApplication,
CompletionAudit,
PermissionGate,
Unknown,
}
impl RequirementKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::DocumentationRead => "documentation_read",
Self::CodeInvestigation => "code_investigation",
Self::Coordination => "coordination",
Self::Tracker => "tracker",
Self::Verification => "verification",
Self::Command => "command",
Self::FileArtifact => "file_artifact",
Self::SkillApplication => "skill_application",
Self::CompletionAudit => "completion_audit",
Self::PermissionGate => "permission_gate",
Self::Unknown => "unknown",
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChecklistSource {
pub label: String,
pub kind: String,
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SourceSpan {
pub label: String,
pub start: usize,
pub end: usize,
pub text: String,
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EvidenceExpectation {
pub kind: String,
pub target: String,
pub strength: String,
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VerificationExpectation {
pub kind: String,
pub target: String,
pub required: bool,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CompletionRequirement {
pub id: String,
pub kind: RequirementKind,
pub summary: String,
pub evidence_expectations: Vec<EvidenceExpectation>,
pub verification_expectations: Vec<VerificationExpectation>,
pub source_spans: Vec<SourceSpan>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UnknownClause {
pub id: String,
pub reason: String,
pub source_span: SourceSpan,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChecklistSummary {
pub requirement_count: usize,
pub unknown_count: usize,
pub has_unknowns: bool,
pub source_bytes: usize,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CompletionChecklist {
pub schema: String,
pub source: ChecklistSource,
pub objective_hash: String,
pub objective_text: String,
pub requirements: Vec<CompletionRequirement>,
pub unknown_clauses: Vec<UnknownClause>,
pub summary: ChecklistSummary,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EvidenceRecordStatus {
Pass,
Fail,
CapacityBlocked,
StaticOnly,
Stale,
Missing,
Inconclusive,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RequirementSupport {
Direct,
Supporting,
Weak,
Blocked,
Stale,
Missing,
Contradicted,
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EvidenceRecord {
pub kind: String,
pub target: String,
pub source: String,
pub status: EvidenceRecordStatus,
pub strength: String,
pub summary: String,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EvidenceBundle {
pub records: Vec<EvidenceRecord>,
}
#[must_use]
pub fn evidence_bundle_from_verification_records(
records: &[VerificationEvidenceRecord],
) -> EvidenceBundle {
let mut records = records
.iter()
.map(evidence_record_from_verification_record)
.collect::<Vec<_>>();
records.sort();
records.dedup();
EvidenceBundle { records }
}
#[must_use]
pub fn evidence_record_from_verification_record(
record: &VerificationEvidenceRecord,
) -> EvidenceRecord {
evidence_record(
verification_record_kind(record),
&record.gate_name,
&verification_record_source(record),
verification_record_status(record),
verification_record_strength(record),
&verification_record_summary(record),
)
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MissingExpectation {
pub kind: String,
pub target: String,
pub required: bool,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RequirementEvidence {
pub requirement_id: String,
pub support: RequirementSupport,
pub confidence: String,
pub evidence_records: Vec<EvidenceRecord>,
pub missing_expectations: Vec<MissingExpectation>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ClaimContradictionKind {
StaleAcceptance,
UnsupportedDuplicateClaim,
VerifierInconclusive,
SourceDocsConflict,
PermissionGateUnresolved,
NeedsOwnerDecision,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ClaimContradiction {
pub kind: ClaimContradictionKind,
pub target: String,
pub blocks_closure: bool,
pub suggested_tracker_action: String,
pub evidence_records: Vec<EvidenceRecord>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CompletionVerdict {
Complete,
Incomplete,
Blocked,
Unknown,
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CompletionGap {
pub requirement_id: String,
pub support: RequirementSupport,
pub reason: String,
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResidualRisk {
pub kind: String,
pub target: String,
pub reason: String,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum LocalBuildPolicyState {
LocalDisallowedAttempt,
RemoteRequiredBlocked,
RemoteVerified,
RemoteRequiredReady,
Unknown,
}
impl LocalBuildPolicyState {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::LocalDisallowedAttempt => "local_disallowed_attempt",
Self::RemoteRequiredBlocked => "remote_required_blocked",
Self::RemoteVerified => "remote_verified",
Self::RemoteRequiredReady => "remote_required_ready",
Self::Unknown => "unknown",
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LocalBuildPolicyAudit {
pub policy: String,
pub state: LocalBuildPolicyState,
pub required_remote_wrapper: String,
pub evidence_records: Vec<EvidenceRecord>,
pub repair_actions: Vec<String>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CompletionAuditReport {
pub schema: String,
pub checklist: CompletionChecklist,
pub evidence_by_requirement: Vec<RequirementEvidence>,
pub gaps: Vec<CompletionGap>,
pub residual_risks: Vec<ResidualRisk>,
pub recommended_next_actions: Vec<String>,
pub local_build_policy: LocalBuildPolicyAudit,
pub completion_verdict: CompletionVerdict,
}
#[must_use]
pub fn build_completion_audit_report_for_workspace(
source_label: &str,
objective_text: &str,
workspace: &Path,
extra_evidence: Option<EvidenceBundle>,
) -> CompletionAuditReport {
let checklist = extract_completion_checklist(source_label, objective_text);
let mut bundle = build_workspace_completion_evidence(workspace, &checklist);
if let Some(extra) = extra_evidence {
bundle.records.extend(extra.records);
}
bundle.records.sort();
bundle.records.dedup();
build_completion_audit_report(&checklist, &bundle)
}
#[must_use]
pub fn build_workspace_completion_evidence(
workspace: &Path,
checklist: &CompletionChecklist,
) -> EvidenceBundle {
let mut records = Vec::new();
records.push(evidence_record(
"completion_audit",
"prompt-to-artifact checklist",
"ee handoff completion-audit",
EvidenceRecordStatus::Pass,
"direct",
"completion audit command produced an objective-to-artifact checklist",
));
records.push(evidence_record(
"completion_audit",
"explicit objective requirements",
"ee handoff completion-audit",
EvidenceRecordStatus::Pass,
"direct",
"completion audit command evaluated explicit objective requirements",
));
records.extend(local_cargo_policy_workspace_evidence(workspace));
for requirement in &checklist.requirements {
for expectation in &requirement.evidence_expectations {
push_static_workspace_evidence(&mut records, workspace, expectation);
}
for expectation in &requirement.verification_expectations {
let expectation = EvidenceExpectation {
kind: expectation.kind.clone(),
target: expectation.target.clone(),
strength: if expectation.required {
"direct".to_owned()
} else {
"supporting".to_owned()
},
};
push_static_workspace_evidence(&mut records, workspace, &expectation);
}
}
records.sort();
records.dedup();
EvidenceBundle { records }
}
fn evidence_record(
kind: &str,
target: &str,
source: &str,
status: EvidenceRecordStatus,
strength: &str,
summary: &str,
) -> EvidenceRecord {
EvidenceRecord {
kind: kind.to_owned(),
target: target.to_owned(),
source: source.to_owned(),
status,
strength: strength.to_owned(),
summary: summary.to_owned(),
}
}
fn verification_record_kind(record: &VerificationEvidenceRecord) -> &'static str {
if record.offload.required_remote || record.offload.offload_tool.as_deref() == Some("rch") {
"rch"
} else if record.offload.offload_tool.as_deref() == Some("github_actions") {
"ci"
} else {
"verification"
}
}
fn verification_record_status(record: &VerificationEvidenceRecord) -> EvidenceRecordStatus {
match record.status {
VerificationStatus::Passed if record.is_authoritative_pass() => EvidenceRecordStatus::Pass,
VerificationStatus::Passed => EvidenceRecordStatus::StaticOnly,
VerificationStatus::Failed => EvidenceRecordStatus::Fail,
VerificationStatus::Blocked => EvidenceRecordStatus::CapacityBlocked,
VerificationStatus::FallbackDetected if record.offload.required_remote => {
EvidenceRecordStatus::CapacityBlocked
}
VerificationStatus::FallbackDetected
| VerificationStatus::Interrupted
| VerificationStatus::Unknown => EvidenceRecordStatus::Inconclusive,
}
}
fn verification_record_strength(record: &VerificationEvidenceRecord) -> &'static str {
match verification_record_status(record) {
EvidenceRecordStatus::Pass | EvidenceRecordStatus::Fail => "direct",
EvidenceRecordStatus::CapacityBlocked => {
if record.offload.required_remote {
"direct"
} else {
"supporting"
}
}
EvidenceRecordStatus::StaticOnly => "supporting",
EvidenceRecordStatus::Stale
| EvidenceRecordStatus::Missing
| EvidenceRecordStatus::Inconclusive => "weak",
}
}
fn verification_record_source(record: &VerificationEvidenceRecord) -> String {
format!("{}:{}", record.schema, record.verification_id)
}
fn verification_record_summary(record: &VerificationEvidenceRecord) -> String {
verification_evidence_beads_summary(record)
}
fn local_cargo_policy_workspace_evidence(workspace: &Path) -> Vec<EvidenceRecord> {
let mut records = Vec::new();
let tripwire_path = workspace.join("scripts/check-local-cargo-tripwire.sh");
if tripwire_path.is_file() {
records.push(evidence_record(
"local_cargo_tripwire",
"cargo/build/test command",
"scripts/check-local-cargo-tripwire.sh",
EvidenceRecordStatus::Pass,
"supporting",
"local_cargo_policy_state=remote_required_ready; repository verification tooling owns Cargo/RCH enforcement; ee command-risk memory is advisory only",
));
} else {
records.push(evidence_record(
"local_cargo_tripwire",
"cargo/build/test command",
"scripts/check-local-cargo-tripwire.sh",
EvidenceRecordStatus::Inconclusive,
"supporting",
"local_cargo_policy_state=unknown; repository-local Cargo tripwire is unavailable; ee does not substitute as a command gate",
));
}
let build_admission = super::disk_pressure::gather_build_admission_report(
&super::disk_pressure::BuildAdmissionOptions {
workspace: workspace.to_path_buf(),
workspace_source: "completion_audit",
min_free_bytes: 1024 * 1024 * 1024,
artifact_destinations: Vec::new(),
},
);
if !build_admission.admitted {
records.push(evidence_record(
"remote_rch",
"cargo/build/test command",
"build admission preflight",
EvidenceRecordStatus::CapacityBlocked,
"direct",
"local_cargo_policy_state=remote_required_blocked; build admission blocked RCH-only verification before any local fallback",
));
}
records.extend(local_cargo_process_scan_evidence(
&super::support_bundle::local_cargo_tripwire_process_scan_json(workspace),
));
records
}
fn local_cargo_process_scan_evidence(process_scan: &Value) -> Vec<EvidenceRecord> {
let status = process_scan
.get("status")
.and_then(Value::as_str)
.unwrap_or("unavailable");
let count = super::support_bundle::local_cargo_tripwire_blocking_count(process_scan);
let source = "local Cargo active-process tripwire";
if super::support_bundle::local_cargo_tripwire_detected_bypass(process_scan) {
vec![evidence_record(
"local_cargo_tripwire",
"cargo/build/test command",
source,
EvidenceRecordStatus::Fail,
"direct",
&format!(
"local_cargo_policy_state=local_disallowed_attempt; active process scan detected {count} local Cargo/rustc process(es) for this workspace without rch exec"
),
)]
} else {
match status {
"clean" | "ok" => vec![evidence_record(
"local_cargo_tripwire",
"cargo/build/test command",
source,
EvidenceRecordStatus::Pass,
"supporting",
"local_cargo_policy_state=remote_required_ready; active process scan found no local Cargo/rustc bypass",
)],
_ => vec![evidence_record(
"local_cargo_tripwire",
"cargo/build/test command",
source,
EvidenceRecordStatus::Inconclusive,
"weak",
&format!(
"local_cargo_policy_state=unknown; active process scan unavailable ({status})"
),
)],
}
}
}
fn push_static_workspace_evidence(
records: &mut Vec<EvidenceRecord>,
workspace: &Path,
expectation: &EvidenceExpectation,
) {
match expectation.kind.as_str() {
"file_read"
if static_workspace_file_exists_without_symlinks(
workspace,
Path::new(&expectation.target),
) =>
{
records.push(evidence_record(
"file_read",
&expectation.target,
&format!("{} exists", workspace.join(&expectation.target).display()),
EvidenceRecordStatus::StaticOnly,
"supporting",
"file exists, but this does not prove the agent read and applied it",
));
}
"beads"
if static_workspace_file_exists_without_symlinks(
workspace,
Path::new(".beads").join("issues.jsonl").as_path(),
) =>
{
records.push(evidence_record(
"beads",
".beads/issues.jsonl",
".beads/issues.jsonl exists",
EvidenceRecordStatus::StaticOnly,
"supporting",
"Beads store exists, but this does not prove status/comment evidence was updated",
));
}
"code_inspection"
if static_workspace_directory_exists_without_symlinks(workspace, Path::new("src")) =>
{
records.push(evidence_record(
"code_inspection",
"repository",
"src directory exists",
EvidenceRecordStatus::StaticOnly,
"supporting",
"source tree exists, but this does not prove a targeted architecture audit happened",
));
}
"rch"
if static_workspace_file_exists_without_symlinks(
workspace,
Path::new("scripts").join("rch_verify.sh").as_path(),
) =>
{
records.push(evidence_record(
"rch",
"remote build metadata",
"scripts/rch_verify.sh exists",
EvidenceRecordStatus::StaticOnly,
"supporting",
"RCH helper exists, but this does not prove a remote verifier passed",
));
}
"file_or_path"
if static_workspace_path_exists_without_symlinks(
workspace,
Path::new(&expectation.target),
) =>
{
records.push(evidence_record(
"file_or_path",
&expectation.target,
&format!("{} exists", workspace.join(&expectation.target).display()),
EvidenceRecordStatus::StaticOnly,
"supporting",
"path exists, but this does not prove it was inspected or changed correctly",
));
}
"agent_mail" if agent_mail_archive_present() => {
records.push(evidence_record(
"agent_mail",
&expectation.target,
"agent mail archive present",
EvidenceRecordStatus::StaticOnly,
"supporting",
"Agent Mail storage exists, but this does not prove inbox handling or acknowledgements",
));
}
_ => {}
}
}
fn static_workspace_file_exists_without_symlinks(workspace: &Path, relative: &Path) -> bool {
static_workspace_path_file_type_without_symlinks(workspace, relative)
.is_some_and(|file_type| file_type.is_file())
}
fn static_workspace_directory_exists_without_symlinks(workspace: &Path, relative: &Path) -> bool {
static_workspace_path_file_type_without_symlinks(workspace, relative)
.is_some_and(|file_type| file_type.is_dir())
}
fn static_workspace_path_exists_without_symlinks(workspace: &Path, relative: &Path) -> bool {
static_workspace_path_file_type_without_symlinks(workspace, relative).is_some()
}
fn static_workspace_path_file_type_without_symlinks(
workspace: &Path,
relative: &Path,
) -> Option<std::fs::FileType> {
let path = workspace_relative_static_evidence_path(workspace, relative)?;
if first_existing_static_evidence_symlink_component(&path)
.ok()
.flatten()
.is_some()
{
return None;
}
std::fs::symlink_metadata(path)
.ok()
.map(|metadata| metadata.file_type())
}
fn workspace_relative_static_evidence_path(workspace: &Path, relative: &Path) -> Option<PathBuf> {
if relative.as_os_str().is_empty() || relative.is_absolute() {
return None;
}
if relative.components().any(|component| {
matches!(
component,
Component::ParentDir | Component::Prefix(_) | Component::RootDir
)
}) {
return None;
}
Some(workspace.join(relative))
}
fn first_existing_static_evidence_symlink_component(
path: &Path,
) -> std::io::Result<Option<PathBuf>> {
let mut current = PathBuf::new();
for component in path.components() {
current.push(component.as_os_str());
match std::fs::symlink_metadata(¤t) {
Ok(metadata) if metadata.file_type().is_symlink() => return Ok(Some(current)),
Ok(_) => {}
Err(error)
if matches!(
error.kind(),
std::io::ErrorKind::NotFound | std::io::ErrorKind::NotADirectory
) =>
{
return Ok(None);
}
Err(error) => return Err(error),
}
}
Ok(None)
}
fn agent_mail_archive_present() -> bool {
std::env::var_os("HOME").is_some_and(|home| {
std::path::PathBuf::from(home)
.join(".local/share/mcp_agent_mail")
.exists()
})
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct RequirementAccumulator {
kind: RequirementKind,
summary: String,
evidence_expectations: Vec<EvidenceExpectation>,
verification_expectations: Vec<VerificationExpectation>,
source_spans: Vec<SourceSpan>,
}
#[derive(Clone, Copy, Debug)]
struct PhraseRequirementSpec<'a> {
phrases: &'a [&'a str],
kind: RequirementKind,
summary: &'a str,
evidence: EvidenceSpec<'a>,
verification: VerificationSpec<'a>,
}
#[derive(Clone, Copy, Debug)]
struct EvidenceSpec<'a> {
kind: &'a str,
target: &'a str,
strength: &'a str,
}
#[derive(Clone, Copy, Debug)]
struct VerificationSpec<'a> {
kind: &'a str,
target: &'a str,
required: bool,
}
#[must_use]
pub fn extract_completion_checklist(
source_label: &str,
objective_text: &str,
) -> CompletionChecklist {
let label = if source_label.trim().is_empty() {
"objective"
} else {
source_label.trim()
};
let mut requirements = Vec::new();
let mut unknowns = Vec::new();
add_literal_documentation_requirements(&mut requirements, label, objective_text);
add_keyword_requirements(&mut requirements, label, objective_text);
add_backtick_requirements(&mut requirements, label, objective_text);
add_token_requirements(&mut requirements, label, objective_text);
add_unknown_clauses(&mut unknowns, label, objective_text);
if objective_text.trim().is_empty() {
unknowns.push(UnknownClause {
id: String::new(),
reason: "empty_objective".to_owned(),
source_span: SourceSpan {
label: label.to_owned(),
start: 0,
end: 0,
text: String::new(),
},
});
}
requirements.sort_by(|left, right| {
first_span_start(&left.source_spans)
.cmp(&first_span_start(&right.source_spans))
.then_with(|| left.kind.cmp(&right.kind))
.then_with(|| left.summary.cmp(&right.summary))
});
unknowns.sort_by(|left, right| {
left.source_span
.start
.cmp(&right.source_span.start)
.then_with(|| left.reason.cmp(&right.reason))
});
let requirements = requirements
.into_iter()
.enumerate()
.map(|(index, mut item)| {
item.source_spans.sort();
item.source_spans.dedup();
item.evidence_expectations.sort();
item.evidence_expectations.dedup();
item.verification_expectations.sort();
item.verification_expectations.dedup();
CompletionRequirement {
id: format!("req_{:03}", index + 1),
kind: item.kind,
summary: item.summary,
evidence_expectations: item.evidence_expectations,
verification_expectations: item.verification_expectations,
source_spans: item.source_spans,
}
})
.collect::<Vec<_>>();
let unknown_clauses = unknowns
.into_iter()
.enumerate()
.map(|(index, mut clause)| {
clause.id = format!("unk_{:03}", index + 1);
clause
})
.collect::<Vec<_>>();
CompletionChecklist {
schema: COMPLETION_AUDIT_CHECKLIST_SCHEMA_V1.to_owned(),
source: ChecklistSource {
label: label.to_owned(),
kind: "objective_text".to_owned(),
},
objective_hash: objective_hash(objective_text),
objective_text: objective_text.to_owned(),
summary: ChecklistSummary {
requirement_count: requirements.len(),
unknown_count: unknown_clauses.len(),
has_unknowns: !unknown_clauses.is_empty(),
source_bytes: objective_text.len(),
},
requirements,
unknown_clauses,
}
}
#[must_use]
pub fn evaluate_completion_evidence(
checklist: &CompletionChecklist,
bundle: &EvidenceBundle,
) -> Vec<RequirementEvidence> {
checklist
.requirements
.iter()
.map(|requirement| evaluate_requirement_evidence(requirement, bundle))
.collect()
}
fn evaluate_requirement_evidence(
requirement: &CompletionRequirement,
bundle: &EvidenceBundle,
) -> RequirementEvidence {
let mut records = Vec::new();
let mut missing = Vec::new();
for expectation in &requirement.evidence_expectations {
let matches = matching_records(bundle, &expectation.kind, &expectation.target);
if matches.is_empty() {
missing.push(MissingExpectation {
kind: expectation.kind.clone(),
target: expectation.target.clone(),
required: expectation.strength == "direct",
});
}
records.extend(matches);
}
for expectation in &requirement.verification_expectations {
let matches = matching_records(bundle, &expectation.kind, &expectation.target);
if matches.is_empty() {
missing.push(MissingExpectation {
kind: expectation.kind.clone(),
target: expectation.target.clone(),
required: expectation.required,
});
}
records.extend(matches);
}
records.sort();
records.dedup();
missing.sort();
missing.dedup();
let support = classify_requirement_support(&records, &missing);
RequirementEvidence {
requirement_id: requirement.id.clone(),
support,
confidence: support_confidence(support).to_owned(),
evidence_records: records,
missing_expectations: missing,
}
}
fn matching_records(bundle: &EvidenceBundle, kind: &str, target: &str) -> Vec<EvidenceRecord> {
bundle
.records
.iter()
.filter(|record| record.kind == kind && target_matches(target, &record.target))
.cloned()
.collect()
}
fn target_matches(expected: &str, observed: &str) -> bool {
expected == observed || observed == "*" || expected == "*"
}
fn classify_requirement_support(
records: &[EvidenceRecord],
missing: &[MissingExpectation],
) -> RequirementSupport {
if records
.iter()
.any(|record| record.status == EvidenceRecordStatus::Fail)
{
return RequirementSupport::Contradicted;
}
if records
.iter()
.any(|record| record.status == EvidenceRecordStatus::CapacityBlocked)
{
return RequirementSupport::Blocked;
}
if records
.iter()
.any(|record| record.status == EvidenceRecordStatus::Stale)
{
return RequirementSupport::Stale;
}
if records
.iter()
.any(|record| record.status == EvidenceRecordStatus::Inconclusive)
{
return RequirementSupport::Weak;
}
let has_required_missing = missing.iter().any(|expectation| expectation.required);
if !has_required_missing {
if records.iter().any(|record| {
record.status == EvidenceRecordStatus::Pass && record.strength == "direct"
}) {
return RequirementSupport::Direct;
}
if records.iter().any(|record| {
record.status == EvidenceRecordStatus::Pass && record.strength == "supporting"
}) {
return RequirementSupport::Supporting;
}
}
if records
.iter()
.any(|record| record.status == EvidenceRecordStatus::StaticOnly)
{
return RequirementSupport::Weak;
}
if !missing.is_empty() {
return RequirementSupport::Missing;
}
RequirementSupport::Missing
}
fn support_confidence(support: RequirementSupport) -> &'static str {
match support {
RequirementSupport::Direct => "direct",
RequirementSupport::Supporting => "supporting",
RequirementSupport::Weak => "weak",
RequirementSupport::Blocked => "blocked",
RequirementSupport::Stale => "stale",
RequirementSupport::Missing => "missing",
RequirementSupport::Contradicted => "contradicted",
}
}
#[must_use]
pub fn detect_claim_contradictions(bundle: &EvidenceBundle) -> Vec<ClaimContradiction> {
let mut contradictions = Vec::new();
push_status_issue(
&mut contradictions,
bundle,
ClaimContradictionKind::StaleAcceptance,
&["acceptance", "beads_acceptance"],
&[EvidenceRecordStatus::Stale],
true,
);
push_status_issue(
&mut contradictions,
bundle,
ClaimContradictionKind::UnsupportedDuplicateClaim,
&["duplicate_claim", "duplicate_audit"],
&[EvidenceRecordStatus::Fail, EvidenceRecordStatus::Missing],
true,
);
push_verifier_inconclusive_issues(&mut contradictions, bundle);
push_status_issue(
&mut contradictions,
bundle,
ClaimContradictionKind::VerifierInconclusive,
&["local_cargo_tripwire"],
&[EvidenceRecordStatus::Fail],
true,
);
push_status_issue(
&mut contradictions,
bundle,
ClaimContradictionKind::SourceDocsConflict,
&["source_docs", "docs_contract"],
&[EvidenceRecordStatus::Fail],
true,
);
push_status_issue(
&mut contradictions,
bundle,
ClaimContradictionKind::PermissionGateUnresolved,
&["permission_record", "permission_gate"],
&[
EvidenceRecordStatus::Missing,
EvidenceRecordStatus::Inconclusive,
],
true,
);
push_status_issue(
&mut contradictions,
bundle,
ClaimContradictionKind::NeedsOwnerDecision,
&["scope_decision", "owner_decision"],
&[
EvidenceRecordStatus::Missing,
EvidenceRecordStatus::Inconclusive,
],
true,
);
contradictions.sort_by(|left, right| {
left.kind
.cmp(&right.kind)
.then_with(|| left.target.cmp(&right.target))
.then_with(|| {
left.suggested_tracker_action
.cmp(&right.suggested_tracker_action)
})
});
contradictions
}
fn push_status_issue(
contradictions: &mut Vec<ClaimContradiction>,
bundle: &EvidenceBundle,
kind: ClaimContradictionKind,
record_kinds: &[&str],
statuses: &[EvidenceRecordStatus],
blocks_closure: bool,
) {
let records = bundle
.records
.iter()
.filter(|record| {
record_kinds.contains(&record.kind.as_str()) && statuses.contains(&record.status)
})
.cloned()
.collect::<Vec<_>>();
push_grouped_issues(contradictions, kind, records, blocks_closure);
}
fn push_verifier_inconclusive_issues(
contradictions: &mut Vec<ClaimContradiction>,
bundle: &EvidenceBundle,
) {
let records = bundle
.records
.iter()
.filter(|record| verifier_record_kind(&record.kind))
.cloned()
.collect::<Vec<_>>();
let mut inconclusive_records = records
.iter()
.filter(|record| {
matches!(
record.status,
EvidenceRecordStatus::Inconclusive | EvidenceRecordStatus::Missing
)
})
.cloned()
.collect::<Vec<_>>();
inconclusive_records.retain(|record| !has_direct_verifier_pass(&records, &record.target));
push_grouped_issues(
contradictions,
ClaimContradictionKind::VerifierInconclusive,
inconclusive_records,
true,
);
}
fn verifier_record_kind(kind: &str) -> bool {
matches!(kind, "rch" | "remote_rch" | "verifier" | "test_result")
}
fn has_direct_verifier_pass(records: &[EvidenceRecord], target: &str) -> bool {
records.iter().any(|record| {
record.target == target
&& record.status == EvidenceRecordStatus::Pass
&& record.strength == "direct"
})
}
fn push_grouped_issues(
contradictions: &mut Vec<ClaimContradiction>,
kind: ClaimContradictionKind,
mut records: Vec<EvidenceRecord>,
blocks_closure: bool,
) {
records.sort();
records.dedup();
let mut targets = records
.iter()
.map(|record| record.target.clone())
.collect::<Vec<_>>();
targets.sort();
targets.dedup();
for target in targets {
let mut evidence_records = records
.iter()
.filter(|record| record.target == target)
.cloned()
.collect::<Vec<_>>();
evidence_records.sort();
evidence_records.dedup();
contradictions.push(ClaimContradiction {
kind,
target,
blocks_closure,
suggested_tracker_action: tracker_action_for(kind).to_owned(),
evidence_records,
});
}
}
fn tracker_action_for(kind: ClaimContradictionKind) -> &'static str {
match kind {
ClaimContradictionKind::StaleAcceptance => "refresh_acceptance_or_create_followup",
ClaimContradictionKind::UnsupportedDuplicateClaim => {
"replace_duplicate_claim_with_diff_evidence"
}
ClaimContradictionKind::VerifierInconclusive => "rerun_or_record_remote_verifier_result",
ClaimContradictionKind::SourceDocsConflict => "align_docs_schema_and_source_contract",
ClaimContradictionKind::PermissionGateUnresolved => "request_explicit_user_permission",
ClaimContradictionKind::NeedsOwnerDecision => "record_owner_decision_before_closure",
}
}
#[must_use]
pub fn build_completion_audit_report(
checklist: &CompletionChecklist,
bundle: &EvidenceBundle,
) -> CompletionAuditReport {
let evidence_by_requirement = evaluate_completion_evidence(checklist, bundle);
let contradictions = detect_claim_contradictions(bundle);
let gaps = completion_gaps(&evidence_by_requirement);
let residual_risks = completion_residual_risks(checklist, &contradictions);
let recommended_next_actions =
completion_recommended_next_actions(checklist, &gaps, &contradictions);
let completion_verdict =
completion_verdict(checklist, &evidence_by_requirement, &contradictions);
let local_build_policy = local_build_policy_audit(bundle);
CompletionAuditReport {
schema: COMPLETION_AUDIT_REPORT_SCHEMA_V2.to_owned(),
checklist: checklist.clone(),
evidence_by_requirement,
gaps,
residual_risks,
recommended_next_actions,
local_build_policy,
completion_verdict,
}
}
fn local_build_policy_audit(bundle: &EvidenceBundle) -> LocalBuildPolicyAudit {
let mut evidence_records = bundle
.records
.iter()
.filter(|record| local_build_policy_record(record))
.cloned()
.collect::<Vec<_>>();
evidence_records.sort();
evidence_records.dedup();
let state = if evidence_records.iter().any(local_disallowed_attempt_record) {
LocalBuildPolicyState::LocalDisallowedAttempt
} else if evidence_records.iter().any(remote_verified_record) {
LocalBuildPolicyState::RemoteVerified
} else if evidence_records.iter().any(remote_required_blocked_record) {
LocalBuildPolicyState::RemoteRequiredBlocked
} else if evidence_records.iter().any(|record| {
record.kind == "local_cargo_tripwire" && record.status == EvidenceRecordStatus::Pass
}) {
LocalBuildPolicyState::RemoteRequiredReady
} else {
LocalBuildPolicyState::Unknown
};
LocalBuildPolicyAudit {
policy: "rch_only".to_owned(),
state,
required_remote_wrapper: COMPLETION_AUDIT_REQUIRED_REMOTE_WRAPPER.to_owned(),
evidence_records,
repair_actions: local_build_policy_repair_actions(state),
}
}
fn local_build_policy_record(record: &EvidenceRecord) -> bool {
record.kind == "local_cargo_tripwire" || remote_verifier_record_kind(&record.kind)
}
fn local_disallowed_attempt_record(record: &EvidenceRecord) -> bool {
record.kind == "local_cargo_tripwire"
&& record.status == EvidenceRecordStatus::Fail
&& (record.summary.contains("local_disallowed_attempt")
|| record.summary.contains("local_cargo_disallowed")
|| record.source.contains("local_disallowed_attempt")
|| record.source.contains("local_cargo_disallowed"))
}
fn remote_verified_record(record: &EvidenceRecord) -> bool {
remote_verifier_record_kind(&record.kind)
&& record.status == EvidenceRecordStatus::Pass
&& record.strength == "direct"
}
fn remote_required_blocked_record(record: &EvidenceRecord) -> bool {
(remote_verifier_record_kind(&record.kind)
&& record.status == EvidenceRecordStatus::CapacityBlocked)
|| (record.kind == "local_cargo_tripwire"
&& (record.summary.contains("remote_required_blocked")
|| record.source.contains("remote_required_blocked")))
}
fn remote_verifier_record_kind(kind: &str) -> bool {
matches!(kind, "rch" | "remote_rch")
}
fn local_build_policy_repair_actions(state: LocalBuildPolicyState) -> Vec<String> {
match state {
LocalBuildPolicyState::LocalDisallowedAttempt => vec![
"stop_local_cargo_attempt".to_owned(),
"rerun_required_verification_with_rch".to_owned(),
],
LocalBuildPolicyState::RemoteRequiredBlocked => {
vec!["record_rch_blocker_and_retry_when_remote_path_is_healthy".to_owned()]
}
LocalBuildPolicyState::RemoteVerified => Vec::new(),
LocalBuildPolicyState::RemoteRequiredReady => {
vec!["run_required_verification_through_rch".to_owned()]
}
LocalBuildPolicyState::Unknown => vec!["collect_local_build_policy_evidence".to_owned()],
}
}
fn completion_gaps(evidence: &[RequirementEvidence]) -> Vec<CompletionGap> {
evidence
.iter()
.filter(|item| item.support != RequirementSupport::Direct)
.map(|item| CompletionGap {
requirement_id: item.requirement_id.clone(),
support: item.support,
reason: gap_reason(item.support).to_owned(),
})
.collect()
}
fn gap_reason(support: RequirementSupport) -> &'static str {
match support {
RequirementSupport::Direct => "direct_evidence_present",
RequirementSupport::Supporting => "supporting_evidence_is_not_direct_completion_proof",
RequirementSupport::Weak => "only_weak_or_proxy_evidence_present",
RequirementSupport::Blocked => "required_evidence_blocked",
RequirementSupport::Stale => "evidence_is_stale",
RequirementSupport::Missing => "required_evidence_missing",
RequirementSupport::Contradicted => "evidence_contradicts_completion_claim",
}
}
fn completion_residual_risks(
checklist: &CompletionChecklist,
contradictions: &[ClaimContradiction],
) -> Vec<ResidualRisk> {
let mut risks = Vec::new();
risks.extend(checklist.unknown_clauses.iter().map(|clause| ResidualRisk {
kind: "unknown_clause".to_owned(),
target: clause.id.clone(),
reason: clause.reason.clone(),
}));
risks.extend(contradictions.iter().map(|contradiction| ResidualRisk {
kind: contradiction_kind_name(contradiction.kind).to_owned(),
target: contradiction.target.clone(),
reason: contradiction.suggested_tracker_action.clone(),
}));
risks.sort();
risks.dedup();
risks
}
fn completion_recommended_next_actions(
checklist: &CompletionChecklist,
gaps: &[CompletionGap],
contradictions: &[ClaimContradiction],
) -> Vec<String> {
let mut actions = gaps
.iter()
.map(|gap| format!("collect_direct_evidence_for:{}", gap.requirement_id))
.collect::<Vec<_>>();
actions.extend(
checklist
.unknown_clauses
.iter()
.map(|clause| format!("clarify_unknown_clause:{}", clause.id)),
);
actions.extend(
contradictions
.iter()
.map(|contradiction| contradiction.suggested_tracker_action.clone()),
);
actions.sort();
actions.dedup();
actions
}
fn completion_verdict(
checklist: &CompletionChecklist,
evidence: &[RequirementEvidence],
contradictions: &[ClaimContradiction],
) -> CompletionVerdict {
if evidence
.iter()
.any(|item| item.support == RequirementSupport::Blocked)
|| contradictions.iter().any(|contradiction| {
contradiction.kind == ClaimContradictionKind::PermissionGateUnresolved
})
{
return CompletionVerdict::Blocked;
}
let has_non_direct_evidence = evidence
.iter()
.any(|item| item.support != RequirementSupport::Direct);
if !has_non_direct_evidence && contradictions.is_empty() {
return CompletionVerdict::Complete;
}
if !checklist.unknown_clauses.is_empty()
|| contradictions
.iter()
.any(|contradiction| contradiction.kind == ClaimContradictionKind::NeedsOwnerDecision)
{
return CompletionVerdict::Unknown;
}
if has_non_direct_evidence
|| contradictions.iter().any(|contradiction| {
!matches!(
contradiction.kind,
ClaimContradictionKind::NeedsOwnerDecision
| ClaimContradictionKind::PermissionGateUnresolved
)
})
{
return CompletionVerdict::Incomplete;
}
CompletionVerdict::Complete
}
fn contradiction_kind_name(kind: ClaimContradictionKind) -> &'static str {
match kind {
ClaimContradictionKind::StaleAcceptance => "stale_acceptance",
ClaimContradictionKind::UnsupportedDuplicateClaim => "unsupported_duplicate_claim",
ClaimContradictionKind::VerifierInconclusive => "verifier_inconclusive",
ClaimContradictionKind::SourceDocsConflict => "source_docs_conflict",
ClaimContradictionKind::PermissionGateUnresolved => "permission_gate_unresolved",
ClaimContradictionKind::NeedsOwnerDecision => "needs_owner_decision",
}
}
fn add_literal_documentation_requirements(
requirements: &mut Vec<RequirementAccumulator>,
label: &str,
text: &str,
) {
for file in ["AGENTS.md", "README.md"] {
let mut spans = positive_read_spans(label, text, file);
if spans.is_empty() {
spans = document_reference_spans(label, text, file);
}
if spans.is_empty() {
continue;
}
add_requirement(
requirements,
RequirementKind::DocumentationRead,
format!("Read and understand {file}"),
vec![evidence("file_read", file, "direct")],
vec![verification("prompt_requirement", file, true)],
spans,
);
}
}
fn add_keyword_requirements(
requirements: &mut Vec<RequirementAccumulator>,
label: &str,
text: &str,
) {
add_phrase_requirement(
requirements,
label,
text,
PhraseRequirementSpec {
phrases: &[
"code investigation",
"technical architecture",
"purpose of the project",
],
kind: RequirementKind::CodeInvestigation,
summary: "Investigate the codebase architecture and project purpose",
evidence: EvidenceSpec {
kind: "code_inspection",
target: "repository",
strength: "direct",
},
verification: VerificationSpec {
kind: "read_only_architecture_audit",
target: "repository",
required: true,
},
},
);
add_phrase_requirement(
requirements,
label,
text,
PhraseRequirementSpec {
phrases: &["mcp agent mail", "agent mail", "introduce yourself"],
kind: RequirementKind::Coordination,
summary: "Coordinate through MCP Agent Mail",
evidence: EvidenceSpec {
kind: "agent_mail",
target: "project inbox/outbox",
strength: "direct",
},
verification: VerificationSpec {
kind: "coordination_receipt",
target: "agent mail",
required: true,
},
},
);
add_phrase_requirement(
requirements,
label,
text,
PhraseRequirementSpec {
phrases: &["beads", "br ", "mark beads", "tracking your progress"],
kind: RequirementKind::Tracker,
summary: "Track progress through Beads",
evidence: EvidenceSpec {
kind: "beads",
target: ".beads/issues.jsonl",
strength: "direct",
},
verification: VerificationSpec {
kind: "tracker_comment_or_status",
target: "beads",
required: true,
},
},
);
add_phrase_requirement(
requirements,
label,
text,
PhraseRequirementSpec {
phrases: &["bv tool", "bv ", "prioritize"],
kind: RequirementKind::Tracker,
summary: "Use bv as the ranking aid when selecting work",
evidence: EvidenceSpec {
kind: "bv",
target: "bv robot output",
strength: "supporting",
},
verification: VerificationSpec {
kind: "triage_command",
target: "bv --robot-next or bv --robot-triage",
required: true,
},
},
);
add_phrase_requirement(
requirements,
label,
text,
PhraseRequirementSpec {
phrases: &["rch", "cargo builds", "cargo tests", "builds and tests"],
kind: RequirementKind::Verification,
summary: "Run Cargo/build/test verification through RCH only",
evidence: EvidenceSpec {
kind: "rch",
target: "remote build metadata",
strength: "direct",
},
verification: VerificationSpec {
kind: "remote_rch",
target: "cargo/build/test command",
required: true,
},
},
);
add_phrase_requirement(
requirements,
label,
text,
PhraseRequirementSpec {
phrases: &["stalled out", "stalled", "in progress"],
kind: RequirementKind::Tracker,
summary: "Reopen clearly stalled in-progress beads",
evidence: EvidenceSpec {
kind: "beads",
target: "in-progress bead freshness",
strength: "direct",
},
verification: VerificationSpec {
kind: "stale_bead_audit",
target: "br list --status in_progress",
required: true,
},
},
);
add_phrase_requirement(
requirements,
label,
text,
PhraseRequirementSpec {
phrases: &["acknowledge", "communication requests", "promptly respond"],
kind: RequirementKind::Coordination,
summary: "Acknowledge and respond to coordination requests",
evidence: EvidenceSpec {
kind: "agent_mail",
target: "ack/read state",
strength: "direct",
},
verification: VerificationSpec {
kind: "mail_ack_audit",
target: "ack_required messages",
required: true,
},
},
);
add_phrase_requirement(
requirements,
label,
text,
PhraseRequirementSpec {
phrases: &["completion audit", "prompt-to-artifact", "checklist"],
kind: RequirementKind::CompletionAudit,
summary: "Perform a completion audit before claiming done",
evidence: EvidenceSpec {
kind: "completion_audit",
target: "prompt-to-artifact checklist",
strength: "direct",
},
verification: VerificationSpec {
kind: "completion_audit",
target: "explicit objective requirements",
required: true,
},
},
);
add_phrase_requirement(
requirements,
label,
text,
PhraseRequirementSpec {
phrases: &["explicit permission", "permission", "no file deletion"],
kind: RequirementKind::PermissionGate,
summary: "Respect permission-gated filesystem actions",
evidence: EvidenceSpec {
kind: "permission_record",
target: "user authorization text",
strength: "direct",
},
verification: VerificationSpec {
kind: "permission_gate",
target: "filesystem mutation",
required: true,
},
},
);
}
fn add_phrase_requirement(
requirements: &mut Vec<RequirementAccumulator>,
label: &str,
text: &str,
spec: PhraseRequirementSpec<'_>,
) {
let spans = spec
.phrases
.iter()
.flat_map(|phrase| literal_spans(label, text, phrase))
.collect::<Vec<_>>();
if spans.is_empty() {
return;
}
add_requirement(
requirements,
spec.kind,
spec.summary.to_owned(),
vec![evidence(
spec.evidence.kind,
spec.evidence.target,
spec.evidence.strength,
)],
vec![verification(
spec.verification.kind,
spec.verification.target,
spec.verification.required,
)],
spans,
);
}
fn add_backtick_requirements(
requirements: &mut Vec<RequirementAccumulator>,
label: &str,
text: &str,
) {
for span in backtick_spans(label, text) {
let value = span.text.trim();
if looks_like_skill(value) {
add_requirement(
requirements,
RequirementKind::SkillApplication,
format!("Apply skill {value}"),
vec![evidence("skill", value, "supporting")],
vec![verification("skill_workflow", value, true)],
vec![span],
);
} else if looks_like_command(value) {
add_requirement(
requirements,
RequirementKind::Command,
format!("Run or verify command `{value}`"),
vec![evidence("command", value, "direct")],
vec![verification("command_output", value, true)],
vec![span],
);
}
}
}
fn add_token_requirements(requirements: &mut Vec<RequirementAccumulator>, label: &str, text: &str) {
for span in token_spans(label, text) {
let value = span.text.trim();
if looks_like_skill(value) {
add_requirement(
requirements,
RequirementKind::SkillApplication,
format!("Apply skill {value}"),
vec![evidence("skill", value, "supporting")],
vec![verification("skill_workflow", value, true)],
vec![span],
);
} else if looks_like_file_path(value) && !is_documentation_file(value) {
add_requirement(
requirements,
RequirementKind::FileArtifact,
format!("Account for file or path `{value}`"),
vec![evidence("file_or_path", value, "direct")],
vec![verification("artifact_inspection", value, true)],
vec![span],
);
}
}
}
fn add_unknown_clauses(unknowns: &mut Vec<UnknownClause>, label: &str, text: &str) {
for phrase in [
"dramatically enhance",
"take it to the next level",
"amazing features",
"world-class",
"as compelling",
] {
for span in literal_spans(label, text, phrase) {
unknowns.push(UnknownClause {
id: String::new(),
reason: "broad_ambition".to_owned(),
source_span: span,
});
}
}
for target in ["AGENTS.md", "README.md"] {
let positive_spans = positive_read_spans(label, text, target);
let negative = !negative_read_spans(label, text, target).is_empty();
if !positive_spans.is_empty() && negative {
if let Some(span) = positive_spans.into_iter().next() {
unknowns.push(UnknownClause {
id: String::new(),
reason: format!("contradictory_instruction:{target}"),
source_span: span,
});
}
}
}
}
fn positive_read_spans(label: &str, text: &str, target: &str) -> Vec<SourceSpan> {
let negative_spans = negative_read_spans(label, text, target);
literal_spans(label, text, &format!("read {target}"))
.into_iter()
.filter(|span| {
negative_spans
.iter()
.all(|negative| span.start < negative.start || span.end > negative.end)
})
.collect()
}
fn negative_read_spans(label: &str, text: &str, target: &str) -> Vec<SourceSpan> {
["do not read", "don't read"]
.into_iter()
.flat_map(|prefix| literal_spans(label, text, &format!("{prefix} {target}")))
.collect()
}
fn document_reference_spans(label: &str, text: &str, target: &str) -> Vec<SourceSpan> {
let negative_spans = negative_read_spans(label, text, target);
literal_spans(label, text, target)
.into_iter()
.filter(|span| {
negative_spans
.iter()
.all(|negative| span.start < negative.start || span.end > negative.end)
})
.collect()
}
fn add_requirement(
requirements: &mut Vec<RequirementAccumulator>,
kind: RequirementKind,
summary: String,
evidence_expectations: Vec<EvidenceExpectation>,
verification_expectations: Vec<VerificationExpectation>,
source_spans: Vec<SourceSpan>,
) {
if source_spans.is_empty() {
return;
}
if let Some(existing) = requirements
.iter_mut()
.find(|item| item.kind == kind && item.summary == summary)
{
existing.source_spans.extend(source_spans);
existing.evidence_expectations.extend(evidence_expectations);
existing
.verification_expectations
.extend(verification_expectations);
return;
}
requirements.push(RequirementAccumulator {
kind,
summary,
evidence_expectations,
verification_expectations,
source_spans,
});
}
fn evidence(kind: &str, target: &str, strength: &str) -> EvidenceExpectation {
EvidenceExpectation {
kind: kind.to_owned(),
target: target.to_owned(),
strength: strength.to_owned(),
}
}
fn verification(kind: &str, target: &str, required: bool) -> VerificationExpectation {
VerificationExpectation {
kind: kind.to_owned(),
target: target.to_owned(),
required,
}
}
fn objective_hash(text: &str) -> String {
let hash = blake3::hash(text.as_bytes()).to_hex().to_string();
hash.chars().take(24).collect()
}
fn first_span_start(spans: &[SourceSpan]) -> usize {
spans
.iter()
.map(|span| span.start)
.min()
.unwrap_or(usize::MAX)
}
fn literal_spans(label: &str, text: &str, needle: &str) -> Vec<SourceSpan> {
if needle.is_empty() {
return Vec::new();
}
let lower_text = text.to_ascii_lowercase();
let lower_needle = needle.to_ascii_lowercase();
let mut spans = Vec::new();
let mut offset = 0;
while offset <= lower_text.len() {
let Some(found) = lower_text[offset..].find(&lower_needle) else {
break;
};
let start = offset + found;
let end = start + lower_needle.len();
spans.push(SourceSpan {
label: label.to_owned(),
start,
end,
text: text[start..end].to_owned(),
});
offset = end;
}
spans
}
fn backtick_spans(label: &str, text: &str) -> Vec<SourceSpan> {
let bytes = text.as_bytes();
let mut spans = Vec::new();
let mut index = 0;
while index < bytes.len() {
let Some(open_rel) = bytes[index..].iter().position(|byte| *byte == b'`') else {
break;
};
let open = index + open_rel;
let content_start = open + 1;
let Some(close_rel) = bytes[content_start..].iter().position(|byte| *byte == b'`') else {
break;
};
let content_end = content_start + close_rel;
if content_start < content_end {
spans.push(SourceSpan {
label: label.to_owned(),
start: content_start,
end: content_end,
text: text[content_start..content_end].to_owned(),
});
}
index = content_end + 1;
}
spans
}
fn token_spans(label: &str, text: &str) -> Vec<SourceSpan> {
let mut spans = Vec::new();
let mut token_start = None;
for (index, ch) in text.char_indices() {
if ch.is_whitespace() {
if let Some(start) = token_start.take() {
push_trimmed_token(label, text, start, index, &mut spans);
}
} else if token_start.is_none() {
token_start = Some(index);
}
}
if let Some(start) = token_start {
push_trimmed_token(label, text, start, text.len(), &mut spans);
}
spans
}
fn push_trimmed_token(
label: &str,
text: &str,
start: usize,
end: usize,
spans: &mut Vec<SourceSpan>,
) {
let raw = &text[start..end];
let trimmed = raw.trim_matches(token_boundary_punctuation);
if trimmed.is_empty() {
return;
}
let leading = raw.find(trimmed).unwrap_or(0);
let token_start = start + leading;
let token_end = token_start + trimmed.len();
spans.push(SourceSpan {
label: label.to_owned(),
start: token_start,
end: token_end,
text: trimmed.to_owned(),
});
}
fn token_boundary_punctuation(ch: char) -> bool {
matches!(
ch,
',' | '.' | ';' | ':' | '(' | ')' | '[' | ']' | '{' | '}' | '"' | '\'' | '`' | '*'
)
}
fn looks_like_command(value: &str) -> bool {
let lower = value.to_ascii_lowercase();
["cargo", "br", "bv", "rch", "ee", "git", "./"]
.iter()
.any(|prefix| lower == *prefix || lower.starts_with(&format!("{prefix} ")))
}
fn looks_like_skill(value: &str) -> bool {
value.strip_prefix('$').is_some_and(|tail| {
tail.chars()
.all(|ch| ch.is_ascii_alphanumeric() || ch == '-' || ch == '_')
})
}
fn looks_like_file_path(value: &str) -> bool {
if value.starts_with("http://") || value.starts_with("https://") {
return false;
}
value.contains('/')
|| [
".md", ".rs", ".toml", ".json", ".jsonl", ".yaml", ".yml", ".sh",
]
.iter()
.any(|suffix| value.ends_with(suffix))
}
fn is_documentation_file(value: &str) -> bool {
matches!(value, "AGENTS.md" | "README.md")
}
#[cfg(test)]
mod tests {
use super::*;
fn kinds(checklist: &CompletionChecklist) -> Vec<RequirementKind> {
checklist
.requirements
.iter()
.map(|requirement| requirement.kind)
.collect()
}
fn has_summary(checklist: &CompletionChecklist, needle: &str) -> bool {
checklist
.requirements
.iter()
.any(|requirement| requirement.summary.contains(needle))
}
fn requirement_with_summary<'a>(
checklist: &'a CompletionChecklist,
needle: &str,
) -> &'a CompletionRequirement {
for requirement in &checklist.requirements {
if requirement.summary.contains(needle) {
return requirement;
}
}
panic!("missing requirement containing {needle}");
}
fn evidence_for_requirement<'a>(
evidence: &'a [RequirementEvidence],
requirement_id: &str,
) -> &'a RequirementEvidence {
for item in evidence {
if item.requirement_id == requirement_id {
return item;
}
}
panic!("missing evidence for requirement {requirement_id}");
}
fn record(
kind: &str,
target: &str,
source: &str,
status: EvidenceRecordStatus,
strength: &str,
) -> EvidenceRecord {
EvidenceRecord {
kind: kind.to_owned(),
target: target.to_owned(),
source: source.to_owned(),
status,
strength: strength.to_owned(),
summary: format!("{source}: {kind} {target}"),
}
}
#[test]
fn broad_swarm_objective_extracts_explicit_obligations_and_unknowns() {
let objective = "First read ALL of the AGENTS.md file and README.md file. \
Use code investigation to understand the technical architecture and purpose. \
Register with MCP Agent Mail, acknowledge communication requests, track progress via Beads, \
use the bv tool to prioritize, and run all cargo builds and tests through RCH. \
Look for stalled in progress beads, apply $idea-wizard, and perform a prompt-to-artifact completion audit. \
Also come up with amazing features that take it to the next level.";
let checklist = extract_completion_checklist("thread_goal", objective);
let kinds = kinds(&checklist);
assert_eq!(checklist.schema, COMPLETION_AUDIT_CHECKLIST_SCHEMA_V1);
assert!(kinds.contains(&RequirementKind::DocumentationRead));
assert!(kinds.contains(&RequirementKind::CodeInvestigation));
assert!(kinds.contains(&RequirementKind::Coordination));
assert!(kinds.contains(&RequirementKind::Tracker));
assert!(kinds.contains(&RequirementKind::Verification));
assert!(kinds.contains(&RequirementKind::SkillApplication));
assert!(kinds.contains(&RequirementKind::CompletionAudit));
assert!(has_summary(&checklist, "AGENTS.md"));
assert!(has_summary(&checklist, "README.md"));
assert!(
checklist
.unknown_clauses
.iter()
.any(|clause| clause.reason == "broad_ambition")
);
}
#[test]
fn duplicate_requirements_merge_source_spans() {
let checklist =
extract_completion_checklist("objective", "Read AGENTS.md, then read AGENTS.md again.");
let agents_requirements = checklist
.requirements
.iter()
.filter(|requirement| requirement.summary.contains("AGENTS.md"))
.collect::<Vec<_>>();
assert_eq!(agents_requirements.len(), 1);
assert_eq!(agents_requirements[0].source_spans.len(), 2);
}
#[test]
fn commands_and_paths_are_extracted_deterministically() {
let checklist = extract_completion_checklist(
"objective",
"Update src/core/lab.rs and verify with `cargo fmt --check`.",
);
assert!(has_summary(&checklist, "src/core/lab.rs"));
assert!(has_summary(&checklist, "cargo fmt --check"));
assert_eq!(
checklist
.requirements
.first()
.map(|requirement| requirement.id.as_str()),
Some("req_001")
);
}
#[test]
fn empty_objective_is_unknown_not_satisfied() {
let checklist = extract_completion_checklist("objective", " ");
assert!(checklist.requirements.is_empty());
assert_eq!(checklist.unknown_clauses.len(), 1);
assert_eq!(checklist.unknown_clauses[0].reason, "empty_objective");
assert!(checklist.summary.has_unknowns);
}
#[test]
fn contradictory_doc_instruction_becomes_unknown_clause() {
let checklist = extract_completion_checklist(
"objective",
"Read AGENTS.md carefully, but do not read AGENTS.md.",
);
assert!(
checklist
.unknown_clauses
.iter()
.any(|clause| clause.reason == "contradictory_instruction:AGENTS.md")
);
}
#[test]
fn negative_doc_instruction_alone_is_not_a_contradiction() {
let checklist =
extract_completion_checklist("objective", "Do not read AGENTS.md for this task.");
assert!(
!checklist
.requirements
.iter()
.any(|requirement| requirement.summary.contains("AGENTS.md"))
);
assert!(
checklist
.unknown_clauses
.iter()
.all(|clause| clause.reason != "contradictory_instruction:AGENTS.md")
);
}
#[test]
fn evidence_adapter_distinguishes_remote_rch_pass() {
let checklist = extract_completion_checklist(
"objective",
"Run all cargo builds and tests through RCH.",
);
let requirement = requirement_with_summary(&checklist, "RCH only");
let bundle = EvidenceBundle {
records: vec![
record(
"rch",
"remote build metadata",
"rch job 159 on worker-b",
EvidenceRecordStatus::Pass,
"direct",
),
record(
"remote_rch",
"cargo/build/test command",
"cargo test --lib completion_audit",
EvidenceRecordStatus::Pass,
"direct",
),
],
};
let evidence = evaluate_completion_evidence(&checklist, &bundle);
let item = evidence_for_requirement(&evidence, &requirement.id);
assert_eq!(item.support, RequirementSupport::Direct);
assert!(item.missing_expectations.is_empty());
assert_eq!(item.evidence_records.len(), 2);
}
#[test]
fn evidence_adapter_distinguishes_rch_capacity_blocker_from_pass() {
let checklist = extract_completion_checklist(
"objective",
"Run all cargo builds and tests through RCH.",
);
let requirement = requirement_with_summary(&checklist, "RCH only");
let bundle = EvidenceBundle {
records: vec![record(
"remote_rch",
"cargo/build/test command",
"rch all_workers_at_capacity",
EvidenceRecordStatus::CapacityBlocked,
"direct",
)],
};
let evidence = evaluate_completion_evidence(&checklist, &bundle);
let item = evidence_for_requirement(&evidence, &requirement.id);
assert_eq!(item.support, RequirementSupport::Blocked);
assert_eq!(item.confidence, "blocked");
}
#[test]
fn canonical_verification_records_feed_completion_audit_rch_policy() {
let passed =
crate::models::verification_evidence_record_from_rch_verify(&serde_json::json!({
"schema": crate::models::RCH_VERIFY_SCHEMA_V1,
"command_text": "cargo test --lib completion_audit",
"command_hash": "sha256:rch-pass",
"command_kind": "cargo_test",
"status": "remote_pass",
"exit_code": 0,
"bead_id": "bd-1nxz4.5",
"worker_id": "vmi123",
"remote_required": true
}))
.expect("RCH pass proof normalizes");
let blocked =
crate::models::verification_evidence_record_from_rch_verify(&serde_json::json!({
"schema": crate::models::RCH_VERIFY_SCHEMA_V1,
"command_text": "cargo clippy --all-targets -- -D warnings",
"command_hash": "sha256:rch-blocked",
"command_kind": "cargo_clippy",
"status": "rch_environment_failure",
"exit_code": 1,
"degraded_codes": ["rch_verify_client_daemon_version_skew"],
"remote_required": true
}))
.expect("RCH blocker proof normalizes");
let pass_bundle = evidence_bundle_from_verification_records(&[passed]);
assert_eq!(pass_bundle.records.len(), 1);
assert_eq!(pass_bundle.records[0].kind, "rch");
assert_eq!(pass_bundle.records[0].target, "cargo_test");
assert_eq!(pass_bundle.records[0].status, EvidenceRecordStatus::Pass);
assert_eq!(pass_bundle.records[0].strength, "direct");
assert!(
pass_bundle.records[0]
.summary
.contains("command_hash=sha256:rch-pass")
);
assert!(!pass_bundle.records[0].summary.contains("cargo test"));
assert_eq!(
local_build_policy_audit(&pass_bundle).state,
LocalBuildPolicyState::RemoteVerified
);
let blocked_bundle = evidence_bundle_from_verification_records(&[blocked]);
assert_eq!(
blocked_bundle.records[0].status,
EvidenceRecordStatus::CapacityBlocked
);
assert_eq!(
local_build_policy_audit(&blocked_bundle).state,
LocalBuildPolicyState::RemoteRequiredBlocked
);
}
#[test]
fn completion_report_flags_local_cargo_tripwire_failure_as_verifier_risk() {
let checklist = extract_completion_checklist(
"objective",
"Run all cargo builds and tests through RCH.",
);
let bundle = EvidenceBundle {
records: vec![record(
"local_cargo_tripwire",
"cargo/build/test command",
"policyStatus=local_cargo_disallowed",
EvidenceRecordStatus::Fail,
"direct",
)],
};
let report = build_completion_audit_report(&checklist, &bundle);
assert_eq!(
report.local_build_policy.state,
LocalBuildPolicyState::LocalDisallowedAttempt
);
assert!(
report
.local_build_policy
.repair_actions
.iter()
.any(|action| action == "rerun_required_verification_with_rch")
);
assert_eq!(report.completion_verdict, CompletionVerdict::Incomplete);
assert!(report.gaps.iter().any(|gap| {
gap.reason == "required_evidence_missing"
|| gap.reason == "evidence_contradicts_completion_claim"
}));
assert!(report.residual_risks.iter().any(|risk| {
risk.kind == "verifier_inconclusive" && risk.target == "cargo/build/test command"
}));
assert!(
report
.recommended_next_actions
.iter()
.any(|action| action == "rerun_or_record_remote_verifier_result")
);
}
#[test]
fn completion_report_flags_active_local_cargo_process_scan_as_local_attempt() {
let records = local_cargo_process_scan_evidence(&serde_json::json!({
"schema": "ee.rch_local_cargo_tripwire.v1",
"mode": "probe_processes",
"status": "bypass_detected",
"count": 1,
"detectedLocalBuilds": [{
"policyStatus": "local_cargo_disallowed",
"commandKind": "cargo",
"command": "cargo check --lib",
}],
}));
assert_eq!(records.len(), 1);
assert_eq!(records[0].status, EvidenceRecordStatus::Fail);
assert!(
records[0].summary.contains("local_disallowed_attempt"),
"active bypass evidence must carry the local-disallowed state"
);
let report = build_completion_audit_report(
&extract_completion_checklist(
"objective",
"Run all cargo builds and tests through RCH.",
),
&EvidenceBundle { records },
);
assert_eq!(
report.local_build_policy.state,
LocalBuildPolicyState::LocalDisallowedAttempt
);
}
#[test]
fn completion_report_treats_ok_process_scan_as_clean_policy_evidence() {
let records = local_cargo_process_scan_evidence(&serde_json::json!({
"schema": "ee.rch_local_cargo_tripwire.v1",
"mode": "probe_processes",
"status": "ok",
"count": 0,
"detectedLocalBuilds": [],
}));
assert_eq!(records.len(), 1);
assert_eq!(records[0].status, EvidenceRecordStatus::Pass);
assert!(
records[0].summary.contains("remote_required_ready"),
"clean process-scan evidence must preserve the ready policy state"
);
}
#[test]
fn completion_report_treats_informational_process_rows_as_clean_policy_evidence() {
let records = local_cargo_process_scan_evidence(&serde_json::json!({
"schema": "ee.rch_local_cargo_tripwire.v1",
"mode": "probe_processes",
"status": "ok",
"count": 0,
"detectedLocalBuilds": [
{"policyStatus": "editor_tooling_informational"},
{"policyStatus": "unkillable_stale_informational"}
],
}));
assert_eq!(records.len(), 1);
assert_eq!(records[0].status, EvidenceRecordStatus::Pass);
assert!(records[0].summary.contains("remote_required_ready"));
}
#[test]
fn completion_report_blocks_unknown_informational_lookalike() {
let records = local_cargo_process_scan_evidence(&serde_json::json!({
"schema": "ee.rch_local_cargo_tripwire.v1",
"mode": "probe_processes",
"status": "ok",
"count": 0,
"detectedLocalBuilds": [{"policyStatus": "unknown_informational"}],
}));
assert_eq!(records.len(), 1);
assert_eq!(records[0].status, EvidenceRecordStatus::Fail);
assert!(records[0].summary.contains("local_disallowed_attempt"));
}
#[test]
fn completion_report_reports_remote_required_blocked_without_local_bypass() {
let checklist = extract_completion_checklist(
"objective",
"Run all cargo builds and tests through RCH.",
);
let bundle = EvidenceBundle {
records: vec![record(
"remote_rch",
"cargo/build/test command",
"rch topology blocked before compile",
EvidenceRecordStatus::CapacityBlocked,
"direct",
)],
};
let report = build_completion_audit_report(&checklist, &bundle);
assert_eq!(
report.local_build_policy.state,
LocalBuildPolicyState::RemoteRequiredBlocked
);
assert_eq!(report.completion_verdict, CompletionVerdict::Blocked);
assert!(
!report.residual_risks.iter().any(|risk| {
risk.kind == "verifier_inconclusive"
&& risk.reason == "rerun_or_record_remote_verifier_result"
}),
"remote capacity/topology blocks should not be reported as a local bypass"
);
}
#[test]
fn completion_report_reports_remote_verified_policy_state() {
let checklist = extract_completion_checklist(
"objective",
"Run all cargo builds and tests through RCH.",
);
let bundle = EvidenceBundle {
records: vec![
record(
"rch",
"remote build metadata",
"rch job 162 on worker-b",
EvidenceRecordStatus::Pass,
"direct",
),
record(
"remote_rch",
"cargo/build/test command",
"cargo test --lib completion_audit",
EvidenceRecordStatus::Pass,
"direct",
),
],
};
let report = build_completion_audit_report(&checklist, &bundle);
assert_eq!(
report.local_build_policy.state,
LocalBuildPolicyState::RemoteVerified
);
assert!(report.local_build_policy.repair_actions.is_empty());
assert_eq!(report.completion_verdict, CompletionVerdict::Complete);
}
#[test]
fn workspace_completion_audit_includes_local_build_policy_probe() {
let report = build_completion_audit_report_for_workspace(
"objective",
"Run all cargo builds and tests through RCH.",
Path::new(env!("CARGO_MANIFEST_DIR")),
None,
);
assert!(
matches!(
report.local_build_policy.state,
LocalBuildPolicyState::RemoteRequiredReady
| LocalBuildPolicyState::RemoteRequiredBlocked
| LocalBuildPolicyState::LocalDisallowedAttempt
),
"workspace audit should include a concrete local build policy state"
);
assert!(
report
.local_build_policy
.evidence_records
.iter()
.any(|record| record.kind == "local_cargo_tripwire"),
"workspace audit should surface the read-only preflight tripwire probe"
);
}
#[test]
fn local_build_policy_uses_repository_tripwire_not_ee_preflight() {
let records = local_cargo_policy_workspace_evidence(Path::new(env!("CARGO_MANIFEST_DIR")));
assert!(records.iter().any(|record| {
record.source == "scripts/check-local-cargo-tripwire.sh"
&& record
.summary
.contains("ee command-risk memory is advisory only")
}));
}
#[test]
fn evidence_adapter_treats_static_only_check_as_weak_proxy() {
let checklist = extract_completion_checklist(
"objective",
"Run all cargo builds and tests through RCH.",
);
let requirement = requirement_with_summary(&checklist, "RCH only");
let bundle = EvidenceBundle {
records: vec![record(
"remote_rch",
"cargo/build/test command",
"git diff --check only",
EvidenceRecordStatus::StaticOnly,
"weak",
)],
};
let evidence = evaluate_completion_evidence(&checklist, &bundle);
let item = evidence_for_requirement(&evidence, &requirement.id);
assert_eq!(item.support, RequirementSupport::Weak);
assert_eq!(item.confidence, "weak");
}
#[test]
fn evidence_adapter_flags_stale_beads_claim() {
let checklist = extract_completion_checklist("objective", "Track progress via Beads.");
let requirement = requirement_with_summary(&checklist, "Beads");
let bundle = EvidenceBundle {
records: vec![record(
"beads",
".beads/issues.jsonl",
"bd-abc123 updated yesterday",
EvidenceRecordStatus::Stale,
"direct",
)],
};
let evidence = evaluate_completion_evidence(&checklist, &bundle);
let item = evidence_for_requirement(&evidence, &requirement.id);
assert_eq!(item.support, RequirementSupport::Stale);
assert_eq!(item.confidence, "stale");
}
#[test]
fn evidence_adapter_reports_missing_requirement_evidence() {
let checklist = extract_completion_checklist("objective", "Coordinate through Agent Mail.");
let requirement = requirement_with_summary(&checklist, "MCP Agent Mail");
let evidence = evaluate_completion_evidence(&checklist, &EvidenceBundle::default());
let item = evidence_for_requirement(&evidence, &requirement.id);
assert_eq!(item.support, RequirementSupport::Missing);
assert!(!item.missing_expectations.is_empty());
}
#[test]
fn evidence_adapter_does_not_let_optional_missing_evidence_mask_direct_proof() {
let checklist = CompletionChecklist {
schema: COMPLETION_AUDIT_CHECKLIST_SCHEMA_V1.to_owned(),
source: ChecklistSource {
label: "objective".to_owned(),
kind: "objective_text".to_owned(),
},
objective_hash: objective_hash("manual optional evidence check"),
objective_text: "manual optional evidence check".to_owned(),
requirements: vec![CompletionRequirement {
id: "req_001".to_owned(),
kind: RequirementKind::Verification,
summary: "Verify required proof while optional context is absent".to_owned(),
evidence_expectations: vec![evidence("rch", "remote build metadata", "direct")],
verification_expectations: vec![verification(
"owner_context",
"nice-to-have operator note",
false,
)],
source_spans: vec![SourceSpan {
label: "objective".to_owned(),
start: 0,
end: 5,
text: "check".to_owned(),
}],
}],
unknown_clauses: Vec::new(),
summary: ChecklistSummary {
requirement_count: 1,
unknown_count: 0,
has_unknowns: false,
source_bytes: "manual optional evidence check".len(),
},
};
let bundle = EvidenceBundle {
records: vec![record(
"rch",
"remote build metadata",
"rch job 162 on worker-b",
EvidenceRecordStatus::Pass,
"direct",
)],
};
let evidence = evaluate_completion_evidence(&checklist, &bundle);
let item = evidence_for_requirement(&evidence, "req_001");
assert_eq!(item.support, RequirementSupport::Direct);
assert_eq!(item.missing_expectations.len(), 1);
assert!(!item.missing_expectations[0].required);
}
#[test]
fn evidence_adapter_prioritizes_contradictory_failures() {
let checklist = extract_completion_checklist("objective", "Coordinate through Agent Mail.");
let requirement = requirement_with_summary(&checklist, "MCP Agent Mail");
let bundle = EvidenceBundle {
records: vec![record(
"agent_mail",
"project inbox/outbox",
"mail service unavailable",
EvidenceRecordStatus::Fail,
"direct",
)],
};
let evidence = evaluate_completion_evidence(&checklist, &bundle);
let item = evidence_for_requirement(&evidence, &requirement.id);
assert_eq!(item.support, RequirementSupport::Contradicted);
assert_eq!(item.confidence, "contradicted");
}
#[test]
fn claim_detector_flags_stale_acceptance_text() {
let bundle = EvidenceBundle {
records: vec![record(
"acceptance",
"bd-mcp manifest acceptance",
"bead description expects default-build error exit",
EvidenceRecordStatus::Stale,
"direct",
)],
};
let contradictions = detect_claim_contradictions(&bundle);
assert_eq!(contradictions.len(), 1);
assert_eq!(
contradictions[0].kind,
ClaimContradictionKind::StaleAcceptance
);
assert!(contradictions[0].blocks_closure);
}
#[test]
fn claim_detector_flags_unsupported_duplicate_claim() {
let bundle = EvidenceBundle {
records: vec![record(
"duplicate_claim",
"orphan plan bead",
"diff audit found unique content",
EvidenceRecordStatus::Fail,
"direct",
)],
};
let contradictions = detect_claim_contradictions(&bundle);
assert_eq!(contradictions.len(), 1);
assert_eq!(
contradictions[0].kind,
ClaimContradictionKind::UnsupportedDuplicateClaim
);
assert_eq!(
contradictions[0].suggested_tracker_action,
"replace_duplicate_claim_with_diff_evidence"
);
}
#[test]
fn claim_detector_ignores_vanished_verifier_when_direct_remote_pass_exists() {
let bundle = EvidenceBundle {
records: vec![
record(
"remote_rch",
"cargo test --lib completion_audit",
"old queued job vanished",
EvidenceRecordStatus::Inconclusive,
"direct",
),
record(
"remote_rch",
"cargo test --lib completion_audit",
"rch job 162 on worker-b",
EvidenceRecordStatus::Pass,
"direct",
),
],
};
let contradictions = detect_claim_contradictions(&bundle);
assert!(contradictions.is_empty());
}
#[test]
fn claim_detector_flags_verifier_inconclusive_without_remote_pass() {
let bundle = EvidenceBundle {
records: vec![record(
"remote_rch",
"cargo test --test smoke mcp_manifest_json_real_binary_smoke",
"queued job disappeared before terminal status",
EvidenceRecordStatus::Inconclusive,
"direct",
)],
};
let contradictions = detect_claim_contradictions(&bundle);
assert_eq!(contradictions.len(), 1);
assert_eq!(
contradictions[0].kind,
ClaimContradictionKind::VerifierInconclusive
);
}
#[test]
fn claim_detector_flags_docs_source_conflict() {
let bundle = EvidenceBundle {
records: vec![record(
"source_docs",
"README command table",
"docs list command absent from source enum",
EvidenceRecordStatus::Fail,
"direct",
)],
};
let contradictions = detect_claim_contradictions(&bundle);
assert_eq!(contradictions.len(), 1);
assert_eq!(
contradictions[0].kind,
ClaimContradictionKind::SourceDocsConflict
);
}
#[test]
fn claim_detector_separates_permission_gate_from_owner_decision() {
let bundle = EvidenceBundle {
records: vec![
record(
"permission_record",
"file deletion authorization",
"no user authorization text found",
EvidenceRecordStatus::Missing,
"direct",
),
record(
"scope_decision",
"mcp parity public command",
"scope decision unresolved",
EvidenceRecordStatus::Inconclusive,
"supporting",
),
],
};
let contradictions = detect_claim_contradictions(&bundle);
let kinds = contradictions
.iter()
.map(|contradiction| contradiction.kind)
.collect::<Vec<_>>();
assert_eq!(contradictions.len(), 2);
assert!(kinds.contains(&ClaimContradictionKind::PermissionGateUnresolved));
assert!(kinds.contains(&ClaimContradictionKind::NeedsOwnerDecision));
}
#[test]
fn completion_report_returns_complete_when_all_requirements_have_direct_evidence() {
let checklist = extract_completion_checklist(
"objective",
"Run all cargo builds and tests through RCH.",
);
let bundle = EvidenceBundle {
records: vec![
record(
"rch",
"remote build metadata",
"rch job 162 on worker-b",
EvidenceRecordStatus::Pass,
"direct",
),
record(
"remote_rch",
"cargo/build/test command",
"cargo test --lib completion_audit",
EvidenceRecordStatus::Pass,
"direct",
),
],
};
let report = build_completion_audit_report(&checklist, &bundle);
assert_eq!(report.schema, COMPLETION_AUDIT_REPORT_SCHEMA_V2);
assert_eq!(report.completion_verdict, CompletionVerdict::Complete);
assert!(report.gaps.is_empty());
assert!(report.residual_risks.is_empty());
}
#[test]
fn completion_report_returns_incomplete_when_required_evidence_is_missing() {
let checklist = extract_completion_checklist(
"objective",
"Read AGENTS.md and run all cargo builds and tests through RCH.",
);
let bundle = EvidenceBundle {
records: vec![
record(
"rch",
"remote build metadata",
"rch job 162 on worker-b",
EvidenceRecordStatus::Pass,
"direct",
),
record(
"remote_rch",
"cargo/build/test command",
"cargo test --lib completion_audit",
EvidenceRecordStatus::Pass,
"direct",
),
],
};
let report = build_completion_audit_report(&checklist, &bundle);
assert_eq!(report.completion_verdict, CompletionVerdict::Incomplete);
assert!(
report
.gaps
.iter()
.any(|gap| gap.reason == "required_evidence_missing")
);
assert!(
report
.recommended_next_actions
.iter()
.any(|action| action.starts_with("collect_direct_evidence_for:req_"))
);
}
#[test]
fn completion_report_returns_blocked_for_fail_closed_rch_capacity() {
let checklist = extract_completion_checklist(
"objective",
"Run all cargo builds and tests through RCH.",
);
let bundle = EvidenceBundle {
records: vec![record(
"remote_rch",
"cargo/build/test command",
"all workers at capacity",
EvidenceRecordStatus::CapacityBlocked,
"direct",
)],
};
let report = build_completion_audit_report(&checklist, &bundle);
assert_eq!(report.completion_verdict, CompletionVerdict::Blocked);
assert!(
report
.gaps
.iter()
.any(|gap| gap.support == RequirementSupport::Blocked)
);
}
#[test]
fn completion_report_returns_unknown_for_unresolved_broad_objective() {
let checklist = extract_completion_checklist(
"objective",
"Use code investigation to understand the technical architecture and take it to the next level.",
);
let bundle = EvidenceBundle {
records: vec![
record(
"code_inspection",
"repository",
"architecture audit",
EvidenceRecordStatus::Pass,
"direct",
),
record(
"read_only_architecture_audit",
"repository",
"architecture audit",
EvidenceRecordStatus::Pass,
"direct",
),
],
};
let report = build_completion_audit_report(&checklist, &bundle);
assert_eq!(report.completion_verdict, CompletionVerdict::Unknown);
assert!(
report
.residual_risks
.iter()
.any(|risk| risk.kind == "unknown_clause")
);
}
#[test]
fn completion_report_rejects_passing_test_as_unrelated_proxy() {
let checklist = extract_completion_checklist(
"objective",
"Read README.md, coordinate through Agent Mail, and run all cargo builds and tests through RCH.",
);
let bundle = EvidenceBundle {
records: vec![
record(
"rch",
"remote build metadata",
"rch job 162 on worker-b",
EvidenceRecordStatus::Pass,
"direct",
),
record(
"remote_rch",
"cargo/build/test command",
"cargo test --lib completion_audit",
EvidenceRecordStatus::Pass,
"direct",
),
],
};
let report = build_completion_audit_report(&checklist, &bundle);
assert_eq!(report.completion_verdict, CompletionVerdict::Incomplete);
assert!(report.gaps.len() >= 2);
assert!(
report
.gaps
.iter()
.any(|gap| gap.support == RequirementSupport::Missing)
);
}
#[test]
fn workspace_completion_audit_is_conservative_without_direct_evidence() {
let report = build_completion_audit_report_for_workspace(
"objective",
"Read README.md, coordinate through Agent Mail, run all cargo builds and tests through RCH, and perform a completion audit.",
Path::new(env!("CARGO_MANIFEST_DIR")),
None,
);
assert_eq!(report.schema, COMPLETION_AUDIT_REPORT_SCHEMA_V2);
assert_eq!(report.completion_verdict, CompletionVerdict::Incomplete);
assert!(
report
.evidence_by_requirement
.iter()
.any(|item| item.support == RequirementSupport::Weak)
);
assert!(
report
.evidence_by_requirement
.iter()
.any(|item| item.support == RequirementSupport::Direct)
);
}
#[cfg(unix)]
#[test]
fn static_workspace_evidence_ignores_symlinked_file_read_proxy() {
use std::os::unix::fs::symlink;
let tempdir = tempfile::tempdir().expect("tempdir");
let outside = tempdir.path().join("outside-readme.md");
std::fs::write(&outside, "outside").expect("outside");
symlink(&outside, tempdir.path().join("README.md")).expect("symlink readme");
let mut records = Vec::new();
push_static_workspace_evidence(
&mut records,
tempdir.path(),
&evidence("file_read", "README.md", "direct"),
);
assert!(
records.is_empty(),
"symlinked workspace file must not count as static read evidence: {records:?}"
);
}
#[test]
fn static_workspace_evidence_records_regular_file_read_proxy() {
let tempdir = tempfile::tempdir().expect("tempdir");
std::fs::write(tempdir.path().join("README.md"), "readme").expect("readme");
let mut records = Vec::new();
push_static_workspace_evidence(
&mut records,
tempdir.path(),
&evidence("file_read", "README.md", "direct"),
);
assert_eq!(records.len(), 1);
assert_eq!(records[0].kind, "file_read");
assert_eq!(records[0].status, EvidenceRecordStatus::StaticOnly);
}
#[test]
fn static_workspace_evidence_rejects_absolute_path_proxy() {
let tempdir = tempfile::tempdir().expect("tempdir");
let workspace = tempdir.path().join("workspace");
std::fs::create_dir_all(&workspace).expect("workspace");
let outside = tempdir.path().join("outside.md");
std::fs::write(&outside, "outside").expect("outside");
let mut records = Vec::new();
push_static_workspace_evidence(
&mut records,
&workspace,
&evidence("file_or_path", &outside.display().to_string(), "direct"),
);
assert!(
records.is_empty(),
"absolute paths outside the workspace must not count as static evidence: {records:?}"
);
}
#[test]
fn static_workspace_evidence_rejects_parent_traversal_proxy() {
let tempdir = tempfile::tempdir().expect("tempdir");
let workspace = tempdir.path().join("workspace");
std::fs::create_dir_all(&workspace).expect("workspace");
std::fs::write(tempdir.path().join("outside.md"), "outside").expect("outside");
let mut records = Vec::new();
push_static_workspace_evidence(
&mut records,
&workspace,
&evidence("file_or_path", "../outside.md", "direct"),
);
assert!(
records.is_empty(),
"parent traversal must not escape workspace static evidence: {records:?}"
);
}
#[test]
fn explicit_direct_evidence_overrides_static_only_workspace_proxy() {
let tempdir = tempfile::tempdir().expect("tempdir");
std::fs::write(tempdir.path().join("README.md"), "readme").expect("readme");
let checklist = extract_completion_checklist("objective", "Read README.md.");
let mut records = Vec::new();
push_static_workspace_evidence(
&mut records,
tempdir.path(),
&evidence("file_read", "README.md", "direct"),
);
assert_eq!(records.len(), 1);
assert_eq!(records[0].status, EvidenceRecordStatus::StaticOnly);
records.extend([
record(
"file_read",
"README.md",
"session transcript",
EvidenceRecordStatus::Pass,
"direct",
),
record(
"prompt_requirement",
"README.md",
"session transcript",
EvidenceRecordStatus::Pass,
"direct",
),
]);
let report = build_completion_audit_report(&checklist, &EvidenceBundle { records });
assert_eq!(report.completion_verdict, CompletionVerdict::Complete);
assert!(report.gaps.is_empty());
}
}