use super::clean_overlay_planner::{CleanOverlayManifest, ExcludedPath, ExclusionReason};
use serde::Serialize;
pub const DEFAULT_TARGET_DIR: &str = "/data/tmp/rch_target_asupersync_test";
pub const REQUIRED_CLEAN_OVERLAY_FLAGS: [&str; 4] = [
"--base",
"--clean-overlay",
"--overlay-path",
"--no-overlay",
];
pub const CLEAN_OVERLAY_CAPABILITY_BLOCKER: &str =
"# BLOCKED: installed RCH clean-overlay capability unsupported; no proof command emitted";
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct CleanOverlayCapability {
capability_probe_version: String,
clean_overlay_supported: bool,
missing_flags: Vec<String>,
capability_findings: Vec<String>,
}
impl CleanOverlayCapability {
#[must_use]
pub fn from_rch_exec_help(
capability_probe_version: impl Into<String>,
help_text: &str,
) -> Self {
let capability_probe_version = capability_probe_version.into().trim().to_string();
let missing_flags = sorted_unique(
REQUIRED_CLEAN_OVERLAY_FLAGS
.iter()
.filter(|flag| !help_declares_flag(help_text, flag))
.map(|flag| (*flag).to_string())
.collect::<Vec<_>>(),
);
let mut capability_findings = Vec::new();
if missing_flags.is_empty() {
capability_findings.push(
"installed rch exec help exposes all required clean-overlay flags".to_string(),
);
} else {
capability_findings.push(format!(
"installed rch exec help lacks required clean-overlay flags: {}",
missing_flags.join(",")
));
}
if capability_probe_version.is_empty() {
capability_findings.push("installed capability probe version is missing".to_string());
}
let clean_overlay_supported =
missing_flags.is_empty() && !capability_probe_version.is_empty();
Self {
capability_probe_version,
clean_overlay_supported,
missing_flags,
capability_findings: sorted_unique(capability_findings),
}
}
#[must_use]
pub fn supports_required_flags(&self) -> bool {
self.clean_overlay_supported
&& self.missing_flags.is_empty()
&& !self.capability_probe_version.trim().is_empty()
}
#[must_use]
pub fn capability_probe_version(&self) -> &str {
&self.capability_probe_version
}
#[must_use]
pub fn clean_overlay_supported(&self) -> bool {
self.supports_required_flags()
}
#[must_use]
pub fn missing_flags(&self) -> &[String] {
&self.missing_flags
}
#[must_use]
pub fn capability_findings(&self) -> &[String] {
&self.capability_findings
}
}
const FORBIDDEN_TOKENS: &[&str] = &[
"git branch",
"git worktree",
"worktree add",
"git clone",
"git clean",
"git reset",
"git checkout -b",
"rm -rf",
"rm -r ",
"rm -f ",
];
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct OverlayProofCommand {
head_commit: String,
target_dir: String,
validation_intent: String,
selected_paths: Vec<String>,
overlay_paths: Vec<String>,
excluded_paths: Vec<ExcludedPath>,
reservation_evidence: Vec<String>,
capability_probe_version: String,
clean_overlay_supported: bool,
missing_flags: Vec<String>,
capability_findings: Vec<String>,
admitted: bool,
report_only: bool,
fail_closed_path_count: usize,
no_claim_boundaries: Vec<String>,
}
impl OverlayProofCommand {
#[must_use]
pub fn from_manifest(
manifest: &CleanOverlayManifest,
capability: &CleanOverlayCapability,
) -> Self {
Self::from_manifest_with_target(manifest, DEFAULT_TARGET_DIR, capability)
}
#[must_use]
pub fn from_manifest_with_target(
manifest: &CleanOverlayManifest,
target_dir: &str,
capability: &CleanOverlayCapability,
) -> Self {
let fail_closed_path_count = manifest
.excluded_paths
.iter()
.filter(|excluded| is_fail_closed(excluded.reason))
.count();
let admitted = !manifest.blocked
&& !manifest.report_only
&& !manifest.selected_paths.is_empty()
&& capability.supports_required_flags();
let mut no_claim_boundaries = manifest.no_claim_boundaries.clone();
no_claim_boundaries.push(
"no claim that peer dirt was excluded unless installed RCH clean-overlay capability evidence is supported and an admitted command completed with terminal execution evidence"
.to_string(),
);
Self {
head_commit: manifest.head_commit.clone(),
target_dir: target_dir.to_string(),
validation_intent: manifest.command_intent.clone(),
selected_paths: manifest.selected_paths.clone(),
overlay_paths: manifest.included_paths.clone(),
excluded_paths: manifest.excluded_paths.clone(),
reservation_evidence: manifest.reservation_evidence.clone(),
capability_probe_version: capability.capability_probe_version().to_string(),
clean_overlay_supported: capability.supports_required_flags(),
missing_flags: capability.missing_flags().to_vec(),
capability_findings: capability.capability_findings().to_vec(),
admitted,
report_only: manifest.report_only,
fail_closed_path_count,
no_claim_boundaries,
}
}
#[must_use]
pub fn overlay_path_flags(&self) -> String {
self.overlay_paths
.iter()
.map(|path| format!("--overlay-path {path}"))
.collect::<Vec<_>>()
.join(" ")
}
#[must_use]
pub fn rendered_command(&self) -> String {
if !self.clean_overlay_supported {
return CLEAN_OVERLAY_CAPABILITY_BLOCKER.to_string();
}
if self.report_only {
return format!(
"# REPORT-ONLY: clean-overlay dry run; no RCH proof command emitted ({} path(s) would fail closed)",
self.fail_closed_path_count
);
}
if !self.admitted {
return format!(
"# BLOCKED: clean-overlay refused; no RCH proof command emitted ({} fail-closed path(s))",
self.fail_closed_path_count
);
}
let flags = self.overlay_path_flags();
let base = format!(
"RCH_REQUIRE_REMOTE=1 rch exec --base {} --clean-overlay",
self.head_commit
);
let scope = if flags.is_empty() {
"--no-overlay".to_string()
} else {
flags
};
format!(
"{base} {scope} -- env CARGO_TARGET_DIR={} {}",
self.target_dir, self.validation_intent
)
}
#[must_use]
pub fn reproduction_command(&self) -> String {
self.rendered_command()
}
#[must_use]
pub fn admitted(&self) -> bool {
self.admitted
}
#[must_use]
pub fn report_only(&self) -> bool {
self.report_only
}
#[must_use]
pub fn clean_overlay_supported(&self) -> bool {
self.clean_overlay_supported
}
#[must_use]
pub fn selected_paths(&self) -> &[String] {
&self.selected_paths
}
#[must_use]
pub fn overlay_paths(&self) -> &[String] {
&self.overlay_paths
}
#[must_use]
pub fn excluded_paths(&self) -> &[ExcludedPath] {
&self.excluded_paths
}
#[must_use]
pub fn reservation_evidence(&self) -> &[String] {
&self.reservation_evidence
}
#[must_use]
pub fn missing_flags(&self) -> &[String] {
&self.missing_flags
}
#[must_use]
pub fn capability_findings(&self) -> &[String] {
&self.capability_findings
}
#[must_use]
pub fn no_claim_boundaries(&self) -> &[String] {
&self.no_claim_boundaries
}
#[must_use]
pub fn forbidden_operations(&self) -> Vec<&'static str> {
let surface = format!(
"{}\n{}",
self.rendered_command(),
self.reproduction_command()
);
FORBIDDEN_TOKENS
.iter()
.copied()
.filter(|token| surface.contains(token))
.collect()
}
#[must_use]
pub fn uses_local_cargo_fallback(&self) -> bool {
let surface = format!(
"{}\n{}",
self.rendered_command(),
self.reproduction_command()
);
surface.contains("|| cargo")
|| surface.contains("; cargo")
|| surface.contains("locally")
|| surface.contains("local fallback")
|| (surface.contains("cargo") && !surface.contains("rch exec"))
}
#[must_use]
pub fn has_exclusion(&self, reason: ExclusionReason) -> bool {
self.excluded_paths
.iter()
.any(|excluded| excluded.reason == reason)
}
#[must_use]
pub fn render_report(&self) -> String {
let mut out = String::new();
out.push_str("## Clean-overlay focused proof — ");
out.push_str(if !self.clean_overlay_supported {
"blocked (capability drift)"
} else if self.report_only {
"report-only"
} else if self.admitted {
"admitted"
} else {
"blocked (fail-closed)"
});
out.push_str("\n\n- HEAD: `");
out.push_str(&self.head_commit);
out.push_str("`\n- Lane: RCH-only; no local Cargo fallback\n- Validation intent: `");
out.push_str(&self.validation_intent);
out.push_str("`\n- Capability probe: `");
out.push_str(&self.capability_probe_version);
out.push_str("`\n- Clean-overlay capability supported: `");
out.push_str(if self.clean_overlay_supported {
"true"
} else {
"false"
});
out.push_str("`\n- Exact RCH command:\n```sh\n");
out.push_str(&self.rendered_command());
out.push_str("\n```\n- Reproduction command:\n```sh\n");
out.push_str(&self.reproduction_command());
out.push_str("\n```\n\n");
push_path_list(&mut out, "Selected paths", &self.selected_paths);
push_path_list(&mut out, "Overlay (included) paths", &self.overlay_paths);
out.push_str(&format!(
"### Excluded paths ({})\n",
self.excluded_paths.len()
));
if self.excluded_paths.is_empty() {
out.push_str("- _none_\n");
} else {
for excluded in &self.excluded_paths {
out.push_str("- `");
out.push_str(&excluded.path);
out.push_str("` — ");
out.push_str(exclusion_label(excluded.reason));
out.push('\n');
}
}
out.push('\n');
push_path_list(&mut out, "Reservation evidence", &self.reservation_evidence);
push_path_list(&mut out, "Missing capability flags", &self.missing_flags);
push_path_list(&mut out, "Capability findings", &self.capability_findings);
out.push_str("### No-claim boundaries\n");
if self.no_claim_boundaries.is_empty() {
out.push_str("- _none recorded_\n");
} else {
for boundary in &self.no_claim_boundaries {
out.push_str("- ");
out.push_str(boundary);
out.push('\n');
}
}
out
}
}
fn help_declares_flag(help_text: &str, flag: &str) -> bool {
let mut in_options = false;
for line in help_text.lines() {
let declaration = line.trim();
if declaration.eq_ignore_ascii_case("options:") {
in_options = true;
continue;
}
if !in_options {
continue;
}
if !declaration.is_empty() && !declaration.starts_with('-') && declaration.ends_with(':') {
in_options = false;
continue;
}
if !declaration.starts_with('-') {
continue;
}
for token in declaration.split_whitespace() {
let token = token.trim_end_matches(',');
let token = token.split_once('=').map_or(token, |(name, _)| name);
if !token.starts_with('-') {
break;
}
if token == flag {
return true;
}
}
}
false
}
fn sorted_unique(mut values: Vec<String>) -> Vec<String> {
values.sort();
values.dedup();
values
}
const fn is_fail_closed(reason: ExclusionReason) -> bool {
matches!(
reason,
ExclusionReason::PeerDirtyUnselected
| ExclusionReason::UnreservedSelection
| ExclusionReason::DeletedSelectionRefused
)
}
fn push_path_list(out: &mut String, title: &str, paths: &[String]) {
out.push_str(&format!("### {title} ({})\n", paths.len()));
if paths.is_empty() {
out.push_str("- _none_\n");
} else {
for path in paths {
out.push_str("- `");
out.push_str(path);
out.push_str("`\n");
}
}
out.push('\n');
}
const fn exclusion_label(reason: ExclusionReason) -> &'static str {
match reason {
ExclusionReason::PeerDirtyUnselected => "peer-dirty (unselected) — excluded from overlay",
ExclusionReason::UnreservedSelection => "unreserved selection (no held lease)",
ExclusionReason::DeletedSelectionRefused => {
"deleted selection (an overlay cannot prove a removal)"
}
}
}