provable_contracts/
levels.rs1use crate::proof_status::ProofLevel;
15
16pub const MARKER: &str = "<!-- generated from ProofLevel; do not edit -->";
18pub const END_MARKER: &str = "<!-- end generated from ProofLevel -->";
20
21impl ProofLevel {
22 pub const ALL_DESCENDING: [ProofLevel; 5] = [
24 ProofLevel::L5,
25 ProofLevel::L4,
26 ProofLevel::L3,
27 ProofLevel::L2,
28 ProofLevel::L1,
29 ];
30
31 #[must_use]
36 pub fn method(self) -> &'static str {
37 match self {
38 ProofLevel::L1 => "Contract YAML with equations",
39 ProofLevel::L2 => "Falsification tests cover every obligation",
40 ProofLevel::L3 => "L2 + at least one Kani bounded-model-check harness",
41 ProofLevel::L4 => {
42 "Every obligation has a sorry-free in-tree Lean 4 theorem or is not applicable, with at least one proved"
43 }
44 ProofLevel::L5 => "L4 + at least one binding, every binding implemented",
45 }
46 }
47}
48
49#[must_use]
51pub fn ladder_block() -> String {
52 let mut out = String::new();
53 out.push_str(MARKER);
54 out.push('\n');
55 out.push_str("| Level | Method |\n|-------|--------|\n");
56 for level in ProofLevel::ALL_DESCENDING {
57 out.push_str(&format!("| {level} | {} |\n", level.method()));
58 }
59 out.push_str(
60 "\nL4 and L5 are grounded only textually until PVL-001 EV-8b lands: a claimed Lean \
61 proof counts when a sorry-free Lean theorem in this tree matches it (a claim with \
62 none is reported self-declared and excluded from L4), a not-applicable count is \
63 taken from the contract's own verification summary, and no checked lake \
64 discharge summary is read yet.\n",
65 );
66 out.push_str(END_MARKER);
67 out
68}
69
70pub fn stale_level_pairings(doc: &str) -> Vec<String> {
81 let outside: String = match (doc.find(MARKER), doc.find(END_MARKER)) {
82 (Some(a), Some(b)) if a < b => format!("{}{}", &doc[..a], &doc[b + END_MARKER.len()..]),
83 _ => doc.to_string(),
84 };
85 let mut stale = Vec::new();
86 for line in outside.lines() {
87 let low = line.to_lowercase();
88 let levels = level_tokens(&low);
89 let wrong = ["kani", "lean"].iter().any(|tool| {
90 word_starts(&low, tool).any(|t| {
91 let t_end = t + tool.len();
92 let nearest = levels.iter().min_by_key(|(start, end, _)| {
93 if *end <= t {
94 t - end
95 } else if *start >= t_end {
96 start - t_end
97 } else {
98 0
99 }
100 });
101 match (*tool, nearest) {
102 ("kani", Some((_, _, n))) => *n >= 4,
103 ("lean", Some((_, _, n))) => *n == 5 && !low.contains("binding"),
104 _ => false,
105 }
106 })
107 });
108 if wrong {
109 stale.push(line.trim().to_string());
110 }
111 }
112 stale
113}
114
115fn word_starts<'a>(hay: &'a str, needle: &'a str) -> impl Iterator<Item = usize> + 'a {
117 hay.match_indices(needle)
118 .map(|(i, _)| i)
119 .filter(move |&i| i == 0 || !hay.as_bytes()[i - 1].is_ascii_alphanumeric())
120}
121
122fn level_tokens(low: &str) -> Vec<(usize, usize, u8)> {
124 let mut out = Vec::new();
125 for n in 1u8..=5 {
126 for tok in [format!("l{n}"), format!("level {n}")] {
127 for i in word_starts(low, &tok) {
128 let end = i + tok.len();
129 if !low[end..].starts_with(|c: char| c.is_ascii_alphanumeric()) {
130 out.push((i, end, n));
131 }
132 }
133 }
134 }
135 out
136}
137
138#[test]
145fn readme_and_ladder_docs_match_enum() {
146 let readme = crate::readme_gen::verification_ladder_table(&[0, 0, 0, 0, 0]);
148 for level in ProofLevel::ALL_DESCENDING {
149 let row = format!("| {level} | 0 | {} |", level.method());
150 assert!(
151 readme.lines().any(|l| l == row),
152 "README ladder row for {level} is not the enum's definition.\nwant: {row}\nREADME table:\n{readme}"
153 );
154 }
155 assert!(ProofLevel::L4.method().contains("at least one proved"));
161 assert!(ProofLevel::L5.method().contains("at least one binding"));
162 }
166
167#[test]
170fn the_ladder_block_names_each_level_once_highest_first() {
171 let block = ladder_block();
172 let is_level_row =
173 |l: &&str| l.len() > 4 && l.starts_with("| L") && l.as_bytes()[3].is_ascii_digit();
174 let rows: Vec<&str> = block.lines().filter(is_level_row).collect();
175 assert_eq!(rows.len(), 5, "{block}");
176 for (row, level) in rows.iter().zip(ProofLevel::ALL_DESCENDING) {
177 assert!(row.starts_with(&format!("| {level} |")), "{row}");
178 }
179 assert!(block.starts_with(MARKER) && block.ends_with(END_MARKER));
180}
181
182#[cfg(test)]
185#[test]
186fn stale_level_pairings_case_table() {
187 let must_flag = [
188 "| Obligation Type | Level 1 (Types) | Level 3 (probar) | Level 4 (Kani) | Level 5 (Lean) |",
189 "| Obligation Type | L1 (Types) | L3 (probar) | L4 (Kani) | L5 (Lean) |",
190 "3. **L4:** Kani exhaustively verified for ALL inputs within the kernel's",
191 "3. **Level 4:** Kani has exhaustively verified the property for ALL inputs up",
192 " L5 Theorem proving Lean 4 True for ALL inputs. Period.",
193 "Kani is the tool used at Level 4 for bounded checks.",
195 "The Lean prover is what L5 means here.",
196 "Falsification is L2 then Kani at L4.",
199 ];
200 for line in must_flag {
201 assert_eq!(stale_level_pairings(line).len(), 1, "must flag: {line}");
202 }
203 let must_not_flag = [
204 "| Obligation Type | Types (rustc) | L2 (probar/proptest) | L3 (Kani) | L4 (Lean) |",
205 "3. **L3:** Kani exhaustively verified it for ALL inputs within the kernel's",
206 "4. **L4:** a Lean 4 theorem proves it unbounded; **L5** additionally requires",
207 "## How Kani (L3) and Lean (L4) Compose",
208 "| L5 | L4 + at least one binding, every binding implemented |",
209 "E4 and E5 are defined in YAML but not yet run in CI.",
210 "| **E4** | Logic bugs, overflows | Kani `#[kani::proof]` BMC |",
211 ];
212 for line in must_not_flag {
213 assert!(
214 stale_level_pairings(line).is_empty(),
215 "must not flag: {line}"
216 );
217 }
218 assert!(stale_level_pairings(&ladder_block()).is_empty());
220}