use super::proof_traffic_receipt::ProofTrafficDecision;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet};
pub const PROOF_TRAFFIC_PARKING_LOT_SCHEMA_VERSION: &str = "proof-traffic-parking-lot-v1";
const NO_CLAIM_BOUNDARIES: &[&str] = &[
"No release-readiness claim.",
"No broad workspace-health claim.",
"No runtime-correctness claim.",
"No performance-improvement claim.",
"No live RCH fleet-availability claim.",
"No local Cargo fallback approval.",
"No peer-owned build cancellation authority.",
"Parked, refused, or stale attempts are not green proof evidence.",
];
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProofTrafficRetryPredicate {
pub predicate_id: String,
pub condition: String,
pub required_signal: String,
pub satisfied: bool,
}
impl ProofTrafficRetryPredicate {
#[must_use]
pub fn new(
predicate_id: impl Into<String>,
condition: impl Into<String>,
required_signal: impl Into<String>,
satisfied: bool,
) -> Self {
Self {
predicate_id: predicate_id.into(),
condition: condition.into(),
required_signal: required_signal.into(),
satisfied,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ParkedProofAttempt {
pub attempt_id: String,
pub blocker_key: String,
pub head_commit: String,
pub command_intent: String,
pub exact_rch_command: Option<String>,
pub blocker_marker: String,
pub target_dir: String,
pub owned_paths: Vec<String>,
pub reservation_evidence: Vec<String>,
pub blocker_class: ProofTrafficDecision,
pub blocker_owner: Option<String>,
pub handoff_thread: Option<String>,
pub retry_predicate: ProofTrafficRetryPredicate,
pub no_claim_boundaries: Vec<String>,
}
impl ParkedProofAttempt {
#[must_use]
pub fn new(
attempt_id: impl Into<String>,
blocker_key: impl Into<String>,
head_commit: impl Into<String>,
command_intent: impl Into<String>,
target_dir: impl Into<String>,
blocker_class: ProofTrafficDecision,
retry_predicate: ProofTrafficRetryPredicate,
) -> Self {
Self {
attempt_id: attempt_id.into(),
blocker_key: blocker_key.into(),
head_commit: head_commit.into(),
command_intent: command_intent.into(),
exact_rch_command: None,
blocker_marker: "# PARKED: no proof command emitted".to_string(),
target_dir: target_dir.into(),
owned_paths: Vec::new(),
reservation_evidence: Vec::new(),
blocker_class,
blocker_owner: None,
handoff_thread: None,
retry_predicate,
no_claim_boundaries: NO_CLAIM_BOUNDARIES
.iter()
.map(|boundary| (*boundary).to_string())
.collect(),
}
}
#[must_use]
pub fn with_exact_rch_command(mut self, command: impl Into<String>) -> Self {
self.exact_rch_command = Some(command.into());
self
}
#[must_use]
pub fn with_blocker_marker(mut self, marker: impl Into<String>) -> Self {
self.blocker_marker = marker.into();
self
}
#[must_use]
pub fn with_paths(
mut self,
owned_paths: Vec<String>,
reservation_evidence: Vec<String>,
) -> Self {
self.owned_paths = sorted_unique(owned_paths);
self.reservation_evidence = sorted_unique(reservation_evidence);
self
}
#[must_use]
pub fn with_handoff(
mut self,
blocker_owner: Option<String>,
handoff_thread: Option<String>,
) -> Self {
self.blocker_owner = blocker_owner;
self.handoff_thread = handoff_thread;
self
}
#[must_use]
pub fn can_be_cited_as_green(&self) -> bool {
false
}
#[must_use]
pub fn status_label(&self) -> &'static str {
if self.retry_predicate.satisfied {
"retry-ready"
} else {
"parked"
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ParkedProofGroup {
pub blocker_key: String,
pub attempt_ids: Vec<String>,
pub command_intents: Vec<String>,
pub owned_paths: Vec<String>,
pub attempt_count: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProofTrafficParkingLot {
pub schema_version: String,
pub lot_id: String,
pub attempts: Vec<ParkedProofAttempt>,
pub groups: Vec<ParkedProofGroup>,
pub no_claim_boundaries: Vec<String>,
}
impl ProofTrafficParkingLot {
#[must_use]
pub fn new(lot_id: impl Into<String>, attempts: Vec<ParkedProofAttempt>) -> Self {
let mut attempts = attempts;
attempts.sort_by(|left, right| left.attempt_id.cmp(&right.attempt_id));
let groups = group_attempts(&attempts);
Self {
schema_version: PROOF_TRAFFIC_PARKING_LOT_SCHEMA_VERSION.to_string(),
lot_id: lot_id.into(),
attempts,
groups,
no_claim_boundaries: NO_CLAIM_BOUNDARIES
.iter()
.map(|boundary| (*boundary).to_string())
.collect(),
}
}
#[must_use]
pub fn render_resume(&self, attempt_id: &str) -> Option<String> {
let attempt = self
.attempts
.iter()
.find(|attempt| attempt.attempt_id == attempt_id)?;
if attempt.retry_predicate.satisfied {
if let Some(command) = &attempt.exact_rch_command {
return Some(command.clone());
}
}
Some(render_fresh_blocker(attempt))
}
#[must_use]
pub fn render_markdown(&self) -> String {
let mut out = String::new();
out.push_str("## Proof-traffic parking lot - ");
out.push_str(&self.lot_id);
out.push_str("\n\n");
out.push_str(&format!(
"- schema_version: `{}`\n- attempt_count: `{}`\n- group_count: `{}`\n\n",
self.schema_version,
self.attempts.len(),
self.groups.len()
));
out.push_str("### groups\n");
if self.groups.is_empty() {
out.push_str("- _none_\n");
} else {
for group in &self.groups {
out.push_str("- `");
out.push_str(&group.blocker_key);
out.push_str("` attempts=");
out.push_str(&group.attempt_ids.join(","));
out.push_str(" count=");
out.push_str(&group.attempt_count.to_string());
out.push('\n');
}
}
out.push('\n');
out.push_str("### attempts\n");
if self.attempts.is_empty() {
out.push_str("- _none_\n");
} else {
for attempt in &self.attempts {
out.push_str("- `");
out.push_str(&attempt.attempt_id);
out.push_str("` status=`");
out.push_str(attempt.status_label());
out.push_str("` blocker=`");
out.push_str(attempt.blocker_class.label());
out.push_str("` retry=`");
out.push_str(&attempt.retry_predicate.predicate_id);
out.push_str("`\n");
}
}
out.push('\n');
push_string_section(&mut out, "no_claim_boundaries", &self.no_claim_boundaries);
out
}
#[must_use]
pub fn agent_mail_body(&self) -> String {
let mut out = String::new();
out.push_str("proof_traffic_parking_lot:\n");
out.push_str(&format!(
"- lot_id: `{}`\n- attempt_count: `{}`\n- group_count: `{}`\n",
self.lot_id,
self.attempts.len(),
self.groups.len()
));
for group in &self.groups {
out.push_str("- blocker_key: `");
out.push_str(&group.blocker_key);
out.push_str("` attempts: `");
out.push_str(&group.attempt_ids.join(","));
out.push_str("`\n");
}
out
}
#[must_use]
pub fn br_comment_body(&self) -> String {
let mut out = String::new();
out.push_str("Proof-traffic parking lot\n\n");
out.push_str(&format!(
"- lot_id: `{}`\n- attempt_count: `{}`\n- group_count: `{}`\n",
self.lot_id,
self.attempts.len(),
self.groups.len()
));
out
}
}
fn group_attempts(attempts: &[ParkedProofAttempt]) -> Vec<ParkedProofGroup> {
let mut by_key: BTreeMap<String, Vec<&ParkedProofAttempt>> = BTreeMap::new();
for attempt in attempts {
by_key
.entry(attempt.blocker_key.clone())
.or_default()
.push(attempt);
}
by_key
.into_iter()
.map(|(blocker_key, attempts)| {
let attempt_ids = attempts
.iter()
.map(|attempt| attempt.attempt_id.clone())
.collect::<Vec<_>>();
let command_intents = attempts
.iter()
.map(|attempt| attempt.command_intent.clone())
.collect::<BTreeSet<_>>()
.into_iter()
.collect::<Vec<_>>();
let owned_paths = attempts
.iter()
.flat_map(|attempt| attempt.owned_paths.iter().cloned())
.collect::<BTreeSet<_>>()
.into_iter()
.collect::<Vec<_>>();
ParkedProofGroup {
blocker_key,
attempt_count: attempt_ids.len(),
attempt_ids,
command_intents,
owned_paths,
}
})
.collect()
}
fn render_fresh_blocker(attempt: &ParkedProofAttempt) -> String {
format!(
"# PARKED: blocker={} retry_predicate={} satisfied={} required_signal={}; no proof command emitted",
attempt.blocker_class.label(),
attempt.retry_predicate.predicate_id,
attempt.retry_predicate.satisfied,
attempt.retry_predicate.required_signal
)
}
fn push_string_section(out: &mut String, title: &str, values: &[String]) {
out.push_str(&format!("### {title} ({})\n", values.len()));
if values.is_empty() {
out.push_str("- _none_\n");
} else {
for value in values {
out.push_str("- ");
out.push_str(value);
out.push('\n');
}
}
out.push('\n');
}
fn sorted_unique(mut values: Vec<String>) -> Vec<String> {
values.sort();
values.dedup();
values
}