Skip to main content

provable_contracts/
explain.rs

1//! Contract explanation — chain-of-thought narrative for any contract.
2//!
3//! Unlike `book_gen` (reference tables), explain produces prose that walks
4//! through the contract section by section, explaining the *why* behind
5//! each element.
6
7use std::fmt::Write;
8
9use crate::binding::BindingRegistry;
10use crate::proof_status::compute_proof_level;
11use crate::schema::{Contract, ObligationType};
12
13#[path = "explain_render.rs"]
14mod explain_render;
15use explain_render::{
16    write_binding_status, write_enforcement, write_falsification_tests, write_kani_harnesses,
17    write_kernel_phases, write_obligations, write_qa_gate, write_simd_dispatch,
18    write_verification_ladder,
19};
20
21#[cfg(test)]
22#[allow(clippy::all)]
23#[path = "explain_tests.rs"]
24mod tests;
25
26/// Return a mathematical pattern description for the given obligation type.
27pub fn obligation_pattern(ot: ObligationType) -> &'static str {
28    match ot {
29        ObligationType::Invariant => "∀x ∈ Domain: P(f(x)) — property holds for all inputs",
30        ObligationType::Equivalence => "∀x: |f(x) - g(x)| < ε — two implementations agree",
31        ObligationType::Bound => "∀x: a ≤ f(x)_i ≤ b — output range bounded",
32        ObligationType::Monotonicity => "x_i > x_j → f(x)_i > f(x)_j — order preserved",
33        ObligationType::Idempotency => "f(f(x)) = f(x) — applying twice gives same result",
34        ObligationType::Linearity => "f(αx) = α·f(x) — homogeneous scaling",
35        ObligationType::Symmetry => "f(permute(x)) related to f(x) — permutation property",
36        ObligationType::Associativity => "(a ⊕ b) ⊕ c = a ⊕ (b ⊕ c) — grouping invariant",
37        ObligationType::Conservation => "Q(before) = Q(after) — conserved quantity",
38        ObligationType::Ordering => "a ≤ b → f(a) ≤ f(b) — order relation maintained",
39        ObligationType::Completeness => "∀ required elements present — completeness verified",
40        ObligationType::Soundness => "∀x: P(x) → Q(f(x)) — soundness of transformation",
41        ObligationType::Involution => "f(f(x)) = x — involution (self-inverse)",
42        ObligationType::Determinism => "f(x) = f(x) — deterministic output for same input",
43        ObligationType::Roundtrip => "decode(encode(x)) = x — roundtrip fidelity",
44        ObligationType::StateMachine => "S × A → S — valid state transitions",
45        ObligationType::Classification => "f(x) ∈ C — output belongs to valid class set",
46        ObligationType::Independence => "P(A∩B) = P(A)·P(B) — statistical independence",
47        ObligationType::Termination => "algorithm terminates in finite steps",
48        // Eiffel DbC types
49        ObligationType::Precondition => "P(input) — caller must guarantee before call",
50        ObligationType::Postcondition => "P(in) → Q(out) — kernel guarantees if pre holds",
51        ObligationType::Frame => "modifies(S) ∧ preserves(T\\S) — only S may change",
52        ObligationType::LoopInvariant => "∀ iter i: P(state_i) — maintained across iterations",
53        ObligationType::LoopVariant => "V(state) ∈ ℕ, strictly decreasing — termination witness",
54        ObligationType::OldState => "Q(old(state), new(state)) — relates pre to post state",
55        ObligationType::Subcontract => "weaken(pre) ∧ strengthen(post) — behavioral subtyping",
56    }
57}
58
59/// Kani strategy explanation for the explain output.
60fn strategy_explanation(strategy: &str) -> &str {
61    match strategy {
62        "exhaustive" => "verify for ALL inputs within bound",
63        "stub_float" => {
64            "assume Lean-proved postconditions on transcendentals, verify surrounding code"
65        }
66        "compositional" => "verify sub-kernels separately, compose proofs",
67        "bounded_int" => "integer-only verification within bound",
68        _ => "bounded model check",
69    }
70}
71
72/// Generate a chain-of-thought narrative explanation for a contract.
73///
74/// `stem` is the contract filename without `.yaml`.
75/// `binding` is an optional binding registry for binding status.
76pub fn explain_contract(
77    contract: &Contract,
78    stem: &str,
79    binding: Option<&BindingRegistry>,
80) -> String {
81    let mut out = String::with_capacity(4096);
82
83    write_header(&mut out, contract, stem);
84    write_what(&mut out, contract);
85    write_equations(&mut out, contract);
86    write_obligations(&mut out, contract);
87    write_verification_ladder(&mut out, contract, binding);
88    write_falsification_tests(&mut out, contract);
89    write_kani_harnesses(&mut out, contract);
90    write_kernel_phases(&mut out, contract);
91    write_simd_dispatch(&mut out, contract);
92    write_enforcement(&mut out, contract);
93    write_qa_gate(&mut out, contract);
94    write_type_invariants(&mut out, contract);
95    write_coq_status(&mut out, contract);
96    write_binding_status(&mut out, contract, stem, binding);
97
98    out
99}
100
101fn write_type_invariants(out: &mut String, contract: &Contract) {
102    if contract.type_invariants.is_empty() {
103        return;
104    }
105    let _ = writeln!(out, "Type invariants (Meyer's class invariants)");
106    let _ = writeln!(
107        out,
108        "  These predicates must hold for every instance at every stable state."
109    );
110    let _ = writeln!(out);
111    for inv in &contract.type_invariants {
112        let desc = inv
113            .description
114            .as_deref()
115            .map(|d| format!(" — {d}"))
116            .unwrap_or_default();
117        let _ = writeln!(out, "  {} [{}]{}", inv.name, inv.type_name, desc);
118        let _ = writeln!(out, "    Predicate: {}", inv.predicate);
119        let _ = writeln!(out);
120    }
121}
122
123fn write_coq_status(out: &mut String, contract: &Contract) {
124    let Some(ref spec) = contract.coq_spec else {
125        return;
126    };
127    let _ = writeln!(out, "Coq verification ({})", spec.module);
128    if spec.obligations.is_empty() {
129        let _ = writeln!(out, "  No obligation links defined — stubs only");
130    } else {
131        for ob in &spec.obligations {
132            let _ = writeln!(out, "  {} → {} [{}]", ob.links_to, ob.coq_lemma, ob.status);
133        }
134    }
135    let _ = writeln!(out);
136}
137
138/// Generate a markdown explanation with headers and LaTeX math.
139#[allow(clippy::too_many_lines)]
140pub fn explain_contract_markdown(
141    contract: &Contract,
142    stem: &str,
143    binding: Option<&BindingRegistry>,
144) -> String {
145    let mut out = String::with_capacity(4096);
146    let _ = writeln!(out, "# {stem}\n");
147    let _ = writeln!(
148        out,
149        "**Version:** {} | **Description:** {}\n",
150        contract.metadata.version, contract.metadata.description
151    );
152
153    if !contract.metadata.references.is_empty() {
154        let _ = writeln!(out, "## References\n");
155        for r in &contract.metadata.references {
156            let _ = writeln!(out, "- {r}");
157        }
158        let _ = writeln!(out);
159    }
160
161    if !contract.equations.is_empty() {
162        let _ = writeln!(out, "## Equations\n");
163        for (name, eq) in &contract.equations {
164            let _ = writeln!(out, "### {name}\n");
165            let latex = crate::latex::math_to_latex(&eq.formula);
166            let _ = writeln!(out, "$$\n{latex}\n$$\n");
167            if let Some(ref dom) = eq.domain {
168                let _ = writeln!(out, "**Domain:** ${}$\n", crate::latex::math_to_latex(dom));
169            }
170            if let Some(ref cod) = eq.codomain {
171                let _ = writeln!(
172                    out,
173                    "**Codomain:** ${}$\n",
174                    crate::latex::math_to_latex(cod)
175                );
176            }
177            if !eq.invariants.is_empty() {
178                let _ = writeln!(out, "**Invariants:**\n");
179                for inv in &eq.invariants {
180                    let _ = writeln!(out, "- ${}$", crate::latex::math_to_latex(inv));
181                }
182                let _ = writeln!(out);
183            }
184            if !eq.preconditions.is_empty() {
185                let _ = writeln!(out, "**Preconditions:**\n");
186                for pre in &eq.preconditions {
187                    let _ = writeln!(out, "- `{pre}`");
188                }
189                let _ = writeln!(out);
190            }
191            if !eq.postconditions.is_empty() {
192                let _ = writeln!(out, "**Postconditions:**\n");
193                for post in &eq.postconditions {
194                    let _ = writeln!(out, "- `{post}`");
195                }
196                let _ = writeln!(out);
197            }
198        }
199    }
200
201    if !contract.proof_obligations.is_empty() {
202        let _ = writeln!(out, "## Proof Obligations\n");
203        let _ = writeln!(out, "| # | Type | Property | Formal |");
204        let _ = writeln!(out, "|---|------|----------|--------|");
205        for (i, ob) in contract.proof_obligations.iter().enumerate() {
206            let formal = ob.formal.as_deref().unwrap_or("");
207            let _ = writeln!(
208                out,
209                "| {} | `{}` | {} | {} |",
210                i + 1,
211                ob.obligation_type,
212                ob.property,
213                formal
214            );
215        }
216        let _ = writeln!(out);
217    }
218
219    // Verification summary
220    let level = compute_proof_level(contract, None);
221    let _ = writeln!(out, "## Verification\n");
222    let _ = writeln!(out, "**Proof level:** {level}\n");
223    if let Some(ref vs) = contract.verification_summary {
224        let _ = writeln!(
225            out,
226            "- Lean: {}/{} proved",
227            vs.l4_lean_proved, vs.total_obligations
228        );
229    }
230    let _ = writeln!(out, "- Kani: {} harnesses", contract.kani_harnesses.len());
231    let _ = writeln!(
232        out,
233        "- Tests: {} falsification\n",
234        contract.falsification_tests.len()
235    );
236
237    // Binding status
238    if let Some(registry) = binding {
239        let contract_file = format!("{stem}.yaml");
240        let has_bindings = contract.equations.keys().any(|eq| {
241            registry
242                .bindings
243                .iter()
244                .any(|b| b.contract == contract_file && b.equation == *eq)
245        });
246        if has_bindings {
247            let _ = writeln!(out, "## Bindings ({})\n", registry.target_crate);
248            let _ = writeln!(out, "| Equation | Status |");
249            let _ = writeln!(out, "|----------|--------|");
250            for eq_name in contract.equations.keys() {
251                let status = registry
252                    .bindings
253                    .iter()
254                    .find(|b| b.contract == contract_file && b.equation == *eq_name)
255                    .map_or("missing", |b| match b.status {
256                        crate::binding::ImplStatus::Implemented => "implemented",
257                        crate::binding::ImplStatus::Partial => "partial",
258                        crate::binding::ImplStatus::NotImplemented => "not_implemented",
259                        crate::binding::ImplStatus::Pending => "pending",
260                    });
261                let _ = writeln!(out, "| {eq_name} | {status} |");
262            }
263            let _ = writeln!(out);
264        }
265    }
266
267    out
268}
269
270/// Generate a JSON explanation of the contract.
271pub fn explain_contract_json(
272    contract: &Contract,
273    stem: &str,
274    binding: Option<&BindingRegistry>,
275) -> String {
276    let level = compute_proof_level(contract, None);
277
278    let obligations: Vec<serde_json::Value> = contract
279        .proof_obligations
280        .iter()
281        .map(|ob| {
282            let mut obj = serde_json::json!({
283                "type": ob.obligation_type.to_string(),
284                "property": ob.property,
285                "pattern": obligation_pattern(ob.obligation_type),
286            });
287            if let Some(ref f) = ob.formal {
288                obj["formal"] = serde_json::json!(f);
289            }
290            if let Some(t) = ob.tolerance {
291                obj["tolerance"] = serde_json::json!(t);
292            }
293            if let Some(ref lean) = ob.lean {
294                obj["lean"] = serde_json::json!({
295                    "theorem": lean.theorem,
296                    "status": lean.status.to_string(),
297                });
298            }
299            if let Some(ref req) = ob.requires {
300                obj["requires"] = serde_json::json!(req);
301            }
302            if let Some(ref phase) = ob.applies_to_phase {
303                obj["applies_to_phase"] = serde_json::json!(phase);
304            }
305            if let Some(ref parent) = ob.parent_contract {
306                obj["parent_contract"] = serde_json::json!(parent);
307            }
308            obj
309        })
310        .collect();
311
312    let json = serde_json::json!({
313        "stem": stem,
314        "version": contract.metadata.version,
315        "description": contract.metadata.description,
316        "references": contract.metadata.references,
317        "depends_on": contract.metadata.depends_on,
318        "equations": contract.equations.keys().collect::<Vec<_>>(),
319        "proof_level": level.to_string(),
320        "obligations": obligations,
321        "falsification_tests": contract.falsification_tests.len(),
322        "kani_harnesses": contract.kani_harnesses.len(),
323        "binding": binding.map(|b| {
324            let contract_file = format!("{stem}.yaml");
325            let statuses: std::collections::BTreeMap<String, String> = contract
326                .equations
327                .keys()
328                .map(|eq| {
329                    let status = b.bindings.iter()
330                        .find(|bi| bi.contract == contract_file && bi.equation == *eq)
331                        .map_or_else(|| "missing".to_string(), |bi| bi.status.to_string());
332                    (eq.clone(), status)
333                })
334                .collect();
335            serde_json::json!({
336                "target_crate": b.target_crate,
337                "equations": statuses,
338            })
339        }),
340    });
341
342    serde_json::to_string_pretty(&json).unwrap_or_else(|_| "{}".to_string())
343}
344
345fn write_header(out: &mut String, contract: &Contract, stem: &str) {
346    use crate::schema::ContractKind;
347    let kind_tag = if contract.kind() == ContractKind::Kernel {
348        String::new()
349    } else {
350        format!(" [{}]", contract.kind())
351    };
352    let _ = writeln!(out, "{stem} (v{}){kind_tag}", contract.metadata.version);
353    let _ = writeln!(out, "{}", contract.metadata.description);
354    let _ = writeln!(out);
355}
356
357fn write_what(out: &mut String, contract: &Contract) {
358    let _ = writeln!(out, "What this contract specifies");
359
360    let refs = &contract.metadata.references;
361    if refs.is_empty() {
362        let _ = writeln!(
363            out,
364            "  This contract specifies {}.",
365            contract.metadata.description
366        );
367    } else {
368        let _ = write!(
369            out,
370            "  This contract specifies {}. It derives from",
371            contract.metadata.description
372        );
373        for (i, r) in refs.iter().enumerate() {
374            if i == 0 {
375                let _ = write!(out, " {r}");
376            } else {
377                let _ = write!(out, " and {r}");
378            }
379        }
380        let _ = writeln!(out, ".");
381    }
382
383    if !contract.metadata.depends_on.is_empty() {
384        let _ = write!(out, "  Depends on:");
385        for dep in &contract.metadata.depends_on {
386            let _ = write!(out, " {dep}");
387        }
388        let _ = writeln!(out);
389    }
390
391    let _ = writeln!(out);
392}
393
394fn write_equations(out: &mut String, contract: &Contract) {
395    if contract.equations.is_empty() {
396        return;
397    }
398    let _ = writeln!(out, "Governing equations");
399    let _ = writeln!(out);
400
401    for (name, eq) in &contract.equations {
402        let _ = writeln!(out, "  {name}");
403        let _ = writeln!(out, "    {}", eq.formula);
404
405        if let Some(ref dom) = eq.domain {
406            let _ = writeln!(out, "    Domain: {dom}");
407        }
408        if let Some(ref cod) = eq.codomain {
409            let _ = writeln!(out, "    Range:  {cod}");
410        }
411
412        if !eq.invariants.is_empty() {
413            let _ = writeln!(out);
414            let _ = writeln!(out, "    Invariants:");
415            for (i, inv) in eq.invariants.iter().enumerate() {
416                let _ = writeln!(out, "      {}. {inv}", i + 1);
417            }
418        }
419
420        if !eq.preconditions.is_empty() {
421            let _ = writeln!(out);
422            let _ = writeln!(out, "    Preconditions (caller must guarantee):");
423            for pre in &eq.preconditions {
424                let _ = writeln!(out, "      - {pre}");
425            }
426        }
427
428        if !eq.postconditions.is_empty() {
429            let _ = writeln!(out);
430            let _ = writeln!(out, "    Postconditions (kernel guarantees):");
431            for post in &eq.postconditions {
432                let _ = writeln!(out, "      - {post}");
433            }
434        }
435
436        if let Some(ref theorem) = eq.lean_theorem {
437            let _ = writeln!(out, "    Lean theorem: {theorem}");
438        }
439
440        let _ = writeln!(out);
441    }
442}