aprender_contracts_cli/commands/
audit.rs1use std::path::Path;
2
3use provable_contracts::audit::{audit_binding, audit_contract};
4use provable_contracts::binding::parse_binding;
5use provable_contracts::error::Severity;
6use provable_contracts::schema::{parse_contract, Contract};
7
8pub fn run(path: &Path, binding_path: Option<&Path>) -> Result<(), Box<dyn std::error::Error>> {
9 let contract = parse_contract(path)?;
10
11 let report = audit_contract(&contract);
12 print_traceability_header(&contract, &report);
13 print_lean_status(&contract);
14 print_coq_status(&contract);
15 print_violations(&report.violations);
16
17 let errors = count_errors(&report.violations);
18 let binding_errors = run_binding_audit(path, &contract, binding_path)?;
19
20 let total = errors + binding_errors;
21 if total > 0 {
22 return Err(format!("Audit found {total} error(s)").into());
23 }
24 Ok(())
25}
26
27fn print_traceability_header(contract: &Contract, report: &provable_contracts::audit::AuditReport) {
28 println!("Traceability Audit");
29 println!("==================");
30 println!("Equations: {}", report.equations);
31 println!("Proof obligations: {}", report.obligations);
32 println!("Falsification tests: {}", report.falsification_tests);
33 println!("Kani harnesses: {}", report.kani_harnesses);
34 println!("Type invariants: {}", contract.type_invariants.len());
35}
36
37fn print_lean_status(contract: &Contract) {
38 let lean_proved = contract
39 .verification_summary
40 .as_ref()
41 .map_or(0, |vs| vs.l4_lean_proved);
42 if lean_proved == 0 {
43 return;
44 }
45 let total = contract
46 .verification_summary
47 .as_ref()
48 .map_or(0, |vs| vs.total_obligations);
49 println!("Lean proved: {lean_proved}/{total}");
50}
51
52fn print_coq_status(contract: &Contract) {
53 let Some(spec) = contract.coq_spec.as_ref() else {
54 return;
55 };
56 let total = spec.obligations.len();
57 let proved = spec
58 .obligations
59 .iter()
60 .filter(|o| o.status == "proved")
61 .count();
62 let admitted = spec
63 .obligations
64 .iter()
65 .filter(|o| o.status == "admitted")
66 .count();
67 let stubs = total - proved - admitted;
68 let suffix = if total > 0 {
69 format!(" {total} obligations —")
70 } else {
71 " no obligation links".to_string()
72 };
73 println!(
74 "Coq ({}):{suffix} {proved} proved, {admitted} admitted, {stubs} stub",
75 spec.module
76 );
77}
78
79fn print_violations(violations: &[provable_contracts::error::Violation]) {
80 println!();
81 if violations.is_empty() {
82 println!("No audit findings.");
83 } else {
84 for v in violations {
85 println!("{v}");
86 }
87 }
88}
89
90fn count_errors(violations: &[provable_contracts::error::Violation]) -> usize {
91 violations
92 .iter()
93 .filter(|v| v.severity == Severity::Error)
94 .count()
95}
96
97fn run_binding_audit(
98 path: &Path,
99 contract: &Contract,
100 binding_path: Option<&Path>,
101) -> Result<usize, Box<dyn std::error::Error>> {
102 let Some(bp) = binding_path else {
103 return Ok(0);
104 };
105 let binding = parse_binding(bp)?;
106 let contract_file = path
107 .file_name()
108 .and_then(|n| n.to_str())
109 .unwrap_or("unknown");
110 let binding_report = audit_binding(&[(contract_file, contract)], &binding);
111
112 println!();
113 println!("Binding Audit");
114 println!("=============");
115 println!("Total equations: {}", binding_report.total_equations);
116 println!("Bound equations: {}", binding_report.bound_equations);
117 println!("Implemented: {}", binding_report.implemented);
118 println!("Partial: {}", binding_report.partial);
119 println!("Not implemented: {}", binding_report.not_implemented);
120 println!("Obligations total: {}", binding_report.total_obligations);
121 println!(
122 "Obligations covered: {}",
123 binding_report.covered_obligations
124 );
125 println!();
126
127 if binding_report.violations.is_empty() {
128 println!("No binding gaps found.");
129 } else {
130 for v in &binding_report.violations {
131 println!("{v}");
132 }
133 }
134 Ok(count_errors(&binding_report.violations))
135}