#![cfg(kani)]
use crate::engine::DsfbRoboticsEngine;
use crate::envelope::AdmissibilityEnvelope;
use crate::grammar::{GrammarState, ReasonCode};
use crate::platform::RobotContext;
use crate::policy::PolicyDecision;
use crate::Episode;
const N: usize = 3;
#[kani::proof]
#[kani::unwind(4)]
fn proof_engine_observe_bounded() {
let residuals: [f64; N] = kani::any();
let env = AdmissibilityEnvelope::new(0.1);
let mut eng = DsfbRoboticsEngine::<2, 2>::from_envelope(env);
let mut out: [Episode; N] = [Episode::empty(); N];
let cap: usize = kani::any();
kani::assume(cap <= N);
let n = eng.observe(&residuals, &mut out[..cap], RobotContext::ArmOperating);
assert!(n <= cap, "observe wrote past capacity");
}
#[kani::proof]
fn proof_grammar_severity_is_total_order() {
let reason_idx: u8 = kani::any();
kani::assume(reason_idx < 4);
let reason = match reason_idx {
0 => ReasonCode::SustainedOutwardDrift,
1 => ReasonCode::AbruptSlewViolation,
2 => ReasonCode::RecurrentBoundaryGrazing,
_ => ReasonCode::EnvelopeViolation,
};
let adm = GrammarState::Admissible.severity();
let bnd = GrammarState::Boundary(reason).severity();
let vio = GrammarState::Violation.severity();
assert!(adm < bnd);
assert!(bnd < vio);
}
#[kani::proof]
fn proof_policy_from_grammar_is_total() {
let reason_idx: u8 = kani::any();
kani::assume(reason_idx < 4);
let reason = match reason_idx {
0 => ReasonCode::SustainedOutwardDrift,
1 => ReasonCode::AbruptSlewViolation,
2 => ReasonCode::RecurrentBoundaryGrazing,
_ => ReasonCode::EnvelopeViolation,
};
for state in [
GrammarState::Admissible,
GrammarState::Boundary(reason),
GrammarState::Violation,
] {
let d = PolicyDecision::from_grammar(state);
assert!(matches!(
d,
PolicyDecision::Silent | PolicyDecision::Review | PolicyDecision::Escalate
));
}
}
#[kani::proof]
fn proof_envelope_violation_is_monotone_in_norm() {
let rho: f64 = kani::any();
kani::assume(rho.is_finite() && rho >= 0.0 && rho <= 1.0e6);
let env = AdmissibilityEnvelope::new(rho);
let n1: f64 = kani::any();
let n2: f64 = kani::any();
kani::assume(n1.is_finite() && n2.is_finite());
kani::assume(n1 >= 0.0 && n2 >= 0.0);
if n1 <= n2 {
assert!(!env.is_violation(n1, 1.0) || env.is_violation(n2, 1.0));
}
}
#[kani::proof]
fn proof_dataset_id_slug_roundtrip_is_total() {
use crate::datasets::DatasetId;
let idx: u8 = kani::any();
kani::assume(idx < 20);
let id = match idx {
0 => DatasetId::Cwru,
1 => DatasetId::Ims,
2 => DatasetId::KukaLwr,
3 => DatasetId::FemtoSt,
4 => DatasetId::PandaGaz,
5 => DatasetId::DlrJustin,
6 => DatasetId::Ur10Kufieta,
7 => DatasetId::Cheetah3,
8 => DatasetId::IcubPushRecovery,
9 => DatasetId::Droid,
10 => DatasetId::Openx,
11 => DatasetId::AnymalParkour,
12 => DatasetId::UnitreeG1,
13 => DatasetId::AlohaStatic,
14 => DatasetId::Icub3Sorrentino,
15 => DatasetId::MobileAloha,
16 => DatasetId::So100,
17 => DatasetId::AlohaStaticTape,
18 => DatasetId::AlohaStaticScrewDriver,
_ => DatasetId::AlohaStaticPingpongTest,
};
let slug = id.slug();
assert!(!slug.is_empty());
assert!(slug.len() < 64);
let parsed = DatasetId::from_slug(slug);
assert!(parsed.is_some());
assert_eq!(parsed.unwrap(), id);
}
#[kani::proof]
fn proof_dataset_id_family_is_total() {
use crate::datasets::{DatasetFamily, DatasetId};
let idx: u8 = kani::any();
kani::assume(idx < 20);
let id = match idx {
0 => DatasetId::Cwru,
1 => DatasetId::Ims,
2 => DatasetId::KukaLwr,
3 => DatasetId::FemtoSt,
4 => DatasetId::PandaGaz,
5 => DatasetId::DlrJustin,
6 => DatasetId::Ur10Kufieta,
7 => DatasetId::Cheetah3,
8 => DatasetId::IcubPushRecovery,
9 => DatasetId::Droid,
10 => DatasetId::Openx,
11 => DatasetId::AnymalParkour,
12 => DatasetId::UnitreeG1,
13 => DatasetId::AlohaStatic,
14 => DatasetId::Icub3Sorrentino,
15 => DatasetId::MobileAloha,
16 => DatasetId::So100,
17 => DatasetId::AlohaStaticTape,
18 => DatasetId::AlohaStaticScrewDriver,
_ => DatasetId::AlohaStaticPingpongTest,
};
let family = id.family();
assert!(matches!(
family,
DatasetFamily::Phm | DatasetFamily::Kinematics | DatasetFamily::Balancing
));
assert!(!family.label().is_empty());
}