Skip to main content

sim_lib_bridge/
report.rs

1use sim_kernel::{Expr, Symbol};
2use sim_value::build::entry;
3
4/// One validation defect produced by BRIDGE receive checks.
5#[derive(Clone, Debug, PartialEq, Eq)]
6pub struct BridgeObligation {
7    /// Stable packet path where the defect was found.
8    pub path: String,
9    /// Human-facing reason for the obligation.
10    pub reason: String,
11    /// Expected value, shape, or condition.
12    pub expected: String,
13    /// Actual value or condition observed.
14    pub actual: String,
15    /// Repair choices that a caller may offer to a human or model seat.
16    pub repair_menu: Vec<String>,
17}
18
19impl BridgeObligation {
20    /// Builds an obligation with explicit repair menu entries.
21    pub fn new(
22        path: impl Into<String>,
23        reason: impl Into<String>,
24        expected: impl Into<String>,
25        actual: impl Into<String>,
26        repair_menu: Vec<String>,
27    ) -> Self {
28        Self {
29            path: path.into(),
30            reason: reason.into(),
31            expected: expected.into(),
32            actual: actual.into(),
33            repair_menu,
34        }
35    }
36
37    /// Builds an obligation with the default repair menu.
38    pub fn repair_packet(
39        path: impl Into<String>,
40        reason: impl Into<String>,
41        expected: impl Into<String>,
42        actual: impl Into<String>,
43    ) -> Self {
44        Self::new(
45            path,
46            reason,
47            expected,
48            actual,
49            vec![
50                "repair packet".to_owned(),
51                "fetch missing context".to_owned(),
52                "return receipt".to_owned(),
53            ],
54        )
55    }
56
57    /// Projects this obligation to a stable expression record.
58    pub fn to_expr(&self) -> Expr {
59        Expr::Map(vec![
60            entry("path", Expr::String(self.path.clone())),
61            entry("reason", Expr::String(self.reason.clone())),
62            entry("expected", Expr::String(self.expected.clone())),
63            entry("actual", Expr::String(self.actual.clone())),
64            entry(
65                "repair-menu",
66                Expr::List(
67                    self.repair_menu
68                        .iter()
69                        .map(|item| Expr::String(item.clone()))
70                        .collect(),
71                ),
72            ),
73        ])
74    }
75}
76
77/// Receive-check report for one BRIDGE packet.
78#[derive(Clone, Debug, PartialEq, Eq)]
79pub struct BridgeReport {
80    /// Packet content id used for reporting.
81    pub packet_cid: String,
82    /// Accepted part ids.
83    pub accepted_parts: Vec<String>,
84    /// Rejected part ids.
85    pub rejected_parts: Vec<String>,
86    /// Obligations that must be repaired before the packet is accepted.
87    pub obligations: Vec<BridgeObligation>,
88}
89
90impl BridgeReport {
91    /// Builds an empty report for `packet_cid`.
92    pub fn new(packet_cid: impl Into<String>) -> Self {
93        Self {
94            packet_cid: packet_cid.into(),
95            accepted_parts: Vec::new(),
96            rejected_parts: Vec::new(),
97            obligations: Vec::new(),
98        }
99    }
100
101    /// Records one accepted part id.
102    pub fn accept(&mut self, id: &Symbol) {
103        let id = id.as_qualified_str();
104        if !self.accepted_parts.contains(&id) {
105            self.accepted_parts.push(id);
106        }
107    }
108
109    /// Records one rejected part id.
110    pub fn reject(&mut self, id: &Symbol) {
111        let id = id.as_qualified_str();
112        if !self.rejected_parts.contains(&id) {
113            self.rejected_parts.push(id);
114        }
115    }
116
117    /// Appends an obligation.
118    pub fn obligate(&mut self, obligation: BridgeObligation) {
119        self.obligations.push(obligation);
120    }
121
122    /// Returns true when no obligation remains.
123    pub fn accepted(&self) -> bool {
124        self.obligations.is_empty()
125    }
126
127    /// Projects this report to a stable expression record.
128    pub fn to_expr(&self) -> Expr {
129        Expr::Map(vec![
130            entry("bridge-report", Expr::Bool(true)),
131            entry("packet-cid", Expr::String(self.packet_cid.clone())),
132            entry(
133                "accepted-parts",
134                Expr::List(
135                    self.accepted_parts
136                        .iter()
137                        .map(|id| Expr::String(id.clone()))
138                        .collect(),
139                ),
140            ),
141            entry(
142                "rejected-parts",
143                Expr::List(
144                    self.rejected_parts
145                        .iter()
146                        .map(|id| Expr::String(id.clone()))
147                        .collect(),
148                ),
149            ),
150            entry(
151                "obligations",
152                Expr::List(
153                    self.obligations
154                        .iter()
155                        .map(BridgeObligation::to_expr)
156                        .collect(),
157                ),
158            ),
159        ])
160    }
161}