1use serde::{Deserialize, Serialize};
2use std::collections::BTreeMap;
3
4pub use super::composition::{ShapeContract, ShapeExpr};
5pub use super::kind::ContractKind;
6
7#[derive(Debug, Clone, Default, Serialize, Deserialize)]
12pub struct Contract {
13 pub metadata: Metadata,
14 #[serde(default, deserialize_with = "deserialize_equations")]
23 pub equations: BTreeMap<String, Equation>,
24 #[serde(default)]
25 pub proof_obligations: Vec<ProofObligation>,
26 #[serde(default)]
27 pub kernel_structure: Option<KernelStructure>,
28 #[serde(default)]
29 pub simd_dispatch: BTreeMap<String, BTreeMap<String, String>>,
30 #[serde(default)]
31 pub enforcement: BTreeMap<String, EnforcementRule>,
32 #[serde(default)]
33 pub falsification_tests: Vec<FalsificationTest>,
34 #[serde(default)]
35 pub kani_harnesses: Vec<KaniHarness>,
36 #[serde(default)]
37 pub qa_gate: Option<QaGate>,
38 #[serde(default)]
40 pub verification_summary: Option<VerificationSummary>,
41 #[serde(default)]
43 pub type_invariants: Vec<TypeInvariant>,
44 #[serde(default)]
46 pub coq_spec: Option<CoqSpec>,
47}
48
49impl Contract {
50 pub fn is_registry(&self) -> bool {
52 self.metadata.registry || self.metadata.kind == ContractKind::Registry
53 }
54
55 pub fn kind(&self) -> ContractKind {
57 if self.metadata.registry && self.metadata.kind == ContractKind::Kernel {
58 ContractKind::Registry
59 } else {
60 self.metadata.kind
61 }
62 }
63
64 pub fn requires_proofs(&self) -> bool {
66 self.kind() == ContractKind::Kernel
67 }
68
69 pub fn provability_violations(&self) -> Vec<String> {
73 if !self.requires_proofs() {
74 return vec![];
75 }
76 let mut violations = Vec::new();
77 if self.proof_obligations.is_empty() {
78 violations.push("Kernel contract has no proof_obligations".into());
79 }
80 if self.falsification_tests.is_empty() {
81 violations.push("Kernel contract has no falsification_tests".into());
82 }
83 if self.kani_harnesses.is_empty() {
84 violations.push("Kernel contract has no kani_harnesses".into());
85 }
86 if self.falsification_tests.len() < self.proof_obligations.len() {
87 violations.push(format!(
88 "falsification_tests ({}) < proof_obligations ({})",
89 self.falsification_tests.len(),
90 self.proof_obligations.len(),
91 ));
92 }
93 violations
94 }
95}
96
97#[derive(Debug, Clone, Default, Serialize, Deserialize)]
99pub struct Metadata {
100 pub version: String,
101 #[serde(default)]
102 pub created: Option<String>,
103 #[serde(default)]
104 pub author: Option<String>,
105 pub description: String,
106 #[serde(default)]
107 pub references: Vec<String>,
108 #[serde(default)]
111 pub depends_on: Vec<String>,
112 #[serde(default)]
114 pub registry: bool,
115 #[serde(default)]
117 pub kind: ContractKind,
118 #[serde(default)]
122 pub enforcement_level: Option<EnforcementLevel>,
123 #[serde(default)]
126 pub locked_level: Option<String>,
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
131#[serde(rename_all = "lowercase")]
132pub enum EnforcementLevel {
133 Basic,
135 Standard,
137 Strict,
139 Proven,
141}
142
143#[derive(Debug, Clone, Default, Serialize, Deserialize)]
145pub struct Equation {
146 #[serde(default)]
150 pub formula: String,
151 #[serde(default)]
152 pub domain: Option<String>,
153 #[serde(default)]
154 pub codomain: Option<String>,
155 #[serde(default)]
156 pub invariants: Vec<String>,
157 #[serde(default)]
159 pub preconditions: Vec<String>,
160 #[serde(default)]
162 pub postconditions: Vec<String>,
163 #[serde(default)]
166 pub lean_theorem: Option<String>,
167 #[serde(default)]
169 pub float_tolerance: Option<f64>,
170 #[serde(default)]
173 pub assumes: Option<ShapeContract>,
174 #[serde(default)]
177 pub guarantees: Option<ShapeContract>,
178}
179
180#[derive(Debug, Clone, Default, Serialize, Deserialize)]
186pub struct ProofObligation {
187 #[serde(rename = "type", default)]
191 pub obligation_type: ObligationType,
192 #[serde(default, alias = "statement")]
197 pub property: String,
198 #[serde(default, alias = "verification")]
202 pub formal: Option<String>,
203 #[serde(default)]
204 pub tolerance: Option<f64>,
205 #[serde(default)]
206 pub applies_to: Option<AppliesTo>,
207 #[serde(default)]
209 pub lean: Option<LeanProof>,
210 #[serde(default)]
212 pub requires: Option<String>,
213 #[serde(default)]
215 pub applies_to_phase: Option<String>,
216 #[serde(default)]
218 pub parent_contract: Option<String>,
219}
220
221#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
222#[serde(rename_all = "lowercase")]
223pub enum ObligationType {
224 #[default]
225 Invariant,
226 Equivalence,
227 Bound,
228 Monotonicity,
229 Idempotency,
230 Linearity,
231 Symmetry,
232 Associativity,
233 Conservation,
234 Ordering,
235 Completeness,
236 Soundness,
237 Involution,
238 Determinism,
239 Roundtrip,
240 #[serde(rename = "state_machine")]
241 StateMachine,
242 Classification,
243 Independence,
244 Termination,
245 Safety,
249 Liveness,
253 Precondition,
255 Postcondition,
256 Frame,
257 #[serde(rename = "loop_invariant")]
258 LoopInvariant,
259 #[serde(rename = "loop_variant")]
260 LoopVariant,
261 #[serde(rename = "old_state")]
262 OldState,
263 Subcontract,
264}
265
266impl std::fmt::Display for ObligationType {
267 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
268 let s = match self {
269 Self::Invariant => "invariant",
270 Self::Equivalence => "equivalence",
271 Self::Bound => "bound",
272 Self::Monotonicity => "monotonicity",
273 Self::Idempotency => "idempotency",
274 Self::Linearity => "linearity",
275 Self::Symmetry => "symmetry",
276 Self::Associativity => "associativity",
277 Self::Conservation => "conservation",
278 Self::Ordering => "ordering",
279 Self::Completeness => "completeness",
280 Self::Soundness => "soundness",
281 Self::Involution => "involution",
282 Self::Determinism => "determinism",
283 Self::Roundtrip => "roundtrip",
284 Self::StateMachine => "state_machine",
285 Self::Classification => "classification",
286 Self::Independence => "independence",
287 Self::Termination => "termination",
288 Self::Safety => "safety",
289 Self::Liveness => "liveness",
290 Self::Precondition => "precondition",
291 Self::Postcondition => "postcondition",
292 Self::Frame => "frame",
293 Self::LoopInvariant => "loop_invariant",
294 Self::LoopVariant => "loop_variant",
295 Self::OldState => "old_state",
296 Self::Subcontract => "subcontract",
297 };
298 write!(f, "{s}")
299 }
300}
301
302#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
303#[serde(rename_all = "lowercase")]
304pub enum AppliesTo {
305 All,
306 Scalar,
307 Simd,
308 Converter,
309 #[serde(other)]
311 Other,
312}
313
314#[derive(Debug, Clone, Serialize, Deserialize)]
316pub struct KernelStructure {
317 pub phases: Vec<KernelPhase>,
318}
319
320#[derive(Debug, Clone, Serialize, Deserialize)]
321pub struct KernelPhase {
322 pub name: String,
323 pub description: String,
324 #[serde(default)]
325 pub invariant: Option<String>,
326}
327
328#[derive(Debug, Clone, Serialize, Deserialize)]
330pub struct EnforcementRule {
331 pub description: String,
332 #[serde(default)]
333 pub check: Option<String>,
334 #[serde(default)]
335 pub severity: Option<String>,
336 #[serde(default)]
337 pub reference: Option<String>,
338}
339
340#[derive(Debug, Clone, Default, Serialize, Deserialize)]
345pub struct FalsificationTest {
346 pub id: String,
347 #[serde(default, alias = "description")]
354 pub rule: String,
355 #[serde(default, alias = "expected")]
360 pub prediction: String,
361 #[serde(default, alias = "command")]
364 pub test: Option<String>,
365 #[serde(default, alias = "fails_if")]
368 pub if_fails: String,
369}
370
371#[derive(Debug, Clone, Default, Serialize, Deserialize)]
375pub struct KaniHarness {
376 pub id: String,
377 pub obligation: String,
378 #[serde(default)]
379 pub property: Option<String>,
380 #[serde(default)]
381 pub bound: Option<u32>,
382 #[serde(default)]
383 pub strategy: Option<KaniStrategy>,
384 #[serde(default)]
385 pub solver: Option<String>,
386 #[serde(default)]
387 pub harness: Option<String>,
388 #[serde(default)]
393 pub actually_verified: Option<bool>,
394}
395
396#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
397#[serde(rename_all = "snake_case")]
398pub enum KaniStrategy {
399 Exhaustive,
400 StubFloat,
401 Compositional,
402 BoundedInt,
403}
404
405impl std::fmt::Display for KaniStrategy {
406 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
407 let s = match self {
408 Self::Exhaustive => "exhaustive",
409 Self::StubFloat => "stub_float",
410 Self::Compositional => "compositional",
411 Self::BoundedInt => "bounded_int",
412 };
413 write!(f, "{s}")
414 }
415}
416
417#[derive(Debug, Clone, Serialize, Deserialize)]
419pub struct LeanProof {
420 pub theorem: String,
422 #[serde(default)]
424 pub module: Option<String>,
425 #[serde(default)]
427 pub status: LeanStatus,
428 #[serde(default)]
430 pub depends_on: Vec<String>,
431 #[serde(default)]
433 pub mathlib_imports: Vec<String>,
434 #[serde(default)]
436 pub notes: Option<String>,
437}
438
439#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
441#[serde(rename_all = "kebab-case")]
442pub enum LeanStatus {
443 Proved,
445 #[default]
447 Sorry,
448 Wip,
450 NotApplicable,
452}
453
454impl std::fmt::Display for LeanStatus {
455 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
456 let s = match self {
457 Self::Proved => "proved",
458 Self::Sorry => "sorry",
459 Self::Wip => "wip",
460 Self::NotApplicable => "not-applicable",
461 };
462 write!(f, "{s}")
463 }
464}
465
466#[derive(Debug, Clone, Serialize, Deserialize)]
468pub struct VerificationSummary {
469 pub total_obligations: u32,
470 #[serde(default)]
471 pub l2_property_tested: u32,
472 #[serde(default)]
473 pub l3_kani_proved: u32,
474 #[serde(default)]
475 pub l4_lean_proved: u32,
476 #[serde(default)]
477 pub l4_sorry_count: u32,
478 #[serde(default)]
479 pub l4_not_applicable: u32,
480}
481
482#[derive(Debug, Clone, Default, Serialize, Deserialize)]
489pub struct QaGate {
490 #[serde(default)]
491 pub id: String,
492 #[serde(default)]
493 pub name: String,
494 #[serde(default)]
495 pub description: Option<String>,
496 #[serde(default)]
497 pub checks: Vec<String>,
498 #[serde(default)]
499 pub pass_criteria: Option<String>,
500 #[serde(default)]
501 pub falsification: Option<String>,
502}
503
504#[derive(Debug, Clone, Serialize, Deserialize)]
509pub struct TypeInvariant {
510 pub name: String,
511 #[serde(rename = "type")]
513 pub type_name: String,
514 pub predicate: String,
516 #[serde(default)]
517 pub description: Option<String>,
518}
519
520#[derive(Debug, Clone, Serialize, Deserialize)]
522pub struct CoqSpec {
523 pub module: String,
525 #[serde(default)]
527 pub imports: Vec<String>,
528 #[serde(default)]
530 pub definitions: Vec<CoqDefinition>,
531 #[serde(default)]
533 pub obligations: Vec<CoqObligation>,
534}
535
536#[derive(Debug, Clone, Serialize, Deserialize)]
538pub struct CoqDefinition {
539 pub name: String,
540 pub statement: String,
541}
542
543#[derive(Debug, Clone, Serialize, Deserialize)]
545pub struct CoqObligation {
546 pub links_to: String,
548 pub coq_lemma: String,
550 #[serde(default = "coq_status_default")]
552 pub status: String,
553}
554
555fn coq_status_default() -> String {
556 "stub".to_string()
557}
558
559fn deserialize_equations<'de, D>(d: D) -> Result<BTreeMap<String, Equation>, D::Error>
565where
566 D: serde::Deserializer<'de>,
567{
568 use serde::de::Error;
569 use serde_yaml::Value;
570
571 let value = Value::deserialize(d)?;
572 match value {
573 Value::Null => Ok(BTreeMap::new()),
574 Value::Mapping(_) => serde_yaml::from_value(value).map_err(D::Error::custom),
575 Value::Sequence(items) => {
576 let mut out = BTreeMap::new();
577 for (i, mut item) in items.into_iter().enumerate() {
578 let key = match &mut item {
579 Value::Mapping(m) => m
580 .remove(Value::String("id".into()))
581 .and_then(|v| v.as_str().map(ToString::to_string))
582 .unwrap_or_else(|| format!("equation_{i}")),
583 _ => format!("equation_{i}"),
584 };
585 let eq: Equation = serde_yaml::from_value(item).map_err(D::Error::custom)?;
586 out.insert(key, eq);
587 }
588 Ok(out)
589 }
590 other => Err(D::Error::custom(format!(
591 "`equations:` must be a map or a list; got {other:?}"
592 ))),
593 }
594}
595
596#[cfg(test)]
597#[path = "types_tests.rs"]
598mod tests;