use std::{collections::BTreeMap, path::Path};
use anyhow::Result;
use camino::Utf8PathBuf;
use serde::{Deserialize, Serialize};
pub trait LanguagePlugin: Send + Sync {
fn id(&self) -> &'static str;
fn display_name(&self) -> &'static str;
fn capabilities(&self) -> Vec<PluginCapability> {
vec![
PluginCapability::TargetDiscovery,
PluginCapability::GeneratedTests,
PluginCapability::ExistingTests,
PluginCapability::Coverage,
PluginCapability::RegressionPromotion,
]
}
fn detect_project(&self, root: &Path) -> Result<ProjectInfo>;
fn discover_targets(&self, root: &Path) -> Result<Vec<VerificationTarget>>;
fn generate_tests(
&self,
target: &VerificationTarget,
plan: &VerificationPlan,
) -> Result<Vec<GeneratedArtifact>>;
fn run_tests(
&self,
root: &Path,
artifacts: &[GeneratedArtifact],
plan: &VerificationPlan,
) -> Result<TestRunResult>;
fn collect_coverage(&self, root: &Path) -> Result<Option<CoverageReport>>;
fn replay_behavior(
&self,
_root: &Path,
_target: &VerificationTarget,
_case: &BehaviorReplayCase,
) -> Result<Option<BehaviorReplayObservation>> {
Ok(None)
}
fn replay_behaviors(
&self,
root: &Path,
target: &VerificationTarget,
cases: &[BehaviorReplayCase],
) -> Result<BTreeMap<String, BehaviorReplayObservation>> {
let mut observations = BTreeMap::new();
for case in cases {
if let Some(observation) = self.replay_behavior(root, target, case)? {
observations.insert(case.name.clone(), observation);
}
}
Ok(observations)
}
fn promote_regression(
&self,
_root: &Path,
_report: &VerificationReport,
finding: &Failure,
index: usize,
) -> Result<Vec<GeneratedArtifact>> {
let language = finding_language(finding).unwrap_or_else(|| self.id().to_string());
let mut contents = String::from("# Regression Promotion\n\n");
contents.push_str("Generated by veritas. Review before committing.\n\n");
contents.push_str(&format!("- Finding: {}\n", finding.message));
contents.push_str(&format!("- Severity: {:?}\n", finding.severity));
contents.push_str(&format!("- Command: `{}`\n", finding.command));
if let Some(target_id) = &finding.target_id {
contents.push_str(&format!("- Target: `{target_id}`\n"));
}
if let Some(repro) = &finding.repro {
contents.push_str(&format!("- Repro command: `{}`\n", repro.command));
if let Some(path) = &repro.path {
contents.push_str(&format!("- Repro path: `{path}`\n"));
}
if let Some(input) = &repro.input {
contents.push_str(&format!("- Repro input: `{}`\n", input.trim()));
}
}
contents.push_str("\n## Next Step\n\n");
contents.push_str("This language plugin has not implemented executable regression promotion yet. Add a handwritten test owned by the target package, then rerun `veritas verify`.\n");
Ok(vec![GeneratedArtifact {
id: format!("{language}-promoted-regression-{index}"),
language: language.clone(),
kind: ArtifactKind::RegressionTest,
target_id: finding
.target_id
.clone()
.unwrap_or_else(|| format!("{language}:unknown")),
path: Utf8PathBuf::from(format!(
".veritas/regressions/promoted/{language}_{index}.md"
)),
contents,
description: "Generic regression promotion guidance".to_string(),
status: ArtifactStatus::Planned,
}])
}
}
fn finding_language(finding: &Failure) -> Option<String> {
finding
.target_id
.as_deref()
.and_then(|target_id| target_id.split_once(':').map(|(language, _)| language))
.map(ToString::to_string)
}
pub trait VerificationPlanner: Send + Sync {
fn plan(&self, project: &ProjectInfo, target: &VerificationTarget) -> Result<VerificationPlan>;
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ProjectInfo {
pub language: String,
pub name: String,
pub root: Utf8PathBuf,
pub manifests: Vec<Utf8PathBuf>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct VerificationTarget {
pub id: String,
pub language: String,
pub kind: TargetKind,
pub path: Utf8PathBuf,
pub symbol: Option<String>,
pub signature: Option<String>,
pub line_range: Option<LineRange>,
pub description: String,
pub risk: RiskLevel,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct LineRange {
pub start: usize,
pub end: usize,
}
impl LineRange {
pub fn overlaps(&self, other: &LineRange) -> bool {
self.start <= other.end && other.start <= self.end
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum TargetKind {
Project,
Package,
File,
Function,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "snake_case")]
pub enum RiskLevel {
Low,
Medium,
High,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "snake_case")]
pub enum FailureSeverity {
Info,
Warning,
Error,
Critical,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct VerificationPlan {
pub target_id: String,
pub strategies: Vec<VerificationStrategy>,
pub budget_seconds: u64,
pub write_generated_tests: bool,
pub run_existing_tests: bool,
pub run_generated_tests: bool,
pub fail_on_generated_test_failure: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "snake_case")]
pub enum VerificationStrategy {
ExistingTests,
UnitTests,
PropertyTests,
Fuzzing,
DifferentialTests,
MutationChecks,
CoverageFeedback,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GeneratedArtifact {
pub id: String,
pub language: String,
pub kind: ArtifactKind,
pub target_id: String,
pub path: Utf8PathBuf,
pub contents: String,
pub description: String,
pub status: ArtifactStatus,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ArtifactKind {
UnitTest,
PropertyTest,
FuzzHarness,
HarnessIndex,
MutationCheck,
CoverageFeedback,
DifferentialBaseline,
ReproCase,
PackageAwareness,
PackageGraph,
SymbolGraph,
ChangeDigest,
AiFeedback,
CandidatePatch,
FindingBaseline,
RegressionTest,
DifferentialReplay,
EvolutionPlan,
AssertionCandidate,
CorpusEntry,
ReplayResult,
BudgetPlan,
ConfidenceScore,
MutationTrend,
MutationCampaign,
EvolutionCandidate,
EvolutionSuite,
CorpusReplay,
TargetCache,
SiteAsset,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "snake_case")]
pub enum PluginCapability {
TargetDiscovery,
SymbolGraph,
GeneratedTests,
ExistingTests,
PropertyTests,
Fuzzing,
MutationChecks,
Coverage,
DifferentialReplay,
CorpusReplay,
RegressionPromotion,
ResourceBudgets,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ArtifactStatus {
Planned,
Written,
Skipped,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct TestRunResult {
pub language: String,
pub status: RunStatus,
pub commands: Vec<CommandRecord>,
pub failures: Vec<Failure>,
pub duration_ms: u128,
#[serde(default)]
pub quality: VerificationQuality,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum RunStatus {
Passed,
Failed,
Skipped,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CommandRecord {
pub program: String,
pub args: Vec<String>,
pub cwd: Utf8PathBuf,
pub exit_code: Option<i32>,
pub status: RunStatus,
pub stdout: String,
pub stderr: String,
pub duration_ms: u128,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Failure {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
pub message: String,
#[serde(default = "default_failure_severity")]
pub severity: FailureSeverity,
pub target_id: Option<String>,
pub artifact_id: Option<String>,
pub command: String,
pub stdout_excerpt: String,
pub stderr_excerpt: String,
pub repro: Option<ReproCase>,
}
fn default_failure_severity() -> FailureSeverity {
FailureSeverity::Error
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ReproCase {
pub command: String,
pub input: Option<String>,
pub path: Option<Utf8PathBuf>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BehaviorReplayCase {
pub name: String,
pub inputs: Vec<serde_json::Value>,
pub assertion: Option<String>,
#[serde(default, skip_serializing_if = "is_false")]
pub argument_tuple: bool,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum BehaviorReplayStatus {
Observed,
Unsupported,
Failed,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BehaviorReplayObservation {
pub status: BehaviorReplayStatus,
pub output: serde_json::Value,
pub command: Option<String>,
pub stdout_excerpt: Option<String>,
pub stderr_excerpt: Option<String>,
pub duration_ms: Option<u128>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct AssertionCandidate {
pub language: String,
pub target_id: String,
pub finding_id: Option<String>,
pub source: AssertionSource,
pub domain: AssertionDomain,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub semantic_packs: Vec<String>,
pub title: String,
pub seed_inputs: Vec<String>,
pub expected_behavior: String,
pub replay_command: Option<String>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum AssertionSource {
MutationSurvivor,
FuzzRepro,
GeneratedTestFailure,
DifferentialReplay,
CoverageGap,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "snake_case")]
pub enum AssertionDomain {
AuthPermission,
Money,
Parsing,
Serialization,
ErrorHandling,
Boundary,
General,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CorpusEntry {
pub language: String,
pub target_id: String,
pub finding_id: Option<String>,
pub source: AssertionSource,
pub input: Option<String>,
pub path: Option<Utf8PathBuf>,
pub replay_command: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CommandBudget {
pub language: String,
pub target_id: String,
pub budget_seconds: u64,
pub max_concurrency: usize,
pub resource_limits: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ConfidenceScore {
pub score: u8,
pub grade: ConfidenceGrade,
pub summary: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub correctness_mutation_score_percent: Option<u8>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub brittleness_probe_survival_percent: Option<u8>,
#[serde(default, skip_serializing_if = "is_zero")]
pub brittleness_probes_executed: usize,
#[serde(default, skip_serializing_if = "is_zero")]
pub brittleness_probes_killed: usize,
pub positive_signals: Vec<String>,
pub risks: Vec<String>,
pub recommended_next_steps: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub baseline_delta: Option<QualityDelta>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ConfidenceGrade {
Low,
Medium,
High,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct QualityBaseline {
pub version: u32,
pub quality: VerificationQuality,
pub confidence: u8,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct QualityDelta {
pub mutation_score_delta: Option<i16>,
pub confidence_delta: i16,
pub surviving_mutants_delta: i64,
pub corpus_entries_delta: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CoverageReport {
pub tool: String,
pub summary: String,
pub files: Vec<CoverageFile>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CoverageFile {
pub path: Utf8PathBuf,
pub line_coverage_percent: Option<u8>,
pub uncovered_ranges: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct VerificationReport {
pub project: Option<ProjectInfo>,
pub targets: Vec<VerificationTarget>,
pub plan: Option<VerificationPlan>,
pub artifacts: Vec<GeneratedArtifact>,
pub runs: Vec<TestRunResult>,
pub coverage: Vec<CoverageReport>,
pub findings: Vec<Failure>,
#[serde(default)]
pub quality: VerificationQuality,
pub suggested_next_steps: Vec<String>,
}
impl VerificationReport {
pub fn empty() -> Self {
Self {
project: None,
targets: Vec::new(),
plan: None,
artifacts: Vec::new(),
runs: Vec::new(),
coverage: Vec::new(),
findings: Vec::new(),
quality: VerificationQuality::default(),
suggested_next_steps: Vec::new(),
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct VerificationQuality {
pub mutation: MutationMetrics,
pub property: PropertyMetrics,
pub fuzz: FuzzMetrics,
#[serde(default)]
pub regression: RegressionMetrics,
#[serde(default)]
pub replay: ReplayMetrics,
#[serde(default)]
pub budget: BudgetMetrics,
#[serde(default)]
pub evolution: EvolutionMetrics,
#[serde(default)]
pub performance: PerformanceMetrics,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct MutationMetrics {
pub generated: usize,
#[serde(default)]
pub runnable: usize,
pub executed: usize,
pub killed: usize,
pub survived: usize,
#[serde(default)]
pub not_covered: usize,
#[serde(default)]
pub timed_out: usize,
#[serde(default)]
pub not_viable: usize,
pub skipped: usize,
#[serde(default, skip_serializing_if = "is_zero")]
pub requested_workers: usize,
#[serde(default, skip_serializing_if = "is_zero")]
pub effective_workers: usize,
#[serde(default, skip_serializing_if = "is_zero")]
pub isolation_failures: usize,
#[serde(default, skip_serializing_if = "is_zero_u128")]
pub isolation_setup_ms: u128,
#[serde(default)]
pub isolation_copy_ms: u128,
#[serde(default, skip_serializing_if = "is_zero")]
pub isolation_excluded_path_count: usize,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub isolation_excluded_path_samples: Vec<Utf8PathBuf>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub isolation_exclusion_patterns: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub isolation_runs: Vec<MutationIsolationRecord>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub baseline_duration_ms: Option<u128>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub computed_timeout_seconds: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub timeout_source: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub score_percent: Option<u8>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub correctness_score_percent: Option<u8>,
#[serde(default, skip_serializing_if = "is_zero")]
pub correctness_executed: usize,
#[serde(default, skip_serializing_if = "is_zero")]
pub correctness_killed: usize,
#[serde(default, skip_serializing_if = "is_zero")]
pub correctness_survived: usize,
#[serde(default, skip_serializing_if = "is_zero")]
pub brittleness_executed: usize,
#[serde(default, skip_serializing_if = "is_zero")]
pub brittleness_killed: usize,
#[serde(default, skip_serializing_if = "is_zero")]
pub brittleness_survived: usize,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub brittleness_survival_percent: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub efficacy_percent: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mutant_coverage_percent: Option<u8>,
#[serde(default)]
pub by_domain: BTreeMap<String, MutationAttribution>,
#[serde(default)]
pub by_operator: BTreeMap<String, MutationAttribution>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub records: Vec<MutationRecord>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct MutationIsolationRecord {
pub language: String,
pub worker_index: usize,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub shard_index: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mutant_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub scratch_root: Option<Utf8PathBuf>,
#[serde(default)]
pub copy_duration_ms: u128,
#[serde(default, skip_serializing_if = "is_zero")]
pub excluded_path_count: usize,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub excluded_path_samples: Vec<Utf8PathBuf>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub exclusion_patterns: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct MutationAttribution {
pub generated: usize,
#[serde(default)]
pub runnable: usize,
pub executed: usize,
pub killed: usize,
pub survived: usize,
#[serde(default)]
pub not_covered: usize,
#[serde(default)]
pub timed_out: usize,
#[serde(default)]
pub not_viable: usize,
pub skipped: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MutationRecord {
pub id: String,
pub language: String,
pub path: Utf8PathBuf,
pub symbol: String,
pub operator: String,
pub domain: String,
pub status: MutationStatus,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub from: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub to: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub line_range: Option<LineRange>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_span: Option<SourceSpan>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub diff: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub diff_path: Option<Utf8PathBuf>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub outcome_path: Option<Utf8PathBuf>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub command_log_path: Option<Utf8PathBuf>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stdout_log_path: Option<Utf8PathBuf>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stderr_log_path: Option<Utf8PathBuf>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub risk_note: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub suggested_test: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub skip_reason: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub selected_test_command: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub test_selection_hint: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub test_selection_fallback: Option<String>,
#[serde(default, skip_serializing_if = "is_false")]
pub brittleness_probe: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub command: Option<String>,
#[serde(default)]
pub duration_ms: u128,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SourceSpan {
pub start_byte: usize,
pub end_byte: usize,
}
pub mod mutation_taxonomy {
pub const DOMAINS: &[&str] = &[
"auth_permission",
"money",
"parsing_normalization",
"serialization",
"error_handling",
"boundary",
"concurrency_lifecycle",
"synchronization",
"database",
"retry_resilience",
"testability",
"brittleness",
"general",
];
pub const OPERATORS: &[&str] = &[
"comparison",
"equality",
"boolean",
"arithmetic",
"bitwise",
"assignment",
"increment",
"loop",
"literal",
"negation",
"default",
"nil",
"error",
"boundary",
"await_join",
"task_spawn",
"context_timeout",
"lock_mode",
"unlock_guard",
"channel_select",
"atomic_ordering",
"transaction_boundary",
"rollback_commit",
"isolation_lock",
"idempotency",
"tenant_filter",
"affected_rows",
"retry_attempt",
"retry_classifier",
"backoff_cap",
"injected_clock",
"injected_randomness",
"injected_repository",
"test_scheduler",
"config_lookup",
"temp_isolation",
"error_shape",
"equivalent_ordering",
"log_metric_noise",
"private_refactor",
"general",
];
pub fn valid_domain(domain: &str) -> bool {
DOMAINS.contains(&domain)
}
pub fn valid_operator(operator: &str) -> bool {
OPERATORS.contains(&operator)
}
pub fn normalize_domain(label: &str) -> &'static str {
let label = label.to_ascii_lowercase().replace(['/', '-'], "_");
for domain in DOMAINS {
if label.contains(domain) {
return domain;
}
}
if label.contains("permission") || label.contains("auth") || label.contains("token") {
"auth_permission"
} else if label.contains("parse") || label.contains("format") || label.contains("normal") {
"parsing_normalization"
} else if label.contains("error") || label.contains("err") || label.contains("nil") {
"error_handling"
} else if label.contains("await") || label.contains("spawn") || label.contains("task") {
"concurrency_lifecycle"
} else if label.contains("lock")
|| label.contains("unlock")
|| label.contains("channel")
|| label.contains("select")
|| label.contains("atomic")
|| label.contains("ordering")
{
"synchronization"
} else if label.contains("transaction")
|| label.contains("commit")
|| label.contains("rollback")
|| label.contains("isolation")
|| label.contains("tenant")
|| label.contains("idempot")
|| label.contains("rows affected")
{
"database"
} else if label.contains("retry")
|| label.contains("backoff")
|| label.contains("transient")
{
"retry_resilience"
} else if label.contains("clock")
|| label.contains("random")
|| label.contains("repository")
|| label.contains("scheduler")
|| label.contains("config")
|| label.contains("temp")
{
"testability"
} else if label.contains("equivalent")
|| label.contains("noise")
|| label.contains("private")
|| label.contains("brittle")
{
"brittleness"
} else if label.contains("boundary")
|| label.contains("limit")
|| label.contains("threshold")
|| label.contains("min")
|| label.contains("max")
{
"boundary"
} else if label.contains("money")
|| label.contains("price")
|| label.contains("invoice")
|| label.contains("total")
|| label.contains("refund")
|| label.contains("discount")
{
"money"
} else if label.contains("serial") || label.contains("json") || label.contains("marshal") {
"serialization"
} else {
"general"
}
}
pub fn normalize_operator(label: &str) -> &'static str {
let label = label.to_ascii_lowercase().replace(['/', '-'], "_");
for operator in OPERATORS {
if label.contains(operator) {
return operator;
}
}
if label.contains("equality") {
"equality"
} else if label.contains("comparison") {
"comparison"
} else if label.contains("boolean") || label.contains("connector") {
"boolean"
} else if label.contains("arithmetic") {
"arithmetic"
} else if label.contains("bitwise") {
"bitwise"
} else if label.contains("assignment") {
"assignment"
} else if label.contains("increment") || label.contains("decrement") {
"increment"
} else if label.contains("loop") {
"loop"
} else if label.contains("literal") || label.contains("value perturbation") {
"literal"
} else if label.contains("negation") {
"negation"
} else if label.contains("default") {
"default"
} else if label.contains("nil") {
"nil"
} else if label.contains("error") {
"error"
} else if label.contains("boundary") {
"boundary"
} else {
"general"
}
}
pub fn risk_note(domain: &str, operator: &str) -> &'static str {
match (domain, operator) {
("concurrency_lifecycle", "await_join") => {
"Async lifecycle mutation: tests should prove awaited work is observed before returning."
}
("concurrency_lifecycle", "task_spawn") => {
"Task lifecycle mutation: tests should catch fire-and-forget or missing goroutine behavior."
}
("synchronization", "lock_mode") => {
"Synchronization mutation: tests should catch read/write lock or critical-section weakening."
}
("synchronization", "unlock_guard") => {
"Synchronization mutation: tests should detect lock release timing and cleanup guarantees."
}
("synchronization", "channel_select") => {
"Channel/select mutation: tests should cover cancellation, timeout, and chosen receive/send paths."
}
("synchronization", "atomic_ordering") => {
"Atomic ordering mutation: tests should exercise concurrent visibility assumptions."
}
("database", "transaction_boundary") | ("database", "rollback_commit") => {
"Database mutation: tests should assert transaction commit/rollback behavior and failure atomicity."
}
("database", "isolation_lock") => {
"Database isolation mutation: tests should catch lost locking or isolation guarantees."
}
("database", "tenant_filter") => {
"Tenant isolation mutation: tests should prove cross-tenant data cannot leak."
}
("database", "idempotency") => {
"Idempotency mutation: tests should replay duplicate requests and assert stable side effects."
}
("retry_resilience", _) => {
"Retry/resilience mutation: tests should cover transient failures, retry limits, and backoff choices."
}
("testability", _) => {
"Testing seam mutation: this code may be brittle without injected time, randomness, IO, or schedulers."
}
("brittleness", _) => {
"Brittleness probe: killed behavior-preserving mutants point to tests coupled to ordering, logs, formatting, or implementation noise."
}
_ => "Mutation should be killed by behavior-focused tests over the affected symbol.",
}
}
pub fn suggested_test(domain: &str, operator: &str) -> &'static str {
match (domain, operator) {
("concurrency_lifecycle", _) => {
"Add a deterministic concurrent test that waits for completion and asserts observable side effects."
}
("synchronization", "channel_select") => {
"Add channel/select tests for ready, blocked, cancellation, and timeout paths."
}
("synchronization", _) => {
"Add a contention test with deterministic scheduling or repeated stress under the focused symbol."
}
("database", "tenant_filter") => {
"Add a cross-tenant fixture and assert each query/update is scoped to the active tenant."
}
("database", _) => {
"Add transaction tests that force success and failure paths and inspect persisted state."
}
("retry_resilience", _) => {
"Use a fake dependency that fails transiently, then assert retry count, backoff cap, and final result."
}
("testability", _) => {
"Introduce an injectable seam for time/randomness/IO and assert deterministic behavior through it."
}
("brittleness", _) => {
"If this probe was killed, loosen exact implementation assertions into behavior assertions unless the detail is contractual."
}
_ => "Add a regression assertion that distinguishes the original expression from this mutant.",
}
}
}
fn is_zero(value: &usize) -> bool {
*value == 0
}
fn is_zero_u128(value: &u128) -> bool {
*value == 0
}
pub fn finalize_mutation_metrics(mutation: &mut MutationMetrics) {
mutation.skipped = mutation.generated.saturating_sub(mutation.executed);
mutation.score_percent = percent(mutation.killed, mutation.executed);
mutation.efficacy_percent = percent(mutation.killed, mutation.killed + mutation.survived);
mutation.mutant_coverage_percent = percent(
mutation.killed + mutation.survived,
mutation.killed + mutation.survived + mutation.not_covered,
);
let brittleness = mutation
.by_domain
.get("brittleness")
.cloned()
.unwrap_or_default();
mutation.brittleness_executed = brittleness.executed;
mutation.brittleness_killed = brittleness.killed;
mutation.brittleness_survived = brittleness.survived;
mutation.brittleness_survival_percent =
percent(mutation.brittleness_survived, mutation.brittleness_executed);
mutation.correctness_executed = mutation
.executed
.saturating_sub(mutation.brittleness_executed);
mutation.correctness_killed = mutation.killed.saturating_sub(mutation.brittleness_killed);
mutation.correctness_survived = mutation
.survived
.saturating_sub(mutation.brittleness_survived);
mutation.correctness_score_percent =
percent(mutation.correctness_killed, mutation.correctness_executed);
}
fn percent(numerator: usize, denominator: usize) -> Option<u8> {
(numerator * 100)
.checked_div(denominator)
.map(|score| score.try_into().unwrap_or(100))
}
fn is_false(value: &bool) -> bool {
!*value
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "snake_case")]
pub enum MutationStatus {
Runnable,
NotCovered,
Killed,
Lived,
TimedOut,
NotViable,
Skipped,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct PropertyMetrics {
pub generated_artifacts: usize,
pub failed_generated_tests: usize,
pub no_panic_properties: usize,
pub deterministic_properties: usize,
pub invariant_properties: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub strength_score_percent: Option<u8>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct FuzzMetrics {
pub generated_harnesses: usize,
pub targets_executed: usize,
pub failures: usize,
pub persisted_repros: usize,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct RegressionMetrics {
pub assertion_candidates: usize,
pub promoted_scaffolds: usize,
pub corpus_entries: usize,
pub corpus_replayed: usize,
pub corpus_passed: usize,
pub corpus_failed: usize,
pub corpus_skipped: usize,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct ReplayMetrics {
pub manifests: usize,
pub targets: usize,
pub cases: usize,
pub results: usize,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct BudgetMetrics {
pub budget_plans: usize,
pub skipped_commands: usize,
pub timed_out_commands: usize,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct EvolutionMetrics {
pub suites: usize,
pub candidates: usize,
pub selected: usize,
pub property_candidates: usize,
pub mutation_candidates: usize,
pub fuzz_candidates: usize,
pub regression_candidates: usize,
pub replay_candidates: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub average_fitness_percent: Option<u8>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct PerformanceMetrics {
#[serde(default, skip_serializing_if = "is_zero_u128")]
pub discovery_ms: u128,
#[serde(default, skip_serializing_if = "is_zero_u128")]
pub generation_ms: u128,
#[serde(default, skip_serializing_if = "is_zero_u128")]
pub test_execution_ms: u128,
#[serde(default, skip_serializing_if = "is_zero_u128")]
pub coverage_ms: u128,
#[serde(default, skip_serializing_if = "is_zero_u128")]
pub replay_ms: u128,
#[serde(default, skip_serializing_if = "is_zero_u128")]
pub artifact_synthesis_ms: u128,
#[serde(default, skip_serializing_if = "is_zero_u128")]
pub total_ms: u128,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct EvolutionSuite {
pub version: u8,
pub language: String,
pub generation: u32,
pub selection_budget: usize,
pub fitness_signals: Vec<String>,
pub candidates: Vec<EvolutionCandidateRecord>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct EvolutionCandidateRecord {
pub id: String,
pub language: String,
pub target_id: String,
pub kind: EvolutionCandidateKind,
pub strategy: EvolutionStrategy,
pub status: EvolutionCandidateStatus,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_artifact: Option<Utf8PathBuf>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_finding_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub domain: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub semantic_packs: Vec<String>,
pub fitness: EvolutionFitness,
pub proposed_action: String,
pub keep_if: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub proof_commands: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub done_when: Vec<String>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "snake_case")]
pub enum EvolutionCandidateKind {
Property,
Mutation,
Fuzz,
Regression,
Replay,
Budget,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "snake_case")]
pub enum EvolutionStrategy {
AddAssertion,
StrengthenProperty,
PromoteCorpus,
AddFuzzSeed,
NarrowTarget,
ReduceBudgetRisk,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "snake_case")]
pub enum EvolutionCandidateStatus {
Proposed,
Selected,
Applied,
Kept,
Superseded,
Rejected,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct EvolutionFitness {
pub score_percent: u8,
pub mutation_delta: i16,
pub finding_delta: i16,
pub replay_delta: i16,
pub confidence_delta: i16,
pub rationale: String,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "snake_case")]
pub enum EvolutionOutcome {
Improved,
Neutral,
Regressed,
FailedToEvaluate,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct EvolutionQualityDelta {
#[serde(skip_serializing_if = "Option::is_none")]
pub mutation_score_delta: Option<i16>,
pub killed_mutants_delta: i64,
pub surviving_mutants_delta: i64,
pub not_covered_mutants_delta: i64,
pub findings_delta: i64,
pub replay_cases_delta: i64,
pub corpus_failed_delta: i64,
pub budget_timed_out_delta: i64,
pub budget_skipped_delta: i64,
pub confidence_delta: i16,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct EvolutionGeneration {
pub version: u8,
pub language: String,
pub generation: u32,
pub parent_suite: Utf8PathBuf,
pub created_unix_seconds: u64,
pub outcome: EvolutionOutcome,
pub candidates: Vec<EvolutionGenerationCandidate>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub delta: Option<EvolutionQualityDelta>,
pub written_paths: Vec<Utf8PathBuf>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct EvolutionGenerationCandidate {
pub id: String,
pub target_id: String,
pub previous_status: EvolutionCandidateStatus,
pub status: EvolutionCandidateStatus,
pub outcome: EvolutionOutcome,
pub applied: bool,
pub written_paths: Vec<Utf8PathBuf>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub skipped_reason: Option<String>,
}