#![cfg_attr(coverage_nightly, coverage(off))]
use crate::services::normalized_score::NormalizedScore;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::path::PathBuf;
pub const INFRA_SCORE_MAX_POINTS: f64 = 100.0;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InfraScore {
pub total_score: f64,
pub grade: InfraGrade,
pub auto_fail: bool,
pub categories: InfraCategoryScores,
pub recommendations: Vec<InfraRecommendation>,
pub metadata: InfraScoreMetadata,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum InfraGrade {
APlus, A, B, C, D, F, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InfraCategoryScores {
pub workflow_architecture: InfraCategoryScore, pub build_reliability: InfraCategoryScore, pub quality_pipeline: InfraCategoryScore, pub deployment_release: InfraCategoryScore, pub supply_chain: InfraCategoryScore, pub provable_contracts: InfraCategoryScore, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InfraCategoryScore {
pub score: f64,
pub max_score: f64,
pub percentage: f64,
pub checks: Vec<InfraCheck>,
pub findings: Vec<InfraFinding>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InfraCheck {
pub id: String, pub name: String, pub score: f64, pub max_score: f64, pub passed: bool,
pub evidence: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InfraFinding {
pub severity: InfraSeverity,
pub check_id: String,
pub message: String,
pub location: Option<String>,
pub impact_points: f64,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum InfraSeverity {
Pass, Warning, Fail, Info, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InfraRecommendation {
pub priority: InfraPriority,
pub check_id: String,
pub title: String,
pub description: String,
pub impact_points: f64,
pub estimated_effort: String,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum InfraPriority {
Low = 1,
Medium = 2,
High = 3,
Critical = 4,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InfraScoreMetadata {
pub timestamp: String,
pub repository_path: PathBuf,
pub git_branch: Option<String>,
pub git_commit: Option<String>,
pub pmat_version: String,
pub spec_version: String,
pub execution_time_ms: u64,
}
impl NormalizedScore for InfraScore {
fn raw(&self) -> f64 {
self.total_score
}
fn max_raw(&self) -> f64 {
INFRA_SCORE_MAX_POINTS
}
}
impl fmt::Display for InfraScore {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let fail_marker = if self.auto_fail { " [AUTO-FAIL]" } else { "" };
write!(
f,
"Infra Score: {:.1}/100 ({}){}",
self.total_score,
self.grade.as_str(),
fail_marker,
)
}
}
impl InfraGrade {
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "score_range")]
pub fn from_score(score: f64) -> Self {
match score {
s if s >= 95.0 => InfraGrade::APlus,
s if s >= 90.0 => InfraGrade::A,
s if s >= 80.0 => InfraGrade::B,
s if s >= 60.0 => InfraGrade::C,
s if s >= 40.0 => InfraGrade::D,
_ => InfraGrade::F,
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn as_str(&self) -> &'static str {
match self {
InfraGrade::APlus => "A+",
InfraGrade::A => "A",
InfraGrade::B => "B",
InfraGrade::C => "C",
InfraGrade::D => "D",
InfraGrade::F => "F",
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn is_auto_fail(&self) -> bool {
!matches!(self, InfraGrade::APlus | InfraGrade::A)
}
}
impl InfraCategoryScores {
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn total(&self) -> f64 {
self.workflow_architecture.score
+ self.build_reliability.score
+ self.quality_pipeline.score
+ self.deployment_release.score
+ self.supply_chain.score
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn total_with_bonus(&self) -> f64 {
self.total() + self.provable_contracts.score
}
}
impl Default for InfraCategoryScores {
fn default() -> Self {
Self {
workflow_architecture: InfraCategoryScore::empty(25.0),
build_reliability: InfraCategoryScore::empty(25.0),
quality_pipeline: InfraCategoryScore::empty(20.0),
deployment_release: InfraCategoryScore::empty(15.0),
supply_chain: InfraCategoryScore::empty(15.0),
provable_contracts: InfraCategoryScore::empty(10.0),
}
}
}
impl InfraCategoryScore {
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn empty(max_score: f64) -> Self {
Self {
score: 0.0,
max_score,
percentage: 0.0,
checks: vec![],
findings: vec![],
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn new(max_score: f64, checks: Vec<InfraCheck>, findings: Vec<InfraFinding>) -> Self {
let score: f64 = checks.iter().map(|c| c.score).sum();
let percentage = if max_score > 0.0 {
(score / max_score) * 100.0
} else {
0.0
};
Self {
score,
max_score,
percentage,
checks,
findings,
}
}
}
impl InfraCheck {
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn pass(id: &str, name: &str, max_score: f64, evidence: Vec<String>) -> Self {
Self {
id: id.to_string(),
name: name.to_string(),
score: max_score,
max_score,
passed: true,
evidence,
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn fail(id: &str, name: &str, max_score: f64, evidence: Vec<String>) -> Self {
Self {
id: id.to_string(),
name: name.to_string(),
score: 0.0,
max_score,
passed: false,
evidence,
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn partial(
id: &str,
name: &str,
score: f64,
max_score: f64,
evidence: Vec<String>,
) -> Self {
Self {
id: id.to_string(),
name: name.to_string(),
score,
max_score,
passed: score >= max_score,
evidence,
}
}
}
impl InfraScoreMetadata {
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn new(repository_path: PathBuf) -> Self {
Self {
timestamp: chrono::Utc::now().to_rfc3339(),
repository_path,
git_branch: None,
git_commit: None,
pmat_version: env!("CARGO_PKG_VERSION").to_string(),
spec_version: "0.1.0".to_string(),
execution_time_ms: 0,
}
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_infra_grade_from_score() {
assert_eq!(InfraGrade::from_score(100.0), InfraGrade::APlus);
assert_eq!(InfraGrade::from_score(95.0), InfraGrade::APlus);
assert_eq!(InfraGrade::from_score(94.0), InfraGrade::A);
assert_eq!(InfraGrade::from_score(90.0), InfraGrade::A);
assert_eq!(InfraGrade::from_score(89.0), InfraGrade::B);
assert_eq!(InfraGrade::from_score(80.0), InfraGrade::B);
assert_eq!(InfraGrade::from_score(79.0), InfraGrade::C);
assert_eq!(InfraGrade::from_score(60.0), InfraGrade::C);
assert_eq!(InfraGrade::from_score(59.0), InfraGrade::D);
assert_eq!(InfraGrade::from_score(40.0), InfraGrade::D);
assert_eq!(InfraGrade::from_score(39.0), InfraGrade::F);
assert_eq!(InfraGrade::from_score(0.0), InfraGrade::F);
}
#[test]
fn test_infra_grade_auto_fail() {
assert!(!InfraGrade::APlus.is_auto_fail());
assert!(!InfraGrade::A.is_auto_fail());
assert!(InfraGrade::B.is_auto_fail());
assert!(InfraGrade::C.is_auto_fail());
assert!(InfraGrade::D.is_auto_fail());
assert!(InfraGrade::F.is_auto_fail());
}
#[test]
fn test_infra_grade_as_str() {
assert_eq!(InfraGrade::APlus.as_str(), "A+");
assert_eq!(InfraGrade::A.as_str(), "A");
assert_eq!(InfraGrade::B.as_str(), "B");
assert_eq!(InfraGrade::C.as_str(), "C");
assert_eq!(InfraGrade::D.as_str(), "D");
assert_eq!(InfraGrade::F.as_str(), "F");
}
#[test]
fn test_infra_category_scores_total() {
let scores = InfraCategoryScores {
workflow_architecture: InfraCategoryScore {
score: 20.0,
max_score: 25.0,
percentage: 80.0,
checks: vec![],
findings: vec![],
},
build_reliability: InfraCategoryScore {
score: 22.0,
max_score: 25.0,
percentage: 88.0,
checks: vec![],
findings: vec![],
},
quality_pipeline: InfraCategoryScore {
score: 18.0,
max_score: 20.0,
percentage: 90.0,
checks: vec![],
findings: vec![],
},
deployment_release: InfraCategoryScore {
score: 12.0,
max_score: 15.0,
percentage: 80.0,
checks: vec![],
findings: vec![],
},
supply_chain: InfraCategoryScore {
score: 10.0,
max_score: 15.0,
percentage: 66.7,
checks: vec![],
findings: vec![],
},
provable_contracts: InfraCategoryScore {
score: 5.0,
max_score: 10.0,
percentage: 50.0,
checks: vec![],
findings: vec![],
},
};
assert!((scores.total() - 82.0).abs() < f64::EPSILON);
}
#[test]
fn test_infra_check_pass() {
let check = InfraCheck::pass(
"WA-01",
"Reusable workflow",
5.0,
vec!["found uses: org/.github".to_string()],
);
assert!(check.passed);
assert!((check.score - 5.0).abs() < f64::EPSILON);
}
#[test]
fn test_infra_check_fail() {
let check = InfraCheck::fail(
"WA-01",
"Reusable workflow",
5.0,
vec!["no reusable workflow found".to_string()],
);
assert!(!check.passed);
assert!((check.score - 0.0).abs() < f64::EPSILON);
}
#[test]
fn test_infra_check_partial() {
let check = InfraCheck::partial(
"BR-01",
"CI success rate",
3.0,
5.0,
vec!["7/10 passed".to_string()],
);
assert!(!check.passed);
assert!((check.score - 3.0).abs() < f64::EPSILON);
}
#[test]
fn test_infra_category_score_new() {
let checks = vec![
InfraCheck::pass("WA-01", "Reusable", 5.0, vec![]),
InfraCheck::fail("WA-02", "Self-hosted", 5.0, vec![]),
];
let cat = InfraCategoryScore::new(25.0, checks, vec![]);
assert!((cat.score - 5.0).abs() < f64::EPSILON);
assert!((cat.percentage - 20.0).abs() < f64::EPSILON);
}
#[test]
fn test_infra_score_display() {
let score = InfraScore {
total_score: 92.0,
grade: InfraGrade::A,
auto_fail: false,
categories: InfraCategoryScores::default(),
recommendations: vec![],
metadata: InfraScoreMetadata::new(PathBuf::from("/tmp/test")),
};
let display = format!("{}", score);
assert!(display.contains("92.0"));
assert!(display.contains("A"));
assert!(!display.contains("AUTO-FAIL"));
}
#[test]
fn test_infra_score_display_auto_fail() {
let score = InfraScore {
total_score: 75.0,
grade: InfraGrade::C,
auto_fail: true,
categories: InfraCategoryScores::default(),
recommendations: vec![],
metadata: InfraScoreMetadata::new(PathBuf::from("/tmp/test")),
};
let display = format!("{}", score);
assert!(display.contains("AUTO-FAIL"));
}
#[test]
fn test_normalized_score_trait() {
let score = InfraScore {
total_score: 85.0,
grade: InfraGrade::B,
auto_fail: true,
categories: InfraCategoryScores::default(),
recommendations: vec![],
metadata: InfraScoreMetadata::new(PathBuf::from("/tmp/test")),
};
assert!((score.raw() - 85.0).abs() < f64::EPSILON);
assert!((score.max_raw() - 100.0).abs() < f64::EPSILON);
assert!((score.normalized() - 85.0).abs() < f64::EPSILON);
}
#[test]
fn test_metadata_new() {
let meta = InfraScoreMetadata::new(PathBuf::from("/tmp/repo"));
assert_eq!(meta.repository_path, PathBuf::from("/tmp/repo"));
assert_eq!(meta.spec_version, "0.1.0");
assert!(meta.git_branch.is_none());
assert!(meta.git_commit.is_none());
}
}