use super::clean_overlay_planner::{CleanOverlayManifest, ExclusionReason};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ProofAttemptOutcome {
Blocked,
Failed,
StaleProgress,
Green,
}
impl ProofAttemptOutcome {
#[must_use]
pub const fn is_proof_success(self) -> bool {
matches!(self, Self::Green)
}
#[must_use]
pub const fn is_blocked(self) -> bool {
matches!(self, Self::Blocked)
}
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::Blocked => "blocked",
Self::Failed => "failed",
Self::StaleProgress => "stale_progress",
Self::Green => "green",
}
}
#[must_use]
const fn status_sentence(self) -> &'static str {
match self {
Self::Blocked => "BLOCKED — the overlay refused to run; this attests no proof.",
Self::Failed => "FAILED — the proof command failed; not a green proof.",
Self::StaleProgress => {
"STALE-PROGRESS — RCH heartbeat fresh but build progress stalled; NOT a green proof."
}
Self::Green => "GREEN — the selected overlay proved clean (these paths only).",
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RchExecutionEvidence {
pub worker_id: Option<String>,
pub build_id: Option<String>,
}
impl RchExecutionEvidence {
#[must_use]
pub fn new(worker_id: Option<String>, build_id: Option<String>) -> Self {
Self {
worker_id,
build_id,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BlockerReceipt {
pub head_commit: String,
pub command_intent: String,
pub outcome: ProofAttemptOutcome,
pub included_paths: Vec<String>,
pub excluded_paths: Vec<super::clean_overlay_planner::ExcludedPath>,
pub reservation_evidence: Vec<String>,
pub execution: RchExecutionEvidence,
pub report_only: bool,
pub no_local_fallback: bool,
pub no_claim_boundaries: Vec<String>,
}
impl BlockerReceipt {
#[must_use]
pub fn from_manifest(
manifest: &CleanOverlayManifest,
outcome: ProofAttemptOutcome,
execution: RchExecutionEvidence,
) -> Self {
let outcome = if manifest.blocked {
ProofAttemptOutcome::Blocked
} else {
outcome
};
Self {
head_commit: manifest.head_commit.clone(),
command_intent: manifest.command_intent.clone(),
outcome,
included_paths: manifest.included_paths.clone(),
excluded_paths: manifest.excluded_paths.clone(),
reservation_evidence: manifest.reservation_evidence.clone(),
execution,
report_only: manifest.report_only,
no_local_fallback: true,
no_claim_boundaries: manifest.no_claim_boundaries.clone(),
}
}
#[must_use]
pub const fn proves_clean(&self) -> bool {
self.outcome.is_proof_success()
}
#[must_use]
pub fn peer_dirty_paths(&self) -> Vec<&str> {
self.excluded_with(ExclusionReason::PeerDirtyUnselected)
}
#[must_use]
pub fn excluded_with(&self, reason: ExclusionReason) -> Vec<&str> {
self.excluded_paths
.iter()
.filter(|excluded| excluded.reason == reason)
.map(|excluded| excluded.path.as_str())
.collect()
}
#[must_use]
pub fn render_markdown(&self) -> String {
let mut out = String::new();
out.push_str("## Clean-overlay proof receipt — ");
out.push_str(self.outcome.label());
out.push_str("\n\n");
out.push_str("- Command intent: `");
out.push_str(&self.command_intent);
out.push_str("`\n- HEAD: `");
out.push_str(&self.head_commit);
out.push_str("`\n- Mode: ");
out.push_str(if self.report_only {
"report-only (dry run)"
} else {
"enforced"
});
out.push('\n');
out.push_str("- Lane: ");
out.push_str(if self.no_local_fallback {
"RCH-only; no local Cargo fallback"
} else {
"RCH with local fallback"
});
out.push_str("\n- RCH worker: ");
out.push_str(self.execution.worker_id.as_deref().unwrap_or("n/a"));
out.push_str(" · build: ");
out.push_str(self.execution.build_id.as_deref().unwrap_or("n/a"));
out.push_str("\n- Proof status: ");
out.push_str(self.outcome.status_sentence());
out.push_str("\n\n");
push_path_section(&mut out, "Included overlay paths", &self.included_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_section(&mut out, "Reservation evidence", &self.reservation_evidence);
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 push_path_section(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)",
ExclusionReason::UnreservedSelection => "unreserved selection (no held lease)",
ExclusionReason::DeletedSelectionRefused => {
"deleted selection (an overlay cannot prove a removal)"
}
}
}