#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TransformationCategory {
Idempotency,
Determinism,
Safety,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TransformationExplanation {
pub category: TransformationCategory,
pub title: String,
pub original: String,
pub transformed: String,
pub what_changed: String,
pub why_it_matters: String,
pub line_number: Option<usize>,
pub safety_rationale: SafetyRationale,
pub alternatives: Vec<Alternative>,
}
impl TransformationExplanation {
pub fn new(
category: TransformationCategory,
title: impl Into<String>,
original: impl Into<String>,
transformed: impl Into<String>,
what_changed: impl Into<String>,
why_it_matters: impl Into<String>,
) -> Self {
Self {
category,
title: title.into(),
original: original.into(),
transformed: transformed.into(),
what_changed: what_changed.into(),
why_it_matters: why_it_matters.into(),
line_number: None,
safety_rationale: SafetyRationale::new(), alternatives: Vec::new(), }
}
pub fn with_line_number(mut self, line: usize) -> Self {
self.line_number = Some(line);
self
}
pub fn with_safety_rationale(mut self, rationale: SafetyRationale) -> Self {
self.safety_rationale = rationale;
self
}
pub fn with_alternatives(mut self, alternatives: Vec<Alternative>) -> Self {
self.alternatives = alternatives;
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SafetySeverity {
Critical,
High,
Medium,
Low,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SafetyRationale {
pub vulnerabilities_prevented: Vec<String>,
pub failures_eliminated: Vec<String>,
pub attack_vectors_closed: Vec<String>,
pub impact_without_fix: String,
pub severity: SafetySeverity,
}
impl SafetyRationale {
pub fn new() -> Self {
Self {
vulnerabilities_prevented: Vec::new(),
failures_eliminated: Vec::new(),
attack_vectors_closed: Vec::new(),
impact_without_fix: String::new(),
severity: SafetySeverity::Low,
}
}
pub fn add_vulnerability(mut self, vuln: impl Into<String>) -> Self {
self.vulnerabilities_prevented.push(vuln.into());
self
}
pub fn add_failure(mut self, failure: impl Into<String>) -> Self {
self.failures_eliminated.push(failure.into());
self
}
pub fn add_attack_vector(mut self, vector: impl Into<String>) -> Self {
self.attack_vectors_closed.push(vector.into());
self
}
pub fn with_impact(mut self, impact: impl Into<String>) -> Self {
self.impact_without_fix = impact.into();
self
}
pub fn with_severity(mut self, severity: SafetySeverity) -> Self {
self.severity = severity;
self
}
}
impl Default for SafetyRationale {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Alternative {
pub approach: String,
pub example: String,
pub when_to_use: String,
pub pros: Vec<String>,
pub cons: Vec<String>,
}
impl Alternative {
pub fn new(
approach: impl Into<String>,
example: impl Into<String>,
when_to_use: impl Into<String>,
) -> Self {
Self {
approach: approach.into(),
example: example.into(),
when_to_use: when_to_use.into(),
pros: Vec::new(),
cons: Vec::new(),
}
}
pub fn add_pro(mut self, pro: impl Into<String>) -> Self {
self.pros.push(pro.into());
self
}
pub fn add_con(mut self, con: impl Into<String>) -> Self {
self.cons.push(con.into());
self
}
}
pub fn generate_idempotency_rationale(transformation_title: &str) -> SafetyRationale {
match transformation_title {
title if title.contains("mkdir") && title.contains("-p") => SafetyRationale::new()
.add_failure("Script fails if directory already exists")
.add_failure("Non-atomic operations create race conditions")
.add_failure("Partial failure leaves system in inconsistent state")
.with_impact(
"Without -p flag, mkdir fails on re-run, breaking automation \
and deployment pipelines. Creates deployment race conditions \
in parallel execution environments.",
)
.with_severity(SafetySeverity::High),
title if title.contains("rm") && title.contains("-f") => SafetyRationale::new()
.add_failure("Script fails if file doesn't exist")
.add_failure("Cleanup scripts cannot be re-run safely")
.add_failure("Error handling becomes complex")
.with_impact(
"Without -f flag, rm fails if file missing, breaking \
cleanup operations and rollback procedures. Requires \
manual intervention to recover.",
)
.with_severity(SafetySeverity::High),
title if title.contains("ln") && title.contains("-sf") => SafetyRationale::new()
.add_failure("Symlink creation fails if link exists")
.add_failure("Cannot update symlinks atomically")
.add_failure("Deployment scripts break on re-run")
.with_impact(
"Without -sf flags, ln fails on existing symlinks, \
breaking blue-green deployments and atomic updates. \
Creates deployment downtime.",
)
.with_severity(SafetySeverity::High),
_ => SafetyRationale::new()
.add_failure("Operation not safe to re-run")
.with_impact("May fail on subsequent executions")
.with_severity(SafetySeverity::Medium),
}
}
pub fn generate_determinism_rationale(transformation_title: &str) -> SafetyRationale {
match transformation_title {
title if title.contains("RANDOM") => SafetyRationale::new()
.add_vulnerability("Non-reproducible builds break security audits")
.add_vulnerability("Cannot verify script behavior in production")
.add_failure("Debugging impossible with non-deterministic values")
.add_failure("Testing cannot catch all edge cases")
.with_impact(
"$RANDOM creates unpredictable script behavior, breaking \
reproducible builds, security audits, and compliance checks. \
Makes debugging production issues nearly impossible.",
)
.with_severity(SafetySeverity::Critical),
title if title.contains("timestamp") || title.contains("date") => SafetyRationale::new()
.add_vulnerability("Time-based values break reproducibility")
.add_vulnerability("Cannot verify script output")
.add_failure("Testing across time zones fails")
.add_failure("Replay attacks become possible")
.with_impact(
"Timestamps make scripts non-reproducible, breaking security \
verification and compliance. Creates race conditions in \
distributed systems.",
)
.with_severity(SafetySeverity::High),
_ => SafetyRationale::new()
.add_vulnerability("Non-deterministic behavior breaks verification")
.with_impact("Cannot guarantee reproducible results")
.with_severity(SafetySeverity::Medium),
}
}
pub fn generate_safety_rationale(transformation_title: &str) -> SafetyRationale {
match transformation_title {
title if title.contains("quot") || title.contains("variable") => SafetyRationale::new()
.add_vulnerability("Command injection via unquoted variables")
.add_vulnerability("Path traversal attacks")
.add_attack_vector("Inject shell metacharacters into variables")
.add_attack_vector("Word splitting allows arbitrary command execution")
.add_failure("Filename with spaces breaks script")
.add_failure("Glob expansion creates unexpected behavior")
.with_impact(
"Unquoted variables allow CRITICAL command injection attacks. \
Attacker can execute arbitrary commands by controlling \
variable content. Enables privilege escalation and data theft.",
)
.with_severity(SafetySeverity::Critical),
_ => SafetyRationale::new()
.add_vulnerability("Potential security issue")
.with_impact("May create security or safety problem")
.with_severity(SafetySeverity::Medium),
}
}
pub fn format_safety_rationale(rationale: &SafetyRationale) -> String {
let mut output = String::new();
let severity_symbol = match rationale.severity {
SafetySeverity::Critical => "🔴 CRITICAL",
SafetySeverity::High => "🟠HIGH",
SafetySeverity::Medium => "🟡 MEDIUM",
SafetySeverity::Low => "🟢 LOW",
};
output.push_str(&format!("Severity: {}\n\n", severity_symbol));
if !rationale.vulnerabilities_prevented.is_empty() {
output.push_str("Vulnerabilities Prevented:\n");
for vuln in &rationale.vulnerabilities_prevented {
output.push_str(&format!(" • {}\n", vuln));
}
output.push('\n');
}
if !rationale.failures_eliminated.is_empty() {
output.push_str("Failures Eliminated:\n");
for failure in &rationale.failures_eliminated {
output.push_str(&format!(" • {}\n", failure));
}
output.push('\n');
}
if !rationale.attack_vectors_closed.is_empty() {
output.push_str("Attack Vectors Closed:\n");
for vector in &rationale.attack_vectors_closed {
output.push_str(&format!(" • {}\n", vector));
}
output.push('\n');
}
if !rationale.impact_without_fix.is_empty() {
output.push_str("Impact Without Fix:\n");
output.push_str(&format!(" {}\n", rationale.impact_without_fix));
}
output
}
include!("purifier_transforms_generate.rs");