use std::collections::HashSet;
use crate::error::{Severity, Violation};
use crate::schema::types::{Contract, ContractKind, CONTRACT_TOP_LEVEL_FIELDS};
pub fn validate_contract(contract: &Contract) -> Vec<Violation> {
let mut violations = Vec::new();
validate_metadata(contract, &mut violations);
validate_top_level_keys(contract, &mut violations);
if contract.kind() == ContractKind::Kernel && !contract.is_registry() {
validate_equations(contract, &mut violations);
validate_provability_invariant(contract, &mut violations);
validate_proof_obligations(contract, &mut violations);
validate_falsification_tests(contract, &mut violations);
validate_kani_harnesses(contract, &mut violations);
validate_qa_gate(contract, &mut violations);
} else {
validate_proof_obligations(contract, &mut violations);
validate_falsification_tests(contract, &mut violations);
validate_kani_harnesses(contract, &mut violations);
}
if contract.kind() == ContractKind::BeatBenchmark {
validate_beat_benchmark(contract, &mut violations);
}
if contract.kind() == ContractKind::Kaizen {
crate::schema::kaizen::validate_kaizen(contract, &mut violations);
}
validate_crux_intake(contract, &mut violations);
violations
}
pub(crate) const CRUX_COMPETITORS: [&str; 14] = [
"apr-qa-playbook",
"burn",
"ecosystem",
"hf-kernels-community",
"huggingface",
"linfa",
"llama_cpp",
"none",
"ollama",
"openclaw",
"openclip",
"pulp-free-chat",
"pytorch",
"vllm",
];
const DEMAND_SCORE_RANGE: std::ops::RangeInclusive<i64> = 1..=5;
fn validate_crux_intake(contract: &Contract, violations: &mut Vec<Violation>) {
if let Some(score) = contract.metadata.demand_score {
if !DEMAND_SCORE_RANGE.contains(&score) {
violations.push(Violation {
severity: Severity::Error,
rule: "CRUX-001".to_string(),
message: format!(
"metadata.demand_score {score} is outside the documented range {}..={} \
— it is the priority signal pmat work sorts by, so an out-of-range \
value silently outranks every real story",
DEMAND_SCORE_RANGE.start(),
DEMAND_SCORE_RANGE.end(),
),
location: Some("metadata.demand_score".to_string()),
});
}
}
if let Some(competitor) = contract.metadata.competitor.as_deref() {
if !CRUX_COMPETITORS.contains(&competitor) {
violations.push(Violation {
severity: Severity::Error,
rule: "CRUX-002".to_string(),
message: format!(
"metadata.competitor {competitor:?} is not a known competitive-research \
source — must be one of: {}",
CRUX_COMPETITORS.join(", ")
),
location: Some("metadata.competitor".to_string()),
});
}
}
validate_crux_registry_stories(contract, violations);
}
fn validate_crux_registry_stories(contract: &Contract, violations: &mut Vec<Violation>) {
for story in &contract.stories {
let at = |field: &str| Some(format!("stories[{}].{field}", story.id));
match story.demand_score {
None => violations.push(Violation {
severity: Severity::Error,
rule: "CRUX-001".to_string(),
message: format!(
"registry story {} has no demand_score — it is the priority signal \
pmat work sorts by, and an absent one sorts arbitrarily",
story.id
),
location: at("demand_score"),
}),
Some(score) if !DEMAND_SCORE_RANGE.contains(&score) => violations.push(Violation {
severity: Severity::Error,
rule: "CRUX-001".to_string(),
message: format!(
"registry story {} has demand_score {score}, outside the documented \
range {}..={} — a single fabricated score reorders the whole queue",
story.id,
DEMAND_SCORE_RANGE.start(),
DEMAND_SCORE_RANGE.end(),
),
location: at("demand_score"),
}),
Some(_) => {}
}
match story.competitor.as_deref() {
None => violations.push(Violation {
severity: Severity::Error,
rule: "CRUX-002".to_string(),
message: format!(
"registry story {} has no competitor — the row cannot be attributed \
to the UX it was extracted from",
story.id
),
location: at("competitor"),
}),
Some(c) if !CRUX_COMPETITORS.contains(&c) => violations.push(Violation {
severity: Severity::Error,
rule: "CRUX-002".to_string(),
message: format!(
"registry story {} names competitor {c:?}, which is not a known \
competitive-research source — must be one of: {}",
story.id,
CRUX_COMPETITORS.join(", ")
),
location: at("competitor"),
}),
Some(_) => {}
}
}
}
const BEAT_INCUMBENTS: [&str; 5] = ["scikit-learn", "pytorch", "unsloth", "ollama", "llama.cpp"];
fn validate_beat_benchmark(contract: &Contract, violations: &mut Vec<Violation>) {
let push = |violations: &mut Vec<Violation>, rule: &str, message: String, field: &str| {
violations.push(Violation {
severity: Severity::Error,
rule: rule.to_string(),
message,
location: Some(format!("beat.{field}")),
});
};
let Some(beat) = contract.beat.as_ref() else {
violations.push(Violation {
severity: Severity::Error,
rule: "BEAT-001".to_string(),
message: "beat-benchmark contract must define a `beat:` block \
(incumbent, metric, direction, beat_threshold, ci_gate_name)"
.to_string(),
location: Some("beat".to_string()),
});
return;
};
let incumbent = beat.incumbent.trim().to_lowercase();
if incumbent.is_empty() {
push(
violations,
"BEAT-002",
"beat.incumbent must not be empty".to_string(),
"incumbent",
);
} else if !BEAT_INCUMBENTS.iter().any(|p| incumbent.contains(p)) {
push(
violations,
"BEAT-002",
format!(
"beat.incumbent {:?} must name one of the four pillars ({})",
beat.incumbent,
BEAT_INCUMBENTS.join(", ")
),
"incumbent",
);
}
if beat.metric.trim().is_empty() {
push(
violations,
"BEAT-003",
"beat.metric must name the measured quantity (e.g. accuracy, wall_clock_ms, \
tokens_per_sec)"
.to_string(),
"metric",
);
}
match beat.direction.trim() {
"higher_is_better" | "lower_is_better" => {}
other => push(
violations,
"BEAT-004",
format!(
"beat.direction must be `higher_is_better` or `lower_is_better`, got {other:?}"
),
"direction",
),
}
match beat.beat_threshold {
None => push(
violations,
"BEAT-005",
"beat.beat_threshold is required — the pinned value CI fails below".to_string(),
"beat_threshold",
),
Some(t) if !t.is_finite() => push(
violations,
"BEAT-005",
format!("beat.beat_threshold must be finite, got {t}"),
"beat_threshold",
),
Some(_) => {}
}
if beat.ci_gate_name.trim().is_empty() {
push(
violations,
"BEAT-006",
"beat.ci_gate_name must name the CI test that enforces this gate".to_string(),
"ci_gate_name",
);
}
match beat
.approved_compute
.as_deref()
.map(|c| c.trim().to_uppercase())
{
None => push(
violations,
"BEAT-007",
"beat.approved_compute is required — must be `CPU` or `GPU`".to_string(),
"approved_compute",
),
Some(ref c) if c != "CPU" && c != "GPU" => push(
violations,
"BEAT-007",
format!(
"beat.approved_compute must be `CPU` or `GPU`, got {:?}",
beat.approved_compute
),
"approved_compute",
),
Some(_) => {}
}
}
fn validate_provability_invariant(contract: &Contract, violations: &mut Vec<Violation>) {
for v in contract.provability_violations() {
violations.push(Violation {
severity: Severity::Error,
rule: "PROVABILITY-001".to_string(),
message: v,
location: None,
});
}
}
fn key_forms(key: &str) -> Vec<String> {
let squashed: String = key
.chars()
.filter(char::is_ascii_alphanumeric)
.map(|c| c.to_ascii_lowercase())
.collect();
let mut forms = vec![squashed.clone()];
for suffix in ["es", "s"] {
if let Some(stem) = squashed.strip_suffix(suffix) {
if !stem.is_empty() {
forms.push(stem.to_string());
}
}
}
forms
}
fn near_miss_of(key: &str) -> Option<&'static str> {
let forms = key_forms(key);
CONTRACT_TOP_LEVEL_FIELDS
.iter()
.copied()
.find(|field| key_forms(field).iter().any(|f| forms.contains(f)))
}
fn validate_top_level_keys(contract: &Contract, violations: &mut Vec<Violation>) {
if let Some(err) = contract.strict_yaml_error.as_ref() {
violations.push(Violation {
severity: Severity::Error,
rule: "SCHEMA-020".to_string(),
message: format!(
"the contract schema accepted this document but a strict YAML reader \
rejects it ({err}) — `yq`, PyYAML and any `serde_yaml::Value` consumer \
will drop content here. A duplicate mapping key is the usual cause: \
merge the two blocks into one"
),
location: None,
});
}
for key in &contract.unknown_top_level_keys {
if key == "kind" {
violations.push(Violation {
severity: Severity::Error,
rule: "SCHEMA-018".to_string(),
message: "top-level `kind:` is not part of the contract schema and is \
silently dropped — the contract's kind comes from \
`metadata.kind:` (or defaults to `kernel`). Move it under \
`metadata:` if it names a real kind, or delete it"
.to_string(),
location: Some("kind".to_string()),
});
} else if let Some(field) = near_miss_of(key) {
violations.push(Violation {
severity: Severity::Error,
rule: "SCHEMA-019".to_string(),
message: format!(
"top-level `{key}:` is not a contract field and is silently dropped \
— did you mean `{field}:`? Everything under `{key}:` is invisible \
to every pv gate"
),
location: Some(key.clone()),
});
}
}
}
fn validate_metadata(contract: &Contract, violations: &mut Vec<Violation>) {
if contract.metadata.references.is_empty() {
violations.push(Violation {
severity: Severity::Error,
rule: "SCHEMA-001".to_string(),
message: "metadata.references must not be empty — \
every contract must cite its source paper(s)"
.to_string(),
location: Some("metadata.references".to_string()),
});
}
if contract.metadata.version.is_empty() {
violations.push(Violation {
severity: Severity::Error,
rule: "SCHEMA-002".to_string(),
message: "metadata.version must not be empty".to_string(),
location: Some("metadata.version".to_string()),
});
}
}
fn validate_equations(contract: &Contract, violations: &mut Vec<Violation>) {
if contract.equations.is_empty() {
violations.push(Violation {
severity: Severity::Error,
rule: "SCHEMA-003".to_string(),
message: "equations must contain at least one equation".to_string(),
location: Some("equations".to_string()),
});
}
for (name, eq) in &contract.equations {
if eq.formula.is_empty() {
violations.push(Violation {
severity: Severity::Error,
rule: "SCHEMA-004".to_string(),
message: format!("equations.{name}.formula must not be empty"),
location: Some(format!("equations.{name}.formula")),
});
}
}
}
fn validate_proof_obligations(contract: &Contract, violations: &mut Vec<Violation>) {
let mut seen_formal = HashSet::new();
for (i, ob) in contract.proof_obligations.iter().enumerate() {
validate_obligation_identity(i, ob, &mut seen_formal, violations);
validate_obligation_dbc_fields(i, ob, violations);
validate_obligation_parent_link(i, ob, contract, violations);
validate_obligation_not_applicable(i, ob, violations);
}
}
fn validate_obligation_not_applicable(
index: usize,
ob: &crate::schema::types::ProofObligation,
violations: &mut Vec<Violation>,
) {
let blank = |v: &Option<String>| v.as_deref().is_none_or(|s| s.trim().is_empty());
let mut push = |rule: &str, field: &str, message: String| {
violations.push(Violation {
severity: Severity::Error,
rule: rule.to_string(),
message,
location: Some(format!("proof_obligations[{index}].{field}")),
});
};
if ob.is_not_applicable() {
if blank(&ob.na_reason) {
push(
"SCHEMA-021",
"na_reason",
format!(
"proof_obligations[{index}] is applies_to: not_applicable \
but na_reason is missing or empty — say why it is not a code property"
),
);
}
if blank(&ob.na_owner) {
push(
"SCHEMA-022",
"na_owner",
format!(
"proof_obligations[{index}] is applies_to: not_applicable \
but na_owner is missing or empty — name the bench, check or \
evidence command that verifies it"
),
);
}
return;
}
for (field, value) in [("na_reason", &ob.na_reason), ("na_owner", &ob.na_owner)] {
if value.is_some() {
push(
"SCHEMA-023",
field,
format!(
"proof_obligations[{index}].{field} is only valid with \
applies_to: not_applicable — a dangling justification is decoration"
),
);
}
}
}
fn validate_obligation_identity(
index: usize,
ob: &crate::schema::types::ProofObligation,
seen_formal: &mut HashSet<String>,
violations: &mut Vec<Violation>,
) {
if ob.property.is_empty() {
violations.push(Violation {
severity: Severity::Error,
rule: "SCHEMA-005".to_string(),
message: format!("proof_obligations[{index}].property must not be empty"),
location: Some(format!("proof_obligations[{index}].property")),
});
}
if let Some(ref formal) = ob.formal {
if !seen_formal.insert(formal.clone()) {
violations.push(Violation {
severity: Severity::Warning,
rule: "SCHEMA-006".to_string(),
message: format!("Duplicate formal predicate: {formal}"),
location: Some(format!("proof_obligations[{index}].formal")),
});
}
}
}
fn validate_obligation_dbc_fields(
index: usize,
ob: &crate::schema::types::ProofObligation,
violations: &mut Vec<Violation>,
) {
use crate::schema::types::ObligationType;
let misplaced: [(bool, &str, &str, &str); 3] = [
(
ob.requires.is_some() && ob.obligation_type != ObligationType::Postcondition,
"SCHEMA-014",
"requires",
"postcondition",
),
(
ob.applies_to_phase.is_some()
&& ob.obligation_type != ObligationType::LoopInvariant
&& ob.obligation_type != ObligationType::LoopVariant,
"SCHEMA-015",
"applies_to_phase",
"loop_invariant or loop_variant",
),
(
ob.parent_contract.is_some() && ob.obligation_type != ObligationType::Subcontract,
"SCHEMA-016",
"parent_contract",
"subcontract",
),
];
for (is_misplaced, rule, field, valid_on) in misplaced {
if is_misplaced {
violations.push(Violation {
severity: Severity::Error,
rule: rule.to_string(),
message: format!(
"proof_obligations[{index}].{field} is only valid on \
{valid_on} obligations (found on {})",
ob.obligation_type
),
location: Some(format!("proof_obligations[{index}].{field}")),
});
}
}
}
fn validate_obligation_parent_link(
index: usize,
ob: &crate::schema::types::ProofObligation,
contract: &Contract,
violations: &mut Vec<Violation>,
) {
use crate::schema::types::ObligationType;
let Some(parent) = ob.parent_contract.as_ref() else {
return;
};
if ob.obligation_type != ObligationType::Subcontract
|| contract.metadata.depends_on.contains(parent)
{
return;
}
violations.push(Violation {
severity: Severity::Error,
rule: "SCHEMA-017".to_string(),
message: format!(
"proof_obligations[{index}].parent_contract \"{parent}\" \
must be listed in metadata.depends_on"
),
location: Some(format!("proof_obligations[{index}].parent_contract")),
});
}
fn validate_falsification_tests(contract: &Contract, violations: &mut Vec<Violation>) {
let mut ids = HashSet::new();
for test in &contract.falsification_tests {
if !ids.insert(&test.id) {
violations.push(Violation {
severity: Severity::Error,
rule: "SCHEMA-007".to_string(),
message: format!("Duplicate falsification test ID: {}", test.id),
location: Some(format!("falsification_tests.{}", test.id)),
});
}
if test.prediction.is_empty() {
violations.push(Violation {
severity: Severity::Error,
rule: "SCHEMA-008".to_string(),
message: format!(
"falsification_tests.{}.prediction must not be empty — \
every test must make a falsifiable prediction",
test.id
),
location: Some(format!("falsification_tests.{}.prediction", test.id)),
});
}
if test.if_fails.is_empty() {
violations.push(Violation {
severity: Severity::Warning,
rule: "SCHEMA-009".to_string(),
message: format!(
"falsification_tests.{}.if_fails is empty — \
should describe root cause diagnosis",
test.id
),
location: Some(format!("falsification_tests.{}.if_fails", test.id)),
});
}
}
}
fn validate_kani_harnesses(contract: &Contract, violations: &mut Vec<Violation>) {
let mut ids = HashSet::new();
for harness in &contract.kani_harnesses {
if !ids.insert(&harness.id) {
violations.push(Violation {
severity: Severity::Error,
rule: "SCHEMA-010".to_string(),
message: format!("Duplicate Kani harness ID: {}", harness.id),
location: Some(format!("kani_harnesses.{}", harness.id)),
});
}
if harness.obligation.is_empty() {
violations.push(Violation {
severity: Severity::Error,
rule: "SCHEMA-011".to_string(),
message: format!(
"kani_harnesses.{}.obligation must not be empty — \
every harness must reference a proof obligation",
harness.id
),
location: Some(format!("kani_harnesses.{}.obligation", harness.id)),
});
}
if harness.bound.is_none() {
violations.push(Violation {
severity: Severity::Warning,
rule: "SCHEMA-012".to_string(),
message: format!(
"kani_harnesses.{}.bound not specified — \
Kani requires an unwind bound",
harness.id
),
location: Some(format!("kani_harnesses.{}.bound", harness.id)),
});
}
}
}
fn validate_qa_gate(contract: &Contract, violations: &mut Vec<Violation>) {
if contract.qa_gate.is_none() {
violations.push(Violation {
severity: Severity::Warning,
rule: "SCHEMA-013".to_string(),
message: "No qa_gate defined — contract should define a \
certeza quality gate"
.to_string(),
location: Some("qa_gate".to_string()),
});
}
}
#[cfg(test)]
mod tests {
include!("validator_tests.rs");
}