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        ObligationType::Safety => "∀x: safe(f(x)) — memory/IO safety obligation",
57        ObligationType::Liveness => "◇ P — eventually P holds (progress obligation)",
58    }
59}
60
61/// Kani strategy explanation for the explain output.
62fn strategy_explanation(strategy: &str) -> &str {
63    match strategy {
64        "exhaustive" => "verify for ALL inputs within bound",
65        "stub_float" => {
66            "assume Lean-proved postconditions on transcendentals, verify surrounding code"
67        }
68        "compositional" => "verify sub-kernels separately, compose proofs",
69        "bounded_int" => "integer-only verification within bound",
70        _ => "bounded model check",
71    }
72}
73
74/// Generate a chain-of-thought narrative explanation for a contract.
75///
76/// `stem` is the contract filename without `.yaml`.
77/// `binding` is an optional binding registry for binding status.
78pub fn explain_contract(
79    contract: &Contract,
80    stem: &str,
81    binding: Option<&BindingRegistry>,
82) -> String {
83    let mut out = String::with_capacity(4096);
84
85    write_header(&mut out, contract, stem);
86    write_what(&mut out, contract);
87    write_equations(&mut out, contract);
88    write_obligations(&mut out, contract);
89    write_verification_ladder(&mut out, contract, binding);
90    write_falsification_tests(&mut out, contract);
91    write_kani_harnesses(&mut out, contract);
92    write_kernel_phases(&mut out, contract);
93    write_simd_dispatch(&mut out, contract);
94    write_enforcement(&mut out, contract);
95    write_qa_gate(&mut out, contract);
96    write_type_invariants(&mut out, contract);
97    write_coq_status(&mut out, contract);
98    write_binding_status(&mut out, contract, stem, binding);
99
100    out
101}
102
103fn write_type_invariants(out: &mut String, contract: &Contract) {
104    if contract.type_invariants.is_empty() {
105        return;
106    }
107    let _ = writeln!(out, "Type invariants (Meyer's class invariants)");
108    let _ = writeln!(
109        out,
110        "  These predicates must hold for every instance at every stable state."
111    );
112    let _ = writeln!(out);
113    for inv in &contract.type_invariants {
114        let desc = inv
115            .description
116            .as_deref()
117            .map(|d| format!(" — {d}"))
118            .unwrap_or_default();
119        let _ = writeln!(out, "  {} [{}]{}", inv.name, inv.type_name, desc);
120        let _ = writeln!(out, "    Predicate: {}", inv.predicate);
121        let _ = writeln!(out);
122    }
123}
124
125fn write_coq_status(out: &mut String, contract: &Contract) {
126    let Some(ref spec) = contract.coq_spec else {
127        return;
128    };
129    let _ = writeln!(out, "Coq verification ({})", spec.module);
130    if spec.obligations.is_empty() {
131        let _ = writeln!(out, "  No obligation links defined — stubs only");
132    } else {
133        for ob in &spec.obligations {
134            let _ = writeln!(out, "  {} → {} [{}]", ob.links_to, ob.coq_lemma, ob.status);
135        }
136    }
137    let _ = writeln!(out);
138}
139
140/// Generate a markdown explanation with headers and LaTeX math.
141#[allow(clippy::too_many_lines)]
142pub fn explain_contract_markdown(
143    contract: &Contract,
144    stem: &str,
145    binding: Option<&BindingRegistry>,
146) -> String {
147    let mut out = String::with_capacity(4096);
148    let _ = writeln!(out, "# {stem}\n");
149    let _ = writeln!(
150        out,
151        "**Version:** {} | **Description:** {}\n",
152        contract.metadata.version, contract.metadata.description
153    );
154
155    if !contract.metadata.references.is_empty() {
156        let _ = writeln!(out, "## References\n");
157        for r in &contract.metadata.references {
158            let _ = writeln!(out, "- {r}");
159        }
160        let _ = writeln!(out);
161    }
162
163    if !contract.equations.is_empty() {
164        let _ = writeln!(out, "## Equations\n");
165        for (name, eq) in &contract.equations {
166            let _ = writeln!(out, "### {name}\n");
167            let latex = crate::latex::math_to_latex(&eq.formula);
168            let _ = writeln!(out, "$$\n{latex}\n$$\n");
169            if let Some(ref dom) = eq.domain {
170                let _ = writeln!(out, "**Domain:** ${}$\n", crate::latex::math_to_latex(dom));
171            }
172            if let Some(ref cod) = eq.codomain {
173                let _ = writeln!(
174                    out,
175                    "**Codomain:** ${}$\n",
176                    crate::latex::math_to_latex(cod)
177                );
178            }
179            if !eq.invariants.is_empty() {
180                let _ = writeln!(out, "**Invariants:**\n");
181                for inv in &eq.invariants {
182                    let _ = writeln!(out, "- ${}$", crate::latex::math_to_latex(inv));
183                }
184                let _ = writeln!(out);
185            }
186            if !eq.preconditions.is_empty() {
187                let _ = writeln!(out, "**Preconditions:**\n");
188                for pre in &eq.preconditions {
189                    let _ = writeln!(out, "- `{pre}`");
190                }
191                let _ = writeln!(out);
192            }
193            if !eq.postconditions.is_empty() {
194                let _ = writeln!(out, "**Postconditions:**\n");
195                for post in &eq.postconditions {
196                    let _ = writeln!(out, "- `{post}`");
197                }
198                let _ = writeln!(out);
199            }
200        }
201    }
202
203    if !contract.proof_obligations.is_empty() {
204        let _ = writeln!(out, "## Proof Obligations\n");
205        let _ = writeln!(out, "| # | Type | Property | Formal |");
206        let _ = writeln!(out, "|---|------|----------|--------|");
207        for (i, ob) in contract.proof_obligations.iter().enumerate() {
208            let formal = ob.formal.as_deref().unwrap_or("");
209            let _ = writeln!(
210                out,
211                "| {} | `{}` | {} | {} |",
212                i + 1,
213                ob.obligation_type,
214                ob.property,
215                formal
216            );
217        }
218        let _ = writeln!(out);
219    }
220
221    // Verification summary
222    let level = compute_proof_level(contract, None);
223    let _ = writeln!(out, "## Verification\n");
224    let _ = writeln!(out, "**Proof level:** {level}\n");
225    if let Some(ref vs) = contract.verification_summary {
226        let _ = writeln!(
227            out,
228            "- Lean: {}/{} proved",
229            vs.l4_lean_proved, vs.total_obligations
230        );
231    }
232    let _ = writeln!(out, "- Kani: {} harnesses", contract.kani_harnesses.len());
233    let _ = writeln!(
234        out,
235        "- Tests: {} falsification\n",
236        contract.falsification_tests.len()
237    );
238
239    // Binding status
240    if let Some(registry) = binding {
241        let contract_file = format!("{stem}.yaml");
242        let has_bindings = contract.equations.keys().any(|eq| {
243            registry
244                .bindings
245                .iter()
246                .any(|b| b.contract == contract_file && b.equation == *eq)
247        });
248        if has_bindings {
249            let _ = writeln!(out, "## Bindings ({})\n", registry.target_crate);
250            let _ = writeln!(out, "| Equation | Status |");
251            let _ = writeln!(out, "|----------|--------|");
252            for eq_name in contract.equations.keys() {
253                let status = registry
254                    .bindings
255                    .iter()
256                    .find(|b| b.contract == contract_file && b.equation == *eq_name)
257                    .map_or("missing", |b| match b.status {
258                        crate::binding::ImplStatus::Implemented => "implemented",
259                        crate::binding::ImplStatus::Partial => "partial",
260                        crate::binding::ImplStatus::NotImplemented => "not_implemented",
261                        crate::binding::ImplStatus::Pending => "pending",
262                    });
263                let _ = writeln!(out, "| {eq_name} | {status} |");
264            }
265            let _ = writeln!(out);
266        }
267    }
268
269    out
270}
271
272/// Generate a JSON explanation of the contract.
273pub fn explain_contract_json(
274    contract: &Contract,
275    stem: &str,
276    binding: Option<&BindingRegistry>,
277) -> String {
278    let level = compute_proof_level(contract, None);
279
280    let obligations: Vec<serde_json::Value> = contract
281        .proof_obligations
282        .iter()
283        .map(|ob| {
284            let mut obj = serde_json::json!({
285                "type": ob.obligation_type.to_string(),
286                "property": ob.property,
287                "pattern": obligation_pattern(ob.obligation_type),
288            });
289            if let Some(ref f) = ob.formal {
290                obj["formal"] = serde_json::json!(f);
291            }
292            if let Some(t) = ob.tolerance {
293                obj["tolerance"] = serde_json::json!(t);
294            }
295            if let Some(ref lean) = ob.lean {
296                obj["lean"] = serde_json::json!({
297                    "theorem": lean.theorem,
298                    "status": lean.status.to_string(),
299                });
300            }
301            if let Some(ref req) = ob.requires {
302                obj["requires"] = serde_json::json!(req);
303            }
304            if let Some(ref phase) = ob.applies_to_phase {
305                obj["applies_to_phase"] = serde_json::json!(phase);
306            }
307            if let Some(ref parent) = ob.parent_contract {
308                obj["parent_contract"] = serde_json::json!(parent);
309            }
310            obj
311        })
312        .collect();
313
314    let json = serde_json::json!({
315        "stem": stem,
316        "version": contract.metadata.version,
317        "description": contract.metadata.description,
318        "references": contract.metadata.references,
319        "depends_on": contract.metadata.depends_on,
320        "equations": contract.equations.keys().collect::<Vec<_>>(),
321        "proof_level": level.to_string(),
322        "obligations": obligations,
323        "falsification_tests": contract.falsification_tests.len(),
324        "kani_harnesses": contract.kani_harnesses.len(),
325        "binding": binding.map(|b| {
326            let contract_file = format!("{stem}.yaml");
327            let statuses: std::collections::BTreeMap<String, String> = contract
328                .equations
329                .keys()
330                .map(|eq| {
331                    let status = b.bindings.iter()
332                        .find(|bi| bi.contract == contract_file && bi.equation == *eq)
333                        .map_or_else(|| "missing".to_string(), |bi| bi.status.to_string());
334                    (eq.clone(), status)
335                })
336                .collect();
337            serde_json::json!({
338                "target_crate": b.target_crate,
339                "equations": statuses,
340            })
341        }),
342    });
343
344    serde_json::to_string_pretty(&json).unwrap_or_else(|_| "{}".to_string())
345}
346
347fn write_header(out: &mut String, contract: &Contract, stem: &str) {
348    use crate::schema::ContractKind;
349    let kind_tag = if contract.kind() == ContractKind::Kernel {
350        String::new()
351    } else {
352        format!(" [{}]", contract.kind())
353    };
354    let _ = writeln!(out, "{stem} (v{}){kind_tag}", contract.metadata.version);
355    let _ = writeln!(out, "{}", contract.metadata.description);
356    let _ = writeln!(out);
357}
358
359fn write_what(out: &mut String, contract: &Contract) {
360    let _ = writeln!(out, "What this contract specifies");
361
362    let refs = &contract.metadata.references;
363    if refs.is_empty() {
364        let _ = writeln!(
365            out,
366            "  This contract specifies {}.",
367            contract.metadata.description
368        );
369    } else {
370        let _ = write!(
371            out,
372            "  This contract specifies {}. It derives from",
373            contract.metadata.description
374        );
375        for (i, r) in refs.iter().enumerate() {
376            if i == 0 {
377                let _ = write!(out, " {r}");
378            } else {
379                let _ = write!(out, " and {r}");
380            }
381        }
382        let _ = writeln!(out, ".");
383    }
384
385    if !contract.metadata.depends_on.is_empty() {
386        let _ = write!(out, "  Depends on:");
387        for dep in &contract.metadata.depends_on {
388            let _ = write!(out, " {dep}");
389        }
390        let _ = writeln!(out);
391    }
392
393    let _ = writeln!(out);
394}
395
396fn write_equations(out: &mut String, contract: &Contract) {
397    if contract.equations.is_empty() {
398        return;
399    }
400    let _ = writeln!(out, "Governing equations");
401    let _ = writeln!(out);
402
403    for (name, eq) in &contract.equations {
404        let _ = writeln!(out, "  {name}");
405        let _ = writeln!(out, "    {}", eq.formula);
406
407        if let Some(ref dom) = eq.domain {
408            let _ = writeln!(out, "    Domain: {dom}");
409        }
410        if let Some(ref cod) = eq.codomain {
411            let _ = writeln!(out, "    Range:  {cod}");
412        }
413
414        if !eq.invariants.is_empty() {
415            let _ = writeln!(out);
416            let _ = writeln!(out, "    Invariants:");
417            for (i, inv) in eq.invariants.iter().enumerate() {
418                let _ = writeln!(out, "      {}. {inv}", i + 1);
419            }
420        }
421
422        if !eq.preconditions.is_empty() {
423            let _ = writeln!(out);
424            let _ = writeln!(out, "    Preconditions (caller must guarantee):");
425            for pre in &eq.preconditions {
426                let _ = writeln!(out, "      - {pre}");
427            }
428        }
429
430        if !eq.postconditions.is_empty() {
431            let _ = writeln!(out);
432            let _ = writeln!(out, "    Postconditions (kernel guarantees):");
433            for post in &eq.postconditions {
434                let _ = writeln!(out, "      - {post}");
435            }
436        }
437
438        if let Some(ref theorem) = eq.lean_theorem {
439            let _ = writeln!(out, "    Lean theorem: {theorem}");
440        }
441
442        let _ = writeln!(out);
443    }
444}