Skip to main content

sim_lib_bridge/
repair.rs

1use sim_kernel::{Expr, Symbol};
2use sim_value::build::entry;
3
4/// ASK failure data that can be fed back to a model as repair context.
5#[derive(Clone, Debug, PartialEq, Eq)]
6pub enum AskFailure {
7    /// The returned content could not be decoded by the declared codec.
8    Decode {
9        /// Declared return codec.
10        codec: Symbol,
11        /// Decode diagnostic.
12        message: String,
13    },
14    /// The decoded value failed the declared return Shape.
15    Shape {
16        /// Expected shape descriptor.
17        expected: String,
18        /// Shape diagnostics.
19        diagnostics: Vec<String>,
20    },
21}
22
23impl AskFailure {
24    /// Projects this failure into data suitable for fenced repair context.
25    pub fn to_expr(&self) -> Expr {
26        match self {
27            Self::Decode { codec, message } => Expr::Map(vec![
28                entry(
29                    "kind",
30                    Expr::Symbol(Symbol::qualified("bridge", "DecodeFailure")),
31                ),
32                entry("codec", Expr::Symbol(codec.clone())),
33                entry("message", Expr::String(message.clone())),
34            ]),
35            Self::Shape {
36                expected,
37                diagnostics,
38            } => Expr::Map(vec![
39                entry(
40                    "kind",
41                    Expr::Symbol(Symbol::qualified("bridge", "ShapeFailure")),
42                ),
43                entry("expected", Expr::String(expected.clone())),
44                entry(
45                    "diagnostics",
46                    Expr::Vector(diagnostics.iter().cloned().map(Expr::String).collect()),
47                ),
48            ]),
49        }
50    }
51
52    pub(crate) fn message(&self) -> String {
53        match self {
54            Self::Decode { codec, message } => {
55                format!("decode with {codec} failed: {message}")
56            }
57            Self::Shape {
58                expected,
59                diagnostics,
60            } => {
61                let actual = if diagnostics.is_empty() {
62                    "no diagnostics".to_owned()
63                } else {
64                    diagnostics.join("; ")
65                };
66                format!("shape {expected} rejected answer: {actual}")
67            }
68        }
69    }
70}
71
72/// Bounded ASK repair policy.
73#[derive(Clone, Copy, Debug, PartialEq, Eq)]
74pub struct RepairPolicy {
75    /// Maximum retry count. Values above 2 are clamped.
76    pub max_retries: u8,
77}
78
79impl RepairPolicy {
80    /// Builds a repair policy, clamping retries to the BRIDGE maximum.
81    pub fn new(max_retries: u8) -> Self {
82        Self {
83            max_retries: max_retries.min(2),
84        }
85    }
86
87    pub(crate) fn retries(self) -> u8 {
88        self.max_retries.min(2)
89    }
90}
91
92impl Default for RepairPolicy {
93    fn default() -> Self {
94        Self::new(1)
95    }
96}