Skip to main content

asupersync/audit/
proof_traffic_parking_lot.rs

1//! Proof-traffic parking lot and resumable retry manifest (PROOF-TRAFFIC A4).
2//!
3//! Parked proof attempts are not green evidence. They are resumable operator
4//! packets: exact command intent, blocker classification, retry predicate,
5//! handoff context, and no-claim boundaries. This module keeps that packet
6//! deterministic and groups duplicate blockers without losing per-attempt
7//! command details.
8
9use super::proof_traffic_receipt::ProofTrafficDecision;
10use serde::{Deserialize, Serialize};
11use std::collections::{BTreeMap, BTreeSet};
12
13/// Stable schema version for proof-traffic parking lots.
14pub const PROOF_TRAFFIC_PARKING_LOT_SCHEMA_VERSION: &str = "proof-traffic-parking-lot-v1";
15
16const NO_CLAIM_BOUNDARIES: &[&str] = &[
17    "No release-readiness claim.",
18    "No broad workspace-health claim.",
19    "No runtime-correctness claim.",
20    "No performance-improvement claim.",
21    "No live RCH fleet-availability claim.",
22    "No local Cargo fallback approval.",
23    "No peer-owned build cancellation authority.",
24    "Parked, refused, or stale attempts are not green proof evidence.",
25];
26
27/// Retry predicate controlling whether a parked attempt may emit its exact
28/// command again.
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
30pub struct ProofTrafficRetryPredicate {
31    /// Stable predicate id used by reports and grouping.
32    pub predicate_id: String,
33    /// Human-readable condition that must be true before resume.
34    pub condition: String,
35    /// Fresh evidence signal expected from the next operator.
36    pub required_signal: String,
37    /// Whether the predicate is currently satisfied.
38    pub satisfied: bool,
39}
40
41impl ProofTrafficRetryPredicate {
42    /// Construct a retry predicate.
43    #[must_use]
44    pub fn new(
45        predicate_id: impl Into<String>,
46        condition: impl Into<String>,
47        required_signal: impl Into<String>,
48        satisfied: bool,
49    ) -> Self {
50        Self {
51            predicate_id: predicate_id.into(),
52            condition: condition.into(),
53            required_signal: required_signal.into(),
54            satisfied,
55        }
56    }
57}
58
59/// One parked focused-proof attempt.
60#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
61pub struct ParkedProofAttempt {
62    /// Stable attempt id.
63    pub attempt_id: String,
64    /// Duplicate-grouping key for the blocker.
65    pub blocker_key: String,
66    /// `HEAD` commit the attempt was based on.
67    pub head_commit: String,
68    /// Original command intent.
69    pub command_intent: String,
70    /// Exact RCH command, when it is safe to render after predicate satisfaction.
71    pub exact_rch_command: Option<String>,
72    /// Blocker marker emitted while parked or when no exact command is recorded.
73    pub blocker_marker: String,
74    /// `CARGO_TARGET_DIR` intended for the proof.
75    pub target_dir: String,
76    /// Owned paths selected by the attempt.
77    pub owned_paths: Vec<String>,
78    /// Reservation evidence for the owned paths.
79    pub reservation_evidence: Vec<String>,
80    /// Fail-closed blocker classification.
81    pub blocker_class: ProofTrafficDecision,
82    /// Optional blocker owner, usually another agent/build owner.
83    pub blocker_owner: Option<String>,
84    /// Optional Agent Mail or `br` handoff thread id.
85    pub handoff_thread: Option<String>,
86    /// Retry predicate gating resume output.
87    pub retry_predicate: ProofTrafficRetryPredicate,
88    /// Honest no-claim boundaries.
89    pub no_claim_boundaries: Vec<String>,
90}
91
92impl ParkedProofAttempt {
93    /// Construct a parked proof attempt.
94    #[must_use]
95    pub fn new(
96        attempt_id: impl Into<String>,
97        blocker_key: impl Into<String>,
98        head_commit: impl Into<String>,
99        command_intent: impl Into<String>,
100        target_dir: impl Into<String>,
101        blocker_class: ProofTrafficDecision,
102        retry_predicate: ProofTrafficRetryPredicate,
103    ) -> Self {
104        Self {
105            attempt_id: attempt_id.into(),
106            blocker_key: blocker_key.into(),
107            head_commit: head_commit.into(),
108            command_intent: command_intent.into(),
109            exact_rch_command: None,
110            blocker_marker: "# PARKED: no proof command emitted".to_string(),
111            target_dir: target_dir.into(),
112            owned_paths: Vec::new(),
113            reservation_evidence: Vec::new(),
114            blocker_class,
115            blocker_owner: None,
116            handoff_thread: None,
117            retry_predicate,
118            no_claim_boundaries: NO_CLAIM_BOUNDARIES
119                .iter()
120                .map(|boundary| (*boundary).to_string())
121                .collect(),
122        }
123    }
124
125    /// Attach an exact RCH command to emit only after the retry predicate is
126    /// satisfied.
127    #[must_use]
128    pub fn with_exact_rch_command(mut self, command: impl Into<String>) -> Self {
129        self.exact_rch_command = Some(command.into());
130        self
131    }
132
133    /// Attach a blocker marker for parked/unsatisfied states.
134    #[must_use]
135    pub fn with_blocker_marker(mut self, marker: impl Into<String>) -> Self {
136        self.blocker_marker = marker.into();
137        self
138    }
139
140    /// Attach owned paths and reservation evidence.
141    #[must_use]
142    pub fn with_paths(
143        mut self,
144        owned_paths: Vec<String>,
145        reservation_evidence: Vec<String>,
146    ) -> Self {
147        self.owned_paths = sorted_unique(owned_paths);
148        self.reservation_evidence = sorted_unique(reservation_evidence);
149        self
150    }
151
152    /// Attach blocker owner and handoff thread context.
153    #[must_use]
154    pub fn with_handoff(
155        mut self,
156        blocker_owner: Option<String>,
157        handoff_thread: Option<String>,
158    ) -> Self {
159        self.blocker_owner = blocker_owner;
160        self.handoff_thread = handoff_thread;
161        self
162    }
163
164    /// Parked attempts are never green proof evidence.
165    #[must_use]
166    pub fn can_be_cited_as_green(&self) -> bool {
167        false
168    }
169
170    /// Stable status label for report rendering.
171    #[must_use]
172    pub fn status_label(&self) -> &'static str {
173        if self.retry_predicate.satisfied {
174            "retry-ready"
175        } else {
176            "parked"
177        }
178    }
179}
180
181/// Duplicate group for attempts sharing the same blocker key.
182#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
183pub struct ParkedProofGroup {
184    /// Shared blocker key.
185    pub blocker_key: String,
186    /// Attempt ids in this group.
187    pub attempt_ids: Vec<String>,
188    /// Distinct command intents represented in this group.
189    pub command_intents: Vec<String>,
190    /// Distinct owned paths represented in this group.
191    pub owned_paths: Vec<String>,
192    /// Number of attempts in this group.
193    pub attempt_count: usize,
194}
195
196/// Deterministic parking lot manifest.
197#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
198pub struct ProofTrafficParkingLot {
199    /// Stable schema version.
200    pub schema_version: String,
201    /// Stable parking lot id.
202    pub lot_id: String,
203    /// Parked proof attempts, sorted by attempt id.
204    pub attempts: Vec<ParkedProofAttempt>,
205    /// Duplicate groups, sorted by blocker key.
206    pub groups: Vec<ParkedProofGroup>,
207    /// Honest no-claim boundaries.
208    pub no_claim_boundaries: Vec<String>,
209}
210
211impl ProofTrafficParkingLot {
212    /// Build a deterministic parking lot from parked attempts.
213    #[must_use]
214    pub fn new(lot_id: impl Into<String>, attempts: Vec<ParkedProofAttempt>) -> Self {
215        let mut attempts = attempts;
216        attempts.sort_by(|left, right| left.attempt_id.cmp(&right.attempt_id));
217        let groups = group_attempts(&attempts);
218        Self {
219            schema_version: PROOF_TRAFFIC_PARKING_LOT_SCHEMA_VERSION.to_string(),
220            lot_id: lot_id.into(),
221            attempts,
222            groups,
223            no_claim_boundaries: NO_CLAIM_BOUNDARIES
224                .iter()
225                .map(|boundary| (*boundary).to_string())
226                .collect(),
227        }
228    }
229
230    /// Render a resume command for an attempt.
231    ///
232    /// The exact command is emitted only when the retry predicate is satisfied
233    /// and an exact RCH command was recorded. Otherwise this returns a fresh
234    /// blocker marker that cannot be mistaken for proof evidence.
235    #[must_use]
236    pub fn render_resume(&self, attempt_id: &str) -> Option<String> {
237        let attempt = self
238            .attempts
239            .iter()
240            .find(|attempt| attempt.attempt_id == attempt_id)?;
241        if attempt.retry_predicate.satisfied {
242            if let Some(command) = &attempt.exact_rch_command {
243                return Some(command.clone());
244            }
245        }
246        Some(render_fresh_blocker(attempt))
247    }
248
249    /// Render deterministic Markdown for the whole parking lot.
250    #[must_use]
251    pub fn render_markdown(&self) -> String {
252        let mut out = String::new();
253        out.push_str("## Proof-traffic parking lot - ");
254        out.push_str(&self.lot_id);
255        out.push_str("\n\n");
256        out.push_str(&format!(
257            "- schema_version: `{}`\n- attempt_count: `{}`\n- group_count: `{}`\n\n",
258            self.schema_version,
259            self.attempts.len(),
260            self.groups.len()
261        ));
262
263        out.push_str("### groups\n");
264        if self.groups.is_empty() {
265            out.push_str("- _none_\n");
266        } else {
267            for group in &self.groups {
268                out.push_str("- `");
269                out.push_str(&group.blocker_key);
270                out.push_str("` attempts=");
271                out.push_str(&group.attempt_ids.join(","));
272                out.push_str(" count=");
273                out.push_str(&group.attempt_count.to_string());
274                out.push('\n');
275            }
276        }
277        out.push('\n');
278
279        out.push_str("### attempts\n");
280        if self.attempts.is_empty() {
281            out.push_str("- _none_\n");
282        } else {
283            for attempt in &self.attempts {
284                out.push_str("- `");
285                out.push_str(&attempt.attempt_id);
286                out.push_str("` status=`");
287                out.push_str(attempt.status_label());
288                out.push_str("` blocker=`");
289                out.push_str(attempt.blocker_class.label());
290                out.push_str("` retry=`");
291                out.push_str(&attempt.retry_predicate.predicate_id);
292                out.push_str("`\n");
293            }
294        }
295        out.push('\n');
296
297        push_string_section(&mut out, "no_claim_boundaries", &self.no_claim_boundaries);
298        out
299    }
300
301    /// Render Agent Mail body with structured fields.
302    #[must_use]
303    pub fn agent_mail_body(&self) -> String {
304        let mut out = String::new();
305        out.push_str("proof_traffic_parking_lot:\n");
306        out.push_str(&format!(
307            "- lot_id: `{}`\n- attempt_count: `{}`\n- group_count: `{}`\n",
308            self.lot_id,
309            self.attempts.len(),
310            self.groups.len()
311        ));
312        for group in &self.groups {
313            out.push_str("- blocker_key: `");
314            out.push_str(&group.blocker_key);
315            out.push_str("` attempts: `");
316            out.push_str(&group.attempt_ids.join(","));
317            out.push_str("`\n");
318        }
319        out
320    }
321
322    /// Render `br comment` body with structured fields.
323    #[must_use]
324    pub fn br_comment_body(&self) -> String {
325        let mut out = String::new();
326        out.push_str("Proof-traffic parking lot\n\n");
327        out.push_str(&format!(
328            "- lot_id: `{}`\n- attempt_count: `{}`\n- group_count: `{}`\n",
329            self.lot_id,
330            self.attempts.len(),
331            self.groups.len()
332        ));
333        out
334    }
335}
336
337fn group_attempts(attempts: &[ParkedProofAttempt]) -> Vec<ParkedProofGroup> {
338    let mut by_key: BTreeMap<String, Vec<&ParkedProofAttempt>> = BTreeMap::new();
339    for attempt in attempts {
340        by_key
341            .entry(attempt.blocker_key.clone())
342            .or_default()
343            .push(attempt);
344    }
345
346    by_key
347        .into_iter()
348        .map(|(blocker_key, attempts)| {
349            let attempt_ids = attempts
350                .iter()
351                .map(|attempt| attempt.attempt_id.clone())
352                .collect::<Vec<_>>();
353            let command_intents = attempts
354                .iter()
355                .map(|attempt| attempt.command_intent.clone())
356                .collect::<BTreeSet<_>>()
357                .into_iter()
358                .collect::<Vec<_>>();
359            let owned_paths = attempts
360                .iter()
361                .flat_map(|attempt| attempt.owned_paths.iter().cloned())
362                .collect::<BTreeSet<_>>()
363                .into_iter()
364                .collect::<Vec<_>>();
365            ParkedProofGroup {
366                blocker_key,
367                attempt_count: attempt_ids.len(),
368                attempt_ids,
369                command_intents,
370                owned_paths,
371            }
372        })
373        .collect()
374}
375
376fn render_fresh_blocker(attempt: &ParkedProofAttempt) -> String {
377    format!(
378        "# PARKED: blocker={} retry_predicate={} satisfied={} required_signal={}; no proof command emitted",
379        attempt.blocker_class.label(),
380        attempt.retry_predicate.predicate_id,
381        attempt.retry_predicate.satisfied,
382        attempt.retry_predicate.required_signal
383    )
384}
385
386fn push_string_section(out: &mut String, title: &str, values: &[String]) {
387    out.push_str(&format!("### {title} ({})\n", values.len()));
388    if values.is_empty() {
389        out.push_str("- _none_\n");
390    } else {
391        for value in values {
392            out.push_str("- ");
393            out.push_str(value);
394            out.push('\n');
395        }
396    }
397    out.push('\n');
398}
399
400fn sorted_unique(mut values: Vec<String>) -> Vec<String> {
401    values.sort();
402    values.dedup();
403    values
404}