use serde::{Deserialize, Serialize};
use serde_yaml::{Mapping, Value};
use crate::error::{Severity, Violation};
pub const KAIZEN_STATUSES: [&str; 5] =
["draft", "implemented", "implementing", "pending", "planned"];
const COST_METRIC_FRAGMENTS: [&str; 9] = [
"alloc", "bytes", "churn", "launch", "overhead", "sync", "wasted", "_us", "_ms",
];
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct KaizenRecord {
#[serde(default)]
pub contract: Option<String>,
#[serde(default)]
pub title: Option<String>,
#[serde(default)]
pub kaizen: Option<Value>,
#[serde(default)]
pub parent: Option<String>,
#[serde(default)]
pub status: Option<String>,
#[serde(default)]
pub date: Option<Value>,
#[serde(default)]
pub baseline: Option<Value>,
#[serde(default)]
pub target: Option<Value>,
#[serde(default)]
pub invariants: Option<Value>,
}
impl KaizenRecord {
#[must_use]
pub fn delta_pair(&self) -> Option<(&Mapping, &Mapping)> {
let baseline = self.baseline.as_ref()?.as_mapping()?;
if let (Some(before), Some(after)) = (
baseline.get("before").and_then(Value::as_mapping),
baseline.get("after").and_then(Value::as_mapping),
) {
return Some((before, after));
}
let target = self.target.as_ref()?.as_mapping()?;
Some((baseline, target))
}
fn states_a_claim(&self, contract: &super::types::Contract) -> bool {
self.delta_pair().is_some()
|| !is_empty_block(self.invariants.as_ref())
|| !contract.proof_obligations.is_empty()
|| !contract.falsification_tests.is_empty()
}
}
fn is_empty_block(value: Option<&Value>) -> bool {
match value {
None | Some(Value::Null) => true,
Some(Value::Sequence(s)) => s.is_empty(),
Some(Value::Mapping(m)) => m.is_empty(),
Some(_) => false,
}
}
const QUANTITY_SUFFIXES: [&str; 14] = [
"", "%", "+", "x", "B", "KB", "MB", "GB", "KiB", "MiB", "GiB", "ms", "us", "s",
];
const QUANTITY_PREFIXES: [&str; 5] = ["<=", ">=", "<", ">", "~"];
#[must_use]
pub fn parse_quantity(value: &Value) -> Option<f64> {
match value {
Value::Number(n) => n.as_f64().filter(|v| v.is_finite()),
Value::String(s) => parse_quantity_str(s),
_ => None,
}
}
fn parse_quantity_str(raw: &str) -> Option<f64> {
let mut rest = raw.trim();
for prefix in QUANTITY_PREFIXES {
if let Some(stripped) = rest.strip_prefix(prefix) {
rest = stripped.trim_start();
break;
}
}
let digits = rest
.find(|c: char| !c.is_ascii_digit() && c != '.' && c != '-' && c != '_')
.unwrap_or(rest.len());
let (number, suffix) = rest.split_at(digits);
let number = number.replace('_', "");
if !QUANTITY_SUFFIXES.contains(&suffix.trim()) {
return None;
}
number.parse::<f64>().ok().filter(|v| v.is_finite())
}
fn comparable_metrics(before: &Mapping, after: &Mapping) -> Vec<(String, f64, f64)> {
let mut out = Vec::new();
for (key, before_value) in before {
let Some(name) = key.as_str() else { continue };
let Some(after_value) = after.get(key) else {
continue;
};
if let (Some(b), Some(a)) = (parse_quantity(before_value), parse_quantity(after_value)) {
out.push((name.to_string(), b, a));
}
}
out
}
fn is_cost_metric(name: &str) -> bool {
let lowered = name.to_ascii_lowercase();
COST_METRIC_FRAGMENTS
.iter()
.any(|fragment| lowered.contains(fragment))
}
fn violation(rule: &str, message: String, location: &str) -> Violation {
Violation {
severity: Severity::Error,
rule: rule.to_string(),
message,
location: Some(location.to_string()),
}
}
pub(crate) fn validate_kaizen(contract: &super::types::Contract, violations: &mut Vec<Violation>) {
let Some(record) = contract.kaizen_record.as_ref() else {
violations.push(violation(
"KAIZEN-001",
"metadata.kind is `kaizen` but the document carries none of the kaizen \
record blocks (`contract:`, `status:`, `baseline:`/`target:`) — a kaizen \
record that records nothing is not a kaizen record"
.to_string(),
"contract",
));
return;
};
validate_identity(record, violations);
validate_non_vacuity(record, contract, violations);
validate_delta(record, violations);
}
fn validate_identity(record: &KaizenRecord, violations: &mut Vec<Violation>) {
if record.contract.as_deref().is_none_or(str::is_empty) {
violations.push(violation(
"KAIZEN-001",
"a kaizen record must carry a non-empty `contract:` id — it is how every \
other document (parent records, qa_gate, the ledger) refers to this one"
.to_string(),
"contract",
));
}
match record.status.as_deref().map(str::trim) {
None | Some("") => violations.push(violation(
"KAIZEN-002",
format!(
"a kaizen record must declare `status:` — one of: {}",
KAIZEN_STATUSES.join(", ")
),
"status",
)),
Some(status) if !KAIZEN_STATUSES.contains(&status) => violations.push(violation(
"KAIZEN-002",
format!(
"kaizen `status: {status}` is not a known lifecycle state — must be one \
of: {}",
KAIZEN_STATUSES.join(", ")
),
"status",
)),
Some(_) => {}
}
}
fn validate_non_vacuity(
record: &KaizenRecord,
contract: &super::types::Contract,
violations: &mut Vec<Violation>,
) {
if !record.states_a_claim(contract) {
violations.push(violation(
"KAIZEN-003",
"this kaizen record states nothing that can fail — it has no baseline/target \
delta, no `invariants:`, no `proof_obligations:` and no \
`falsification_tests:`. A record that cannot be contradicted records an \
opinion, not an improvement"
.to_string(),
"baseline",
));
}
}
fn validate_delta(record: &KaizenRecord, violations: &mut Vec<Violation>) {
validate_delta_shape(record, violations);
let Some((before, after)) = record.delta_pair() else {
return;
};
let metrics = comparable_metrics(before, after);
validate_delta_moves(&metrics, violations);
validate_cost_direction(&metrics, violations);
}
fn validate_delta_shape(record: &KaizenRecord, violations: &mut Vec<Violation>) {
let Some(target) = record.target.as_ref() else {
return;
};
let Some(target_map) = target.as_mapping() else {
violations.push(violation(
"KAIZEN-004",
"`target:` must be a map of metric → value so it can be compared to \
`baseline:` key by key"
.to_string(),
"target",
));
return;
};
let Some(baseline_map) = record.baseline.as_ref().and_then(Value::as_mapping) else {
violations.push(violation(
"KAIZEN-004",
"`target:` is declared with no `baseline:` map to improve on — a target \
without a before-measurement cannot be shown to be an improvement"
.to_string(),
"baseline",
));
return;
};
if !baseline_map.keys().any(|k| target_map.contains_key(k)) {
violations.push(violation(
"KAIZEN-004",
"`baseline:` and `target:` share no metric key — the target measures \
something the baseline never measured, so nothing in this record can be \
compared"
.to_string(),
"target",
));
}
}
fn validate_delta_moves(metrics: &[(String, f64, f64)], violations: &mut Vec<Violation>) {
if metrics.is_empty() {
violations.push(violation(
"KAIZEN-005",
"`baseline:` and `target:` share no metric whose values are both \
quantities — every shared key holds prose on at least one side, so the \
record pins no number and claims nothing measurable"
.to_string(),
"target",
));
return;
}
if metrics.iter().all(|(_, before, after)| before == after) {
let names: Vec<&str> = metrics.iter().map(|(n, _, _)| n.as_str()).collect();
violations.push(violation(
"KAIZEN-005",
format!(
"`target:` restates `baseline:` unchanged on every comparable metric \
({}) — the record claims no movement, so no measurement can falsify it",
names.join(", ")
),
"target",
));
}
}
fn validate_cost_direction(metrics: &[(String, f64, f64)], violations: &mut Vec<Violation>) {
let mut measures_a_cost = false;
let mut a_cost_fell = false;
let mut risen: Vec<String> = Vec::new();
for (name, before, after) in metrics {
if !is_cost_metric(name) {
continue;
}
measures_a_cost = true;
if after < before {
a_cost_fell = true;
} else if after > before {
risen.push(format!("{name} {before} \u{2192} {after}"));
}
}
if !measures_a_cost || a_cost_fell || risen.is_empty() {
return;
}
violations.push(violation(
"KAIZEN-006",
format!(
"every cost metric this record measures either rises or holds, and none \
falls ({}) — a kaizen record removes waste, so this is either a regression \
recorded as an improvement or a `baseline:`/`target:` pair written the \
wrong way round",
risen.join("; ")
),
"target",
));
}
#[cfg(test)]
mod tests {
include!("kaizen_tests.rs");
}