pub mod adversarial;
pub mod docs;
pub mod fairness;
pub mod robustness;
pub mod security;
pub mod velocity;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct QaChecklist {
pub model_path: PathBuf,
pub test_data: Option<PathBuf>,
pub protected_attrs: Vec<String>,
pub latency_sla: Duration,
pub memory_budget: usize,
pub max_turns: u32,
}
impl Default for QaChecklist {
fn default() -> Self {
Self {
model_path: PathBuf::new(),
test_data: None,
protected_attrs: Vec::new(),
latency_sla: Duration::from_millis(100),
memory_budget: 512 * 1024 * 1024, max_turns: 5,
}
}
}
impl QaChecklist {
#[must_use]
pub fn new(model_path: PathBuf) -> Self {
Self {
model_path,
..Default::default()
}
}
#[must_use]
pub fn with_test_data(mut self, path: PathBuf) -> Self {
self.test_data = Some(path);
self
}
#[must_use]
pub fn with_protected_attrs(mut self, attrs: Vec<String>) -> Self {
self.protected_attrs = attrs;
self
}
#[must_use]
pub fn with_latency_sla(mut self, sla: Duration) -> Self {
self.latency_sla = sla;
self
}
#[must_use]
pub fn with_memory_budget(mut self, budget: usize) -> Self {
self.memory_budget = budget;
self
}
#[must_use]
pub const fn max_score() -> u8 {
100
}
#[must_use]
pub fn category_points() -> HashMap<QaCategory, u8> {
let mut points = HashMap::new();
points.insert(QaCategory::Robustness, 20);
points.insert(QaCategory::EdgeCases, 15);
points.insert(QaCategory::DistributionShift, 15);
points.insert(QaCategory::Fairness, 15);
points.insert(QaCategory::Privacy, 10);
points.insert(QaCategory::Latency, 10);
points.insert(QaCategory::Memory, 10);
points.insert(QaCategory::Reproducibility, 5);
points
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QaReport {
pub model_id: String,
pub categories: HashMap<QaCategory, CategoryScore>,
pub total_score: u8,
pub passed: bool,
pub blockers: Vec<QaIssue>,
pub warnings: Vec<QaIssue>,
}
impl QaReport {
#[must_use]
pub fn new(model_id: String) -> Self {
Self {
model_id,
categories: HashMap::new(),
total_score: 0,
passed: false,
blockers: Vec::new(),
warnings: Vec::new(),
}
}
pub fn add_category(&mut self, category: QaCategory, score: CategoryScore) {
self.categories.insert(category, score);
self.recalculate_total();
}
pub fn add_blocker(&mut self, issue: QaIssue) {
self.blockers.push(issue);
self.passed = false;
}
pub fn add_warning(&mut self, issue: QaIssue) {
self.warnings.push(issue);
}
fn recalculate_total(&mut self) {
let earned: u16 = self
.categories
.values()
.map(|s| u16::from(s.points_earned))
.sum();
let possible: u16 = self
.categories
.values()
.map(|s| u16::from(s.points_possible))
.sum();
self.total_score = if possible > 0 {
((earned * 100) / possible).min(100) as u8
} else {
0
};
self.passed = self.total_score >= 80 && self.blockers.is_empty();
}
#[must_use]
pub fn is_production_ready(&self) -> bool {
self.passed && self.total_score >= 90
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum QaCategory {
Robustness,
EdgeCases,
DistributionShift,
Fairness,
Privacy,
Latency,
Memory,
Reproducibility,
}
impl QaCategory {
#[must_use]
pub fn all() -> Vec<Self> {
vec![
Self::Robustness,
Self::EdgeCases,
Self::DistributionShift,
Self::Fairness,
Self::Privacy,
Self::Latency,
Self::Memory,
Self::Reproducibility,
]
}
#[must_use]
pub const fn name(&self) -> &'static str {
match self {
Self::Robustness => "Robustness",
Self::EdgeCases => "Edge Cases",
Self::DistributionShift => "Distribution Shift",
Self::Fairness => "Fairness",
Self::Privacy => "Privacy",
Self::Latency => "Latency",
Self::Memory => "Memory",
Self::Reproducibility => "Reproducibility",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CategoryScore {
pub points_earned: u8,
pub points_possible: u8,
pub tests_passed: u32,
pub tests_failed: u32,
pub details: Vec<TestResult>,
}
impl CategoryScore {
#[must_use]
pub fn new(points_possible: u8) -> Self {
Self {
points_earned: 0,
points_possible,
tests_passed: 0,
tests_failed: 0,
details: Vec::new(),
}
}
pub fn add_result(&mut self, result: TestResult) {
if result.passed {
self.tests_passed += 1;
} else {
self.tests_failed += 1;
}
self.details.push(result);
}
pub fn finalize(&mut self) {
let total = self.tests_passed + self.tests_failed;
if total > 0 {
let pass_rate = f64::from(self.tests_passed) / f64::from(total);
self.points_earned = (f64::from(self.points_possible) * pass_rate).round() as u8;
}
}
#[must_use]
pub fn pass_rate(&self) -> f64 {
let total = self.tests_passed + self.tests_failed;
if total > 0 {
f64::from(self.tests_passed) / f64::from(total) * 100.0
} else {
0.0
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestResult {
pub name: String,
pub passed: bool,
pub message: Option<String>,
pub duration: Duration,
}
impl TestResult {
#[must_use]
pub fn pass(name: impl Into<String>, duration: Duration) -> Self {
Self {
name: name.into(),
passed: true,
message: None,
duration,
}
}
#[must_use]
pub fn fail(name: impl Into<String>, message: impl Into<String>, duration: Duration) -> Self {
Self {
name: name.into(),
passed: false,
message: Some(message.into()),
duration,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QaIssue {
pub category: QaCategory,
pub severity: Severity,
pub message: String,
pub remediation: String,
}
impl QaIssue {
#[must_use]
pub fn new(
category: QaCategory,
severity: Severity,
message: impl Into<String>,
remediation: impl Into<String>,
) -> Self {
Self {
category,
severity,
message: message.into(),
remediation: remediation.into(),
}
}
#[must_use]
pub fn blocker(
category: QaCategory,
message: impl Into<String>,
remediation: impl Into<String>,
) -> Self {
Self::new(category, Severity::Blocker, message, remediation)
}
#[must_use]
pub fn warning(
category: QaCategory,
message: impl Into<String>,
remediation: impl Into<String>,
) -> Self {
Self::new(category, Severity::Warning, message, remediation)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Severity {
Blocker,
Critical,
Warning,
Info,
}
impl Severity {
#[must_use]
pub const fn is_blocking(&self) -> bool {
matches!(self, Self::Blocker)
}
#[must_use]
pub const fn requires_review(&self) -> bool {
matches!(self, Self::Blocker | Self::Critical)
}
}
#[derive(Debug, Clone)]
pub enum JidokaStop {
InvalidHeader,
SignatureFailed,
ChecksumFailed,
WcetViolation,
FairnessViolation,
QualityGateFailed {
score: u8,
threshold: u8,
},
}
impl JidokaStop {
#[must_use]
pub const fn requires_human_review(&self) -> bool {
true
}
#[must_use]
pub fn description(&self) -> String {
match self {
Self::InvalidHeader => "Invalid file header".to_string(),
Self::SignatureFailed => "Signature verification failed".to_string(),
Self::ChecksumFailed => "Checksum mismatch - data corrupted".to_string(),
Self::WcetViolation => "WCET budget exceeded".to_string(),
Self::FairnessViolation => "Fairness threshold breached".to_string(),
Self::QualityGateFailed { score, threshold } => {
format!("Quality gate failed: {score}/100 < {threshold}/100")
}
}
}
}
#[cfg(test)]
#[path = "qa_tests.rs"]
mod tests;