provable_contracts/audit/
mod.rs1use crate::binding::{BindingRegistry, ImplStatus};
8use crate::error::{Severity, Violation};
9use crate::schema::Contract;
10
11#[derive(Debug, Clone)]
13pub struct AuditReport {
14 pub equations: usize,
16 pub obligations: usize,
18 pub falsification_tests: usize,
20 pub kani_harnesses: usize,
22 pub violations: Vec<Violation>,
24}
25
26pub fn audit_contract(contract: &Contract) -> AuditReport {
32 let mut violations = Vec::new();
33
34 let is_kernel_kind = contract.requires_proofs();
38
39 if is_kernel_kind && contract.falsification_tests.is_empty() {
41 violations.push(Violation {
42 severity: Severity::Warning,
43 rule: "AUDIT-001".to_string(),
44 message: "No falsification tests — contract is \
45 not falsifiable"
46 .to_string(),
47 location: Some("falsification_tests".to_string()),
48 });
49 }
50
51 let obligation_props: Vec<&str> = contract
53 .proof_obligations
54 .iter()
55 .map(|o| o.property.as_str())
56 .collect();
57
58 for harness in &contract.kani_harnesses {
59 if harness.obligation.is_empty() {
63 violations.push(Violation {
64 severity: Severity::Error,
65 rule: "AUDIT-002".to_string(),
66 message: format!(
67 "Kani harness {} has empty obligation \
68 reference",
69 harness.id
70 ),
71 location: Some(format!("kani_harnesses.{}.obligation", harness.id)),
72 });
73 }
74 }
75
76 if is_kernel_kind && !contract.equations.is_empty() && contract.proof_obligations.is_empty() {
78 violations.push(Violation {
79 severity: Severity::Warning,
80 rule: "AUDIT-003".to_string(),
81 message: "Equations defined but no proof obligations \
82 — obligations should be derived from equations"
83 .to_string(),
84 location: Some("proof_obligations".to_string()),
85 });
86 }
87
88 let _ = obligation_props;
90
91 AuditReport {
92 equations: contract.equations.len(),
93 obligations: contract.proof_obligations.len(),
94 falsification_tests: contract.falsification_tests.len(),
95 kani_harnesses: contract.kani_harnesses.len(),
96 violations,
97 }
98}
99
100#[derive(Debug, Clone)]
102pub struct BindingAuditReport {
103 pub total_equations: usize,
105 pub bound_equations: usize,
107 pub implemented: usize,
109 pub partial: usize,
111 pub not_implemented: usize,
113 pub total_obligations: usize,
115 pub covered_obligations: usize,
117 pub violations: Vec<Violation>,
119}
120
121pub fn audit_binding(
126 contracts: &[(&str, &Contract)],
127 binding: &BindingRegistry,
128) -> BindingAuditReport {
129 let mut violations = Vec::new();
130 let mut total_equations = 0usize;
131 let mut bound_equations = 0usize;
132 let mut implemented = 0usize;
133 let mut partial = 0usize;
134 let mut not_implemented = 0usize;
135 let mut total_obligations = 0usize;
136 let mut covered_obligations = 0usize;
137
138 for &(contract_file, contract) in contracts {
139 let eq_count = contract.equations.len();
140 total_equations += eq_count;
141 total_obligations += contract.proof_obligations.len();
142
143 for eq_name in contract.equations.keys() {
144 let matching = binding.find_binding(contract_file, eq_name);
145
146 match matching {
147 Some(b) => {
148 bound_equations += 1;
149 match b.status {
150 ImplStatus::Implemented => {
151 implemented += 1;
152 covered_obligations += obligations_for_equation(contract);
155 }
156 ImplStatus::Partial => {
157 partial += 1;
158 violations.push(Violation {
159 severity: Severity::Warning,
160 rule: "BIND-002".to_string(),
161 message: format!(
162 "Equation '{eq_name}' in \
163 {contract_file} is partially \
164 implemented"
165 ),
166 location: Some(format!("bindings.{eq_name}")),
167 });
168 }
169 ImplStatus::NotImplemented => {
170 not_implemented += 1;
171 violations.push(Violation {
172 severity: Severity::Warning,
173 rule: "BIND-003".to_string(),
174 message: format!(
175 "Equation '{eq_name}' in \
176 {contract_file} has no \
177 implementation"
178 ),
179 location: Some(format!("bindings.{eq_name}")),
180 });
181 }
182 ImplStatus::Pending => {
183 not_implemented += 1;
184 violations.push(Violation {
185 severity: Severity::Warning,
186 rule: "BIND-004".to_string(),
187 message: format!(
188 "Equation '{eq_name}' in \
189 {contract_file} is pending \
190 implementation"
191 ),
192 location: Some(format!("bindings.{eq_name}")),
193 });
194 }
195 }
196 }
197 None => {
198 violations.push(Violation {
199 severity: Severity::Error,
200 rule: "BIND-001".to_string(),
201 message: format!(
202 "Equation '{eq_name}' in {contract_file} \
203 has no binding entry"
204 ),
205 location: Some(format!("{contract_file}.equations.{eq_name}")),
206 });
207 }
208 }
209 }
210 }
211
212 BindingAuditReport {
213 total_equations,
214 bound_equations,
215 implemented,
216 partial,
217 not_implemented,
218 total_obligations,
219 covered_obligations,
220 violations,
221 }
222}
223
224fn obligations_for_equation(contract: &Contract) -> usize {
230 contract
231 .proof_obligations
232 .iter()
233 .filter(|o| o.applies_to != Some(crate::schema::AppliesTo::Simd))
234 .count()
235}
236
237#[cfg(test)]
238mod tests {
239 include!("mod_tests.rs");
240}