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 #[serde(default)]
51 pub beat: Option<Beat>,
52}
53
54#[derive(Debug, Clone, Default, Serialize, Deserialize)]
61pub struct Beat {
62 #[serde(default)]
64 pub pillar: Option<u8>,
65 #[serde(default)]
67 pub incumbent: String,
68 #[serde(default)]
70 pub incumbent_pinned: Option<String>,
71 #[serde(default)]
73 pub canonical_task: Option<String>,
74 #[serde(default)]
76 pub metric: String,
77 #[serde(default)]
79 pub direction: String,
80 #[serde(default)]
82 pub baseline_value: Option<f64>,
83 #[serde(default)]
85 pub baseline_floor: Option<f64>,
86 #[serde(default)]
88 pub beat_threshold: Option<f64>,
89 #[serde(default)]
91 pub baseline_sourced_date: Option<String>,
92 #[serde(default)]
94 pub approved_compute: Option<String>,
95 #[serde(default)]
97 pub ci_gate_name: String,
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
103#[serde(rename_all = "lowercase")]
104pub enum BeatOutcome {
105 Won,
108 Regressed,
110}
111
112impl Beat {
113 #[must_use]
124 pub fn evaluate(&self, measured: f64) -> Option<BeatOutcome> {
125 let threshold = self.beat_threshold?;
126 if !threshold.is_finite() || !measured.is_finite() {
127 return None;
128 }
129 match self.direction.trim() {
130 "higher_is_better" => Some(if measured >= threshold {
131 BeatOutcome::Won
132 } else {
133 BeatOutcome::Regressed
134 }),
135 "lower_is_better" => Some(if measured <= threshold {
136 BeatOutcome::Won
137 } else {
138 BeatOutcome::Regressed
139 }),
140 _ => None,
141 }
142 }
143
144 #[must_use]
147 pub fn is_won(&self, measured: f64) -> bool {
148 self.evaluate(measured) == Some(BeatOutcome::Won)
149 }
150}
151
152impl Contract {
153 pub fn is_registry(&self) -> bool {
155 self.metadata.registry || self.metadata.kind == ContractKind::Registry
156 }
157
158 pub fn kind(&self) -> ContractKind {
160 if self.metadata.registry && self.metadata.kind == ContractKind::Kernel {
161 ContractKind::Registry
162 } else {
163 self.metadata.kind
164 }
165 }
166
167 pub fn requires_proofs(&self) -> bool {
169 self.kind() == ContractKind::Kernel
170 }
171
172 pub fn provability_violations(&self) -> Vec<String> {
176 if !self.requires_proofs() {
177 return vec![];
178 }
179 let mut violations = Vec::new();
180 if self.proof_obligations.is_empty() {
181 violations.push("Kernel contract has no proof_obligations".into());
182 }
183 if self.falsification_tests.is_empty() {
184 violations.push("Kernel contract has no falsification_tests".into());
185 }
186 if self.kani_harnesses.is_empty() {
187 violations.push("Kernel contract has no kani_harnesses".into());
188 }
189 if self.falsification_tests.len() < self.proof_obligations.len() {
190 violations.push(format!(
191 "falsification_tests ({}) < proof_obligations ({})",
192 self.falsification_tests.len(),
193 self.proof_obligations.len(),
194 ));
195 }
196 violations
197 }
198}
199
200#[derive(Debug, Clone, Default, Serialize, Deserialize)]
202pub struct Metadata {
203 pub version: String,
204 #[serde(default)]
205 pub created: Option<String>,
206 #[serde(default)]
207 pub author: Option<String>,
208 pub description: String,
209 #[serde(default)]
210 pub references: Vec<String>,
211 #[serde(default)]
214 pub depends_on: Vec<String>,
215 #[serde(default)]
217 pub registry: bool,
218 #[serde(default)]
220 pub kind: ContractKind,
221 #[serde(default)]
225 pub enforcement_level: Option<EnforcementLevel>,
226 #[serde(default)]
229 pub locked_level: Option<String>,
230}
231
232#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
234#[serde(rename_all = "lowercase")]
235pub enum EnforcementLevel {
236 Basic,
238 Standard,
240 Strict,
242 Proven,
244}
245
246#[derive(Debug, Clone, Default, Serialize, Deserialize)]
248pub struct Equation {
249 #[serde(default)]
253 pub formula: String,
254 #[serde(default)]
255 pub domain: Option<String>,
256 #[serde(default)]
257 pub codomain: Option<String>,
258 #[serde(default)]
259 pub invariants: Vec<String>,
260 #[serde(default)]
262 pub preconditions: Vec<String>,
263 #[serde(default)]
265 pub postconditions: Vec<String>,
266 #[serde(default)]
269 pub lean_theorem: Option<String>,
270 #[serde(default)]
272 pub float_tolerance: Option<f64>,
273 #[serde(default)]
276 pub assumes: Option<ShapeContract>,
277 #[serde(default)]
280 pub guarantees: Option<ShapeContract>,
281}
282
283#[derive(Debug, Clone, Default, Serialize, Deserialize)]
289pub struct ProofObligation {
290 #[serde(rename = "type", default)]
294 pub obligation_type: ObligationType,
295 #[serde(default, alias = "statement")]
300 pub property: String,
301 #[serde(default, alias = "verification")]
305 pub formal: Option<String>,
306 #[serde(default)]
307 pub tolerance: Option<f64>,
308 #[serde(default)]
309 pub applies_to: Option<AppliesTo>,
310 #[serde(default)]
312 pub lean: Option<LeanProof>,
313 #[serde(default)]
315 pub requires: Option<String>,
316 #[serde(default)]
318 pub applies_to_phase: Option<String>,
319 #[serde(default)]
321 pub parent_contract: Option<String>,
322}
323
324#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
325#[serde(rename_all = "lowercase")]
326pub enum ObligationType {
327 #[default]
328 Invariant,
329 Equivalence,
330 Bound,
331 Monotonicity,
332 Idempotency,
333 Linearity,
334 Symmetry,
335 Associativity,
336 Conservation,
337 Ordering,
338 Completeness,
339 Soundness,
340 Involution,
341 Determinism,
342 Roundtrip,
343 #[serde(rename = "state_machine")]
344 StateMachine,
345 Classification,
346 Independence,
347 Termination,
348 Safety,
352 Liveness,
356 Precondition,
358 Postcondition,
359 Frame,
360 #[serde(rename = "loop_invariant")]
361 LoopInvariant,
362 #[serde(rename = "loop_variant")]
363 LoopVariant,
364 #[serde(rename = "old_state")]
365 OldState,
366 Subcontract,
367}
368
369impl std::fmt::Display for ObligationType {
370 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
371 let s = match self {
372 Self::Invariant => "invariant",
373 Self::Equivalence => "equivalence",
374 Self::Bound => "bound",
375 Self::Monotonicity => "monotonicity",
376 Self::Idempotency => "idempotency",
377 Self::Linearity => "linearity",
378 Self::Symmetry => "symmetry",
379 Self::Associativity => "associativity",
380 Self::Conservation => "conservation",
381 Self::Ordering => "ordering",
382 Self::Completeness => "completeness",
383 Self::Soundness => "soundness",
384 Self::Involution => "involution",
385 Self::Determinism => "determinism",
386 Self::Roundtrip => "roundtrip",
387 Self::StateMachine => "state_machine",
388 Self::Classification => "classification",
389 Self::Independence => "independence",
390 Self::Termination => "termination",
391 Self::Safety => "safety",
392 Self::Liveness => "liveness",
393 Self::Precondition => "precondition",
394 Self::Postcondition => "postcondition",
395 Self::Frame => "frame",
396 Self::LoopInvariant => "loop_invariant",
397 Self::LoopVariant => "loop_variant",
398 Self::OldState => "old_state",
399 Self::Subcontract => "subcontract",
400 };
401 write!(f, "{s}")
402 }
403}
404
405#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
406#[serde(rename_all = "lowercase")]
407pub enum AppliesTo {
408 All,
409 Scalar,
410 Simd,
411 Converter,
412 #[serde(other)]
414 Other,
415}
416
417#[derive(Debug, Clone, Serialize, Deserialize)]
419pub struct KernelStructure {
420 pub phases: Vec<KernelPhase>,
421}
422
423#[derive(Debug, Clone, Serialize, Deserialize)]
424pub struct KernelPhase {
425 pub name: String,
426 pub description: String,
427 #[serde(default)]
428 pub invariant: Option<String>,
429}
430
431#[derive(Debug, Clone, Serialize, Deserialize)]
433pub struct EnforcementRule {
434 pub description: String,
435 #[serde(default)]
436 pub check: Option<String>,
437 #[serde(default)]
438 pub severity: Option<String>,
439 #[serde(default)]
440 pub reference: Option<String>,
441}
442
443#[derive(Debug, Clone, Default, Serialize, Deserialize)]
448pub struct FalsificationTest {
449 pub id: String,
450 #[serde(default, alias = "description")]
457 pub rule: String,
458 #[serde(default, alias = "expected")]
463 pub prediction: String,
464 #[serde(default, alias = "command")]
467 pub test: Option<String>,
468 #[serde(default, alias = "fails_if")]
471 pub if_fails: String,
472}
473
474#[derive(Debug, Clone, Default, Serialize, Deserialize)]
478pub struct KaniHarness {
479 pub id: String,
480 pub obligation: String,
481 #[serde(default)]
482 pub property: Option<String>,
483 #[serde(default)]
484 pub bound: Option<u32>,
485 #[serde(default)]
486 pub strategy: Option<KaniStrategy>,
487 #[serde(default)]
488 pub solver: Option<String>,
489 #[serde(default)]
490 pub harness: Option<String>,
491 #[serde(default)]
496 pub actually_verified: Option<bool>,
497}
498
499#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
500#[serde(rename_all = "snake_case")]
501pub enum KaniStrategy {
502 Exhaustive,
503 StubFloat,
504 Compositional,
505 BoundedInt,
506}
507
508impl std::fmt::Display for KaniStrategy {
509 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
510 let s = match self {
511 Self::Exhaustive => "exhaustive",
512 Self::StubFloat => "stub_float",
513 Self::Compositional => "compositional",
514 Self::BoundedInt => "bounded_int",
515 };
516 write!(f, "{s}")
517 }
518}
519
520#[derive(Debug, Clone, Serialize, Deserialize)]
522pub struct LeanProof {
523 pub theorem: String,
525 #[serde(default)]
527 pub module: Option<String>,
528 #[serde(default)]
530 pub status: LeanStatus,
531 #[serde(default)]
533 pub depends_on: Vec<String>,
534 #[serde(default)]
536 pub mathlib_imports: Vec<String>,
537 #[serde(default)]
539 pub notes: Option<String>,
540}
541
542#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
544#[serde(rename_all = "kebab-case")]
545pub enum LeanStatus {
546 Proved,
548 #[default]
550 Sorry,
551 Wip,
553 NotApplicable,
555}
556
557impl std::fmt::Display for LeanStatus {
558 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
559 let s = match self {
560 Self::Proved => "proved",
561 Self::Sorry => "sorry",
562 Self::Wip => "wip",
563 Self::NotApplicable => "not-applicable",
564 };
565 write!(f, "{s}")
566 }
567}
568
569#[derive(Debug, Clone, Serialize, Deserialize)]
571pub struct VerificationSummary {
572 pub total_obligations: u32,
573 #[serde(default)]
574 pub l2_property_tested: u32,
575 #[serde(default)]
576 pub l3_kani_proved: u32,
577 #[serde(default)]
578 pub l4_lean_proved: u32,
579 #[serde(default)]
580 pub l4_sorry_count: u32,
581 #[serde(default)]
582 pub l4_not_applicable: u32,
583}
584
585#[derive(Debug, Clone, Default, Serialize, Deserialize)]
592pub struct QaGate {
593 #[serde(default)]
594 pub id: String,
595 #[serde(default)]
596 pub name: String,
597 #[serde(default)]
598 pub description: Option<String>,
599 #[serde(default)]
600 pub checks: Vec<String>,
601 #[serde(default)]
602 pub pass_criteria: Option<String>,
603 #[serde(default)]
604 pub falsification: Option<String>,
605}
606
607#[derive(Debug, Clone, Serialize, Deserialize)]
612pub struct TypeInvariant {
613 pub name: String,
614 #[serde(rename = "type")]
616 pub type_name: String,
617 pub predicate: String,
619 #[serde(default)]
620 pub description: Option<String>,
621}
622
623#[derive(Debug, Clone, Serialize, Deserialize)]
625pub struct CoqSpec {
626 pub module: String,
628 #[serde(default)]
630 pub imports: Vec<String>,
631 #[serde(default)]
633 pub definitions: Vec<CoqDefinition>,
634 #[serde(default)]
636 pub obligations: Vec<CoqObligation>,
637}
638
639#[derive(Debug, Clone, Serialize, Deserialize)]
641pub struct CoqDefinition {
642 pub name: String,
643 pub statement: String,
644}
645
646#[derive(Debug, Clone, Serialize, Deserialize)]
648pub struct CoqObligation {
649 pub links_to: String,
651 pub coq_lemma: String,
653 #[serde(default = "coq_status_default")]
655 pub status: String,
656}
657
658fn coq_status_default() -> String {
659 "stub".to_string()
660}
661
662fn deserialize_equations<'de, D>(d: D) -> Result<BTreeMap<String, Equation>, D::Error>
668where
669 D: serde::Deserializer<'de>,
670{
671 use serde::de::Error;
672 use serde_yaml::Value;
673
674 let value = Value::deserialize(d)?;
675 match value {
676 Value::Null => Ok(BTreeMap::new()),
677 Value::Mapping(_) => serde_yaml::from_value(value).map_err(D::Error::custom),
678 Value::Sequence(items) => {
679 let mut out = BTreeMap::new();
680 for (i, mut item) in items.into_iter().enumerate() {
681 let key = match &mut item {
682 Value::Mapping(m) => m
683 .remove(Value::String("id".into()))
684 .and_then(|v| v.as_str().map(ToString::to_string))
685 .unwrap_or_else(|| format!("equation_{i}")),
686 _ => format!("equation_{i}"),
687 };
688 let eq: Equation = serde_yaml::from_value(item).map_err(D::Error::custom)?;
689 out.insert(key, eq);
690 }
691 Ok(out)
692 }
693 other => Err(D::Error::custom(format!(
694 "`equations:` must be a map or a list; got {other:?}"
695 ))),
696 }
697}
698
699#[cfg(test)]
700#[path = "types_tests.rs"]
701mod tests;