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}
389
390#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
391#[serde(rename_all = "snake_case")]
392pub enum KaniStrategy {
393 Exhaustive,
394 StubFloat,
395 Compositional,
396 BoundedInt,
397}
398
399impl std::fmt::Display for KaniStrategy {
400 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
401 let s = match self {
402 Self::Exhaustive => "exhaustive",
403 Self::StubFloat => "stub_float",
404 Self::Compositional => "compositional",
405 Self::BoundedInt => "bounded_int",
406 };
407 write!(f, "{s}")
408 }
409}
410
411#[derive(Debug, Clone, Serialize, Deserialize)]
413pub struct LeanProof {
414 pub theorem: String,
416 #[serde(default)]
418 pub module: Option<String>,
419 #[serde(default)]
421 pub status: LeanStatus,
422 #[serde(default)]
424 pub depends_on: Vec<String>,
425 #[serde(default)]
427 pub mathlib_imports: Vec<String>,
428 #[serde(default)]
430 pub notes: Option<String>,
431}
432
433#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
435#[serde(rename_all = "kebab-case")]
436pub enum LeanStatus {
437 Proved,
439 #[default]
441 Sorry,
442 Wip,
444 NotApplicable,
446}
447
448impl std::fmt::Display for LeanStatus {
449 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
450 let s = match self {
451 Self::Proved => "proved",
452 Self::Sorry => "sorry",
453 Self::Wip => "wip",
454 Self::NotApplicable => "not-applicable",
455 };
456 write!(f, "{s}")
457 }
458}
459
460#[derive(Debug, Clone, Serialize, Deserialize)]
462pub struct VerificationSummary {
463 pub total_obligations: u32,
464 #[serde(default)]
465 pub l2_property_tested: u32,
466 #[serde(default)]
467 pub l3_kani_proved: u32,
468 #[serde(default)]
469 pub l4_lean_proved: u32,
470 #[serde(default)]
471 pub l4_sorry_count: u32,
472 #[serde(default)]
473 pub l4_not_applicable: u32,
474}
475
476#[derive(Debug, Clone, Default, Serialize, Deserialize)]
483pub struct QaGate {
484 #[serde(default)]
485 pub id: String,
486 #[serde(default)]
487 pub name: String,
488 #[serde(default)]
489 pub description: Option<String>,
490 #[serde(default)]
491 pub checks: Vec<String>,
492 #[serde(default)]
493 pub pass_criteria: Option<String>,
494 #[serde(default)]
495 pub falsification: Option<String>,
496}
497
498#[derive(Debug, Clone, Serialize, Deserialize)]
503pub struct TypeInvariant {
504 pub name: String,
505 #[serde(rename = "type")]
507 pub type_name: String,
508 pub predicate: String,
510 #[serde(default)]
511 pub description: Option<String>,
512}
513
514#[derive(Debug, Clone, Serialize, Deserialize)]
516pub struct CoqSpec {
517 pub module: String,
519 #[serde(default)]
521 pub imports: Vec<String>,
522 #[serde(default)]
524 pub definitions: Vec<CoqDefinition>,
525 #[serde(default)]
527 pub obligations: Vec<CoqObligation>,
528}
529
530#[derive(Debug, Clone, Serialize, Deserialize)]
532pub struct CoqDefinition {
533 pub name: String,
534 pub statement: String,
535}
536
537#[derive(Debug, Clone, Serialize, Deserialize)]
539pub struct CoqObligation {
540 pub links_to: String,
542 pub coq_lemma: String,
544 #[serde(default = "coq_status_default")]
546 pub status: String,
547}
548
549fn coq_status_default() -> String {
550 "stub".to_string()
551}
552
553fn deserialize_equations<'de, D>(d: D) -> Result<BTreeMap<String, Equation>, D::Error>
559where
560 D: serde::Deserializer<'de>,
561{
562 use serde::de::Error;
563 use serde_yaml::Value;
564
565 let value = Value::deserialize(d)?;
566 match value {
567 Value::Null => Ok(BTreeMap::new()),
568 Value::Mapping(_) => serde_yaml::from_value(value).map_err(D::Error::custom),
569 Value::Sequence(items) => {
570 let mut out = BTreeMap::new();
571 for (i, mut item) in items.into_iter().enumerate() {
572 let key = match &mut item {
573 Value::Mapping(m) => m
574 .remove(Value::String("id".into()))
575 .and_then(|v| v.as_str().map(ToString::to_string))
576 .unwrap_or_else(|| format!("equation_{i}")),
577 _ => format!("equation_{i}"),
578 };
579 let eq: Equation = serde_yaml::from_value(item).map_err(D::Error::custom)?;
580 out.insert(key, eq);
581 }
582 Ok(out)
583 }
584 other => Err(D::Error::custom(format!(
585 "`equations:` must be a map or a list; got {other:?}"
586 ))),
587 }
588}
589
590#[cfg(test)]
591#[path = "types_tests.rs"]
592mod tests;