pub mod cache;
pub mod capability_cells_gate;
mod composition_gate;
pub mod config;
pub mod consistency_gate;
pub mod diff;
pub mod duplicate_stems;
pub mod evidence_gate;
pub mod finding;
mod gates;
pub use gates::collect_yaml_files;
pub mod bindings_gate;
mod gates_extended;
pub mod ratchet_gates;
pub mod refinement_gate;
pub mod refines_gate;
pub mod relations_gate;
pub mod rules;
pub mod sarif;
pub mod shapes_gate;
pub mod sigma_gate;
pub mod sigma_symbols;
mod strict_test_binding;
pub mod subsumption;
pub mod tbox_gate;
pub mod trend;
pub mod valid_under_gate;
use std::collections::{HashMap, HashSet};
use std::path::Path;
use std::time::Instant;
use serde::Serialize;
use self::finding::LintFinding;
use self::gates::{
load_binding, load_contracts, run_audit_gate, run_score_gate, run_validate_gate,
};
use self::gates_extended::{
check_stale_suppressions, run_enforce_gate, run_enforcement_level_gate,
run_reverse_coverage_gate, run_verify_gate,
};
use self::rules::RuleSeverity;
use crate::ontology::arming::{meet_armed, ArmedGates};
use crate::ontology::verdict::Verdict;
#[derive(Debug, Clone, Serialize)]
pub struct GateResult {
pub name: String,
pub passed: bool,
pub skipped: bool,
pub verdict: Verdict,
pub duration_ms: u64,
pub detail: GateDetail,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub extra: Option<GateExtra>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type")]
pub enum GateDetail {
#[serde(rename = "validate")]
Validate {
contracts: usize,
errors: usize,
warnings: usize,
error_messages: Vec<String>,
},
#[serde(rename = "audit")]
Audit {
contracts: usize,
findings: usize,
finding_messages: Vec<String>,
},
#[serde(rename = "score")]
Score {
contracts: usize,
min_score: f64,
mean_score: f64,
threshold: f64,
below_threshold: Vec<String>,
},
#[serde(rename = "verify")]
Verify {
total_refs: usize,
existing: usize,
missing: usize,
},
#[serde(rename = "enforce")]
Enforce {
equations_total: usize,
equations_with_pre: usize,
equations_with_post: usize,
equations_with_lean: usize,
},
#[serde(rename = "reverse_coverage")]
ReverseCoverage {
total_pub_fns: usize,
bound_fns: usize,
unbound_fns: usize,
coverage_pct: f64,
threshold_pct: f64,
},
#[serde(rename = "composition")]
Composition {
edges_checked: usize,
edges_satisfied: usize,
edges_broken: usize,
},
#[serde(rename = "skipped")]
Skipped { reason: String },
}
#[derive(Debug, Clone, Serialize)]
pub struct ShapesControls {
pub pc_extract: std::collections::BTreeMap<String, String>,
pub pc_shapes: std::collections::BTreeMap<String, String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub capability_cells: Option<capability_cells_gate::CapabilityCellsReport>,
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type")]
#[non_exhaustive]
pub enum GateExtra {
#[serde(rename = "duplicate_stems")]
DuplicateStems {
divergent: usize,
baselined: usize,
unbaselined: Vec<String>,
stale: Vec<String>,
divergent_stems: Vec<String>,
},
#[serde(rename = "sigma")]
Sigma {
entity_types: usize,
roles: usize,
symbols: usize,
contracts_checked: usize,
violations: usize,
formal_total: usize,
formal_prose: usize,
},
#[serde(rename = "relations")]
Relations {
relations_n: usize,
contracts_with_relations: usize,
roles_used: Vec<String>,
cycles: Vec<String>,
legacy_depends_on: usize,
legacy_unresolved_depends_on: usize,
violations: usize,
},
#[serde(rename = "evidence")]
Evidence {
contracts_checked: usize,
contracts_with_evidence: usize,
entity_types_checked: usize,
entity_types: Vec<String>,
levels_source: String,
by_level: Vec<String>,
census_git_sha: Option<String>,
unresolved_git_shas: usize,
violations: usize,
},
#[serde(rename = "valid_under")]
ValidUnder {
worlds: Vec<String>,
contracts_checked: usize,
kernel_contracts: usize,
contracts_with_valid_under: usize,
contracts_without_valid_under: usize,
baseline: Option<usize>,
by_world: Vec<String>,
violations: usize,
},
#[serde(rename = "theorem_pairing")]
TheoremPairing {
lean_base: String,
book_pages: usize,
theorem_modules: usize,
paired: usize,
unpaired_theorem_modules: usize,
baseline: Option<usize>,
unpaired: Vec<String>,
violations: usize,
},
#[serde(rename = "depends_on_present")]
DependsOnPresent {
contracts_checked: usize,
kernel_contracts: usize,
contracts_without_depends_on: usize,
baseline: Option<usize>,
violations: usize,
},
#[serde(rename = "proved_is_derived")]
ProvedIsDerived {
summary: Option<String>,
summary_error: Option<String>,
summary_green: bool,
contracts_checked: usize,
proved_claims: usize,
underived_proved_claims: usize,
baseline: Option<usize>,
underived: Vec<String>,
violations: usize,
},
#[serde(rename = "challenge_fresh")]
ChallengeFresh {
lean_dir: String,
files: usize,
stale: Vec<String>,
unrestated: Vec<String>,
},
#[serde(rename = "shapes")]
Shapes {
shapes_n: usize,
focus_nodes_n: usize,
pc_shape: String,
plant_violations: usize,
violations: usize,
warnings: usize,
by_shape: Vec<String>,
triples: usize,
armed_shapes: Vec<String>,
not_armed_shapes: Vec<String>,
declines: Vec<String>,
unarmed_violations: usize,
by_entity_type: std::collections::BTreeMap<String, usize>,
#[serde(flatten)]
controls: Box<ShapesControls>,
receipts: usize,
witnesses: usize,
hex_mismatches: usize,
unmeasured_rows: usize,
w3c_cases_passed: usize,
w3c_cases_n: usize,
#[serde(flatten)]
counters: Box<ShapesCounters>,
#[serde(skip_serializing_if = "Option::is_none")]
release: Option<Box<crate::ontology::extract::release_evidence::ReleaseStats>>,
readme: MeasuredSet,
claude_md: MeasuredSet,
ratchets: Ratchets,
},
#[serde(rename = "ont_consistency")]
Consistency {
checkable_n: usize,
units: usize,
unencoded_edges: usize,
pc_checker: String,
core: Vec<String>,
violations: usize,
witness: consistency_gate::WitnessReport,
},
#[serde(rename = "refines")]
Refines(Box<refines_gate::RefinesCounters>),
#[serde(rename = "bindings")]
Bindings(Box<bindings_gate::BindingsCounters>),
#[serde(rename = "refinement")]
Refinement(Box<refinement_gate::RefinementCounters>),
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct ShapesCounters {
pub symbols_resolved: usize,
pub symbols_unresolved: usize,
pub lean_statements: usize,
pub lean_refs_unresolved: usize,
pub inherited_shapes_applied: usize,
pub inherited_by_shape: Vec<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
pub struct MeasuredSet {
pub verified_commands: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct Ratchets {
pub measured_sets: String,
}
impl Default for Ratchets {
fn default() -> Self {
Self {
measured_sets: "not-checked".into(),
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct LintReport {
pub passed: bool,
pub gates: Vec<GateResult>,
pub total_duration_ms: u64,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub findings: Vec<LintFinding>,
#[serde(skip)]
pub cache_stats: cache::CacheStats,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub contract_timings: Vec<(String, u64)>,
pub verdict: Verdict,
pub armed_gates: Vec<ArmedGateVerdict>,
pub not_armed: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub armed_monotone: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub armed_shapes_monotone: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct ArmedGateVerdict {
pub name: String,
pub verdict: Verdict,
}
impl LintReport {
pub fn arm(&mut self, armed: &ArmedGates) {
let results: Vec<(String, Verdict)> = self
.gates
.iter()
.map(|g| (g.name.clone(), g.verdict))
.collect();
let meet = meet_armed(&results, armed);
self.verdict = meet.verdict;
self.armed_gates = meet
.armed
.into_iter()
.map(|(name, verdict)| ArmedGateVerdict { name, verdict })
.collect();
self.not_armed = meet.not_armed;
}
}
pub struct LintConfig<'a> {
pub contract_dir: &'a Path,
pub binding_path: Option<&'a Path>,
pub min_score: f64,
pub severity_filter: Option<RuleSeverity>,
pub severity_overrides: HashMap<String, RuleSeverity>,
pub suppressed_findings: Vec<String>,
pub suppressed_rules: Vec<String>,
pub suppressed_files: Vec<String>,
pub strict: bool,
pub no_cache: bool,
pub cache_stats: bool,
pub crate_dir: Option<&'a Path>,
pub min_level: Option<crate::schema::EnforcementLevel>,
pub strict_test_binding: bool,
pub strict_test_binding_only: bool,
}
impl<'a> LintConfig<'a> {
pub fn new(contract_dir: &'a Path, binding_path: Option<&'a Path>, min_score: f64) -> Self {
Self {
contract_dir,
binding_path,
min_score,
severity_filter: None,
severity_overrides: HashMap::new(),
suppressed_findings: Vec::new(),
suppressed_rules: Vec::new(),
suppressed_files: Vec::new(),
strict: false,
no_cache: false,
cache_stats: false,
crate_dir: None,
min_level: None,
strict_test_binding: false,
strict_test_binding_only: false,
}
}
}
fn push_gate<F>(
gates: &mut Vec<GateResult>,
all_findings: &mut Vec<LintFinding>,
run: F,
name: &str,
validation_passed: bool,
) where
F: FnOnce() -> (GateResult, Vec<LintFinding>),
{
let (result, mut findings) = if validation_passed {
run()
} else {
(skipped_gate(name, "validation failed"), Vec::new())
};
gates.push(result);
all_findings.append(&mut findings);
}
fn per_contract_timings(
contracts: &[(String, crate::schema::Contract)],
binding: Option<&crate::binding::BindingRegistry>,
) -> Vec<(String, u64)> {
let mut contract_timings: Vec<(String, u64)> = Vec::with_capacity(contracts.len());
for (stem, contract) in contracts {
let ct_start = Instant::now();
let _ = crate::schema::validate_contract(contract);
let _ = crate::audit::audit_contract(contract);
let _ = crate::scoring::score_contract(contract, binding, stem);
let ct_ms = u64::try_from(ct_start.elapsed().as_micros() / 1000).unwrap_or(0);
contract_timings.push((format!("{stem}.yaml"), ct_ms));
}
contract_timings.sort_by_key(|b| std::cmp::Reverse(b.1));
contract_timings
}
fn store_findings_in_cache(
root: &std::path::Path,
config: &LintConfig,
contracts: &[(String, crate::schema::Contract)],
all_findings: &[LintFinding],
stats: &mut cache::CacheStats,
) {
let rule_cfg = format!("{:?}{:?}", config.severity_overrides, config.strict);
for (stem, _) in contracts {
stats.total += 1;
let yaml_path = config.contract_dir.join(format!("{stem}.yaml"));
let yaml_content = std::fs::read_to_string(&yaml_path).unwrap_or_default();
let hash = cache::content_hash(&yaml_content, &rule_cfg);
if cache::cache_get(root, &hash).is_some() {
stats.hits += 1;
} else {
stats.misses += 1;
let contract_findings: Vec<_> = all_findings
.iter()
.filter(|f| f.contract_stem.as_deref() == Some(stem.as_str()))
.cloned()
.collect();
let _ = cache::cache_put(root, &hash, &contract_findings);
}
}
}
fn run_gates_2_to_9(
config: &LintConfig,
contracts: &[(String, crate::schema::Contract)],
binding: Option<&crate::binding::BindingRegistry>,
validation_passed: bool,
gates: &mut Vec<GateResult>,
all_findings: &mut Vec<LintFinding>,
) {
push_gate(
gates,
all_findings,
|| run_audit_gate(contracts),
"audit",
validation_passed,
);
push_gate(
gates,
all_findings,
|| run_score_gate(contracts, binding, config.min_score),
"score",
validation_passed,
);
push_gate(
gates,
all_findings,
|| {
let project_root = config.contract_dir.parent().unwrap_or(config.contract_dir);
run_verify_gate(contracts, project_root)
},
"verify",
validation_passed,
);
push_gate(
gates,
all_findings,
|| run_enforce_gate(contracts),
"enforce",
validation_passed,
);
push_gate(
gates,
all_findings,
|| {
let min_level = config
.min_level
.unwrap_or(crate::schema::EnforcementLevel::Standard);
run_enforcement_level_gate(contracts, min_level)
},
"enforcement-level",
validation_passed,
);
push_gate(
gates,
all_findings,
|| match (config.binding_path, config.crate_dir) {
(Some(bp), Some(cd)) => run_reverse_coverage_gate(bp, cd),
_ => (
skipped_gate("reverse-coverage", "no --binding or --crate-dir provided"),
Vec::new(),
),
},
"reverse-coverage",
validation_passed,
);
let duplicates = duplicate_stems::scan_duplicate_stems(config.contract_dir);
let ambiguous = duplicate_stems::ambiguous_stems(&duplicates);
push_gate(
gates,
all_findings,
|| {
let project_root = config.contract_dir.parent().unwrap_or(config.contract_dir);
let baseline = duplicate_stems::read_baseline(project_root);
duplicate_stems::run_duplicate_stem_gate(&duplicates, &baseline)
},
"duplicate-stems",
validation_passed,
);
push_gate(
gates,
all_findings,
|| composition_gate::run_composition_gate(contracts, &ambiguous),
"composition",
validation_passed,
);
}
fn run_ontology_gates_10_to_22(
contract_dir: &Path,
validation_passed: bool,
gates: &mut Vec<GateResult>,
all_findings: &mut Vec<LintFinding>,
) {
let (sigma_gate_result, mut sigma_findings) = sigma_result(contract_dir, validation_passed);
gates.push(sigma_gate_result);
all_findings.append(&mut sigma_findings);
let (relations_gate_result, mut relations_findings) =
relations_result(contract_dir, validation_passed);
gates.push(relations_gate_result);
all_findings.append(&mut relations_findings);
let (shapes_gate_result, mut shapes_findings) = shapes_result(contract_dir, validation_passed);
gates.push(shapes_gate_result);
all_findings.append(&mut shapes_findings);
let (valid_under_gate_result, mut valid_under_findings) =
valid_under_result(contract_dir, validation_passed);
gates.push(valid_under_gate_result);
all_findings.append(&mut valid_under_findings);
for (name, run) in RATCHET_GATES {
let (result, mut findings) = ratchet_result(contract_dir, validation_passed, name, run);
gates.push(result);
all_findings.append(&mut findings);
}
gates.push(challenge_result(contract_dir));
let (consistency_gate_result, mut consistency_findings) =
consistency_result(contract_dir, validation_passed);
gates.push(consistency_gate_result);
all_findings.append(&mut consistency_findings);
let (refines_gate_result, mut refines_findings) =
refines_result(contract_dir, validation_passed);
gates.push(refines_gate_result);
all_findings.append(&mut refines_findings);
let (bindings_gate_result, mut bindings_findings) = ratchet_result(
contract_dir,
validation_passed,
bindings_gate::GATE,
bindings_gate::run_bindings_gate,
);
gates.push(bindings_gate_result);
all_findings.append(&mut bindings_findings);
let (refinement_gate_result, mut refinement_findings) = ratchet_result(
contract_dir,
validation_passed,
refinement_gate::GATE,
refinement_gate::run_refinement_gate,
);
gates.push(refinement_gate_result);
all_findings.append(&mut refinement_findings);
let (evidence_gate_result, mut evidence_findings) =
evidence_result(contract_dir, validation_passed);
gates.push(evidence_gate_result);
all_findings.append(&mut evidence_findings);
}
fn run_strict_test_binding_gate_if_enabled(
config: &LintConfig,
contracts: &[(String, crate::schema::Contract)],
validation_passed: bool,
gates: &mut Vec<GateResult>,
all_findings: &mut Vec<LintFinding>,
) {
if !config.strict_test_binding {
return;
}
push_gate(
gates,
all_findings,
|| {
let project_root = config.contract_dir.parent().unwrap_or(config.contract_dir);
strict_test_binding::run_strict_test_binding_gate(
contracts,
project_root,
config.strict,
)
},
"strict-test-binding",
validation_passed,
);
}
#[allow(clippy::too_many_arguments)]
fn finalize_findings(
config: &LintConfig,
contracts: &[(String, crate::schema::Contract)],
binding: Option<&crate::binding::BindingRegistry>,
validation_passed: bool,
cache_root: Option<&std::path::Path>,
stats: &mut cache::CacheStats,
all_findings: &mut Vec<LintFinding>,
mut validate_findings: Vec<LintFinding>,
) -> Vec<(String, u64)> {
all_findings.append(&mut validate_findings);
let contract_timings = if validation_passed {
per_contract_timings(contracts, binding)
} else {
Vec::new()
};
let mut stale_findings = check_stale_suppressions(
all_findings,
&config.suppressed_rules,
&config.suppressed_findings,
);
all_findings.append(&mut stale_findings);
mark_new_findings(all_findings, config.contract_dir);
if let Some(root) = cache_root {
store_findings_in_cache(root, config, contracts, all_findings, stats);
}
apply_suppressions(all_findings, config);
apply_severity_overrides(all_findings, config);
if let Some(min_sev) = config.severity_filter {
all_findings.retain(|f| f.severity >= min_sev);
}
contract_timings
}
pub fn run_lint(config: &LintConfig) -> LintReport {
let overall_start = Instant::now();
let mut gates = Vec::with_capacity(3);
let mut all_findings = Vec::new();
let mut stats = cache::CacheStats::default();
let cache_root = if config.no_cache || config.strict_test_binding_only {
None
} else {
Some(cache::cache_dir(config.contract_dir))
};
let (contracts, parse_errors) = load_contracts(config.contract_dir);
let binding = load_binding(config.binding_path);
let (validate_result, validate_findings) = run_validate_gate(&contracts, &parse_errors);
let validation_passed = validate_result.passed;
gates.push(validate_result);
if !config.strict_test_binding_only {
run_gates_2_to_9(
config,
&contracts,
binding.as_ref(),
validation_passed,
&mut gates,
&mut all_findings,
);
run_ontology_gates_10_to_22(
config.contract_dir,
validation_passed,
&mut gates,
&mut all_findings,
);
}
run_strict_test_binding_gate_if_enabled(
config,
&contracts,
validation_passed,
&mut gates,
&mut all_findings,
);
let contract_timings = finalize_findings(
config,
&contracts,
binding.as_ref(),
validation_passed,
cache_root.as_deref(),
&mut stats,
&mut all_findings,
validate_findings,
);
let passed = gates.iter().all(|g| g.passed || g.skipped);
let mut report = LintReport {
passed,
gates,
total_duration_ms: u64::try_from(overall_start.elapsed().as_millis()).unwrap_or(u64::MAX),
findings: all_findings,
cache_stats: stats,
contract_timings,
verdict: Verdict::Unknown(crate::ontology::verdict::Reason::NotArmed),
armed_gates: Vec::new(),
not_armed: Vec::new(),
armed_monotone: None,
armed_shapes_monotone: None,
};
report.arm(&ArmedGates::default_set());
report
}
pub enum NamedGateOutcome {
UnknownGate,
Sigma(sigma_gate::SigmaOutcome),
Relations(relations_gate::RelationsOutcome),
Shapes(shapes_gate::ShapesOutcome),
Consistency(consistency_gate::ConsistencyOutcome),
Refines(refines_gate::RefinesOutcome),
Tbox(tbox_gate::TboxOutcome),
Evidence(evidence_gate::EvidenceOutcome),
ValidUnder(valid_under_gate::ValidUnderOutcome),
Ratchet(ratchet_gates::RatchetOutcome),
Ran {
result: Box<GateResult>,
findings: Vec<LintFinding>,
},
}
#[must_use]
pub fn run_named_gate(contract_dir: &Path, name: &str) -> NamedGateOutcome {
run_named_gate_with(contract_dir, name, &shapes_gate::ShapesOptions::default())
}
pub fn run_named_gate_with(
contract_dir: &Path,
name: &str,
shapes_opts: &shapes_gate::ShapesOptions,
) -> NamedGateOutcome {
match name {
"evidence" => NamedGateOutcome::Evidence(evidence_gate::run_evidence_gate(contract_dir)),
"relations" => {
NamedGateOutcome::Relations(relations_gate::run_relations_gate(contract_dir))
}
"shapes" => {
NamedGateOutcome::Shapes(shapes_gate::run_shapes_gate_with(contract_dir, shapes_opts))
}
"sigma" => NamedGateOutcome::Sigma(sigma_gate::run_sigma_gate(contract_dir)),
consistency_gate::GATE => {
NamedGateOutcome::Consistency(consistency_gate::run_consistency_gate(contract_dir))
}
refines_gate::GATE => {
NamedGateOutcome::Refines(refines_gate::run_refines_gate(contract_dir))
}
bindings_gate::GATE => {
NamedGateOutcome::Ratchet(bindings_gate::run_bindings_gate(contract_dir))
}
refinement_gate::GATE => {
NamedGateOutcome::Ratchet(refinement_gate::run_refinement_gate(contract_dir))
}
"tbox" => NamedGateOutcome::Tbox(tbox_gate::run_tbox_gate(contract_dir)),
"valid-under" => {
NamedGateOutcome::ValidUnder(valid_under_gate::run_valid_under_gate(contract_dir))
}
"theorem-pairing" => {
NamedGateOutcome::Ratchet(ratchet_gates::run_theorem_pairing_gate(contract_dir))
}
"depends-on-present" => {
NamedGateOutcome::Ratchet(ratchet_gates::run_depends_on_present_gate(contract_dir))
}
"proved-is-derived" => {
NamedGateOutcome::Ratchet(ratchet_gates::run_proved_is_derived_gate(contract_dir))
}
"validate" => {
let (contracts, parse_errors) = load_contracts(contract_dir);
let (result, findings) = run_validate_gate(&contracts, &parse_errors);
NamedGateOutcome::Ran {
result: Box::new(result),
findings,
}
}
_ => NamedGateOutcome::UnknownGate,
}
}
pub const NAMED_GATES: [&str; 14] = [
bindings_gate::GATE,
refinement_gate::GATE,
"depends-on-present",
"evidence",
consistency_gate::GATE,
"proved-is-derived",
refines_gate::GATE,
"relations",
"shapes",
"sigma",
"tbox",
"theorem-pairing",
"valid-under",
"validate",
];
type RatchetRun = fn(&Path) -> ratchet_gates::RatchetOutcome;
const RATCHET_GATES: [(&str, RatchetRun); 3] = [
("theorem-pairing", ratchet_gates::run_theorem_pairing_gate),
(
"depends-on-present",
ratchet_gates::run_depends_on_present_gate,
),
(
"proved-is-derived",
ratchet_gates::run_proved_is_derived_gate,
),
];
fn ratchet_result(
contract_dir: &Path,
validation_passed: bool,
name: &str,
run: RatchetRun,
) -> (GateResult, Vec<LintFinding>) {
if !validation_passed {
return (skipped_gate(name, "validation failed"), Vec::new());
}
match run(contract_dir) {
ratchet_gates::RatchetOutcome::Ran { result, findings } => (*result, findings),
ratchet_gates::RatchetOutcome::Declined(why) => (skipped_gate(name, &why), Vec::new()),
}
}
fn sigma_result(contract_dir: &Path, validation_passed: bool) -> (GateResult, Vec<LintFinding>) {
if !validation_passed {
return (skipped_gate("sigma", "validation failed"), Vec::new());
}
match sigma_gate::run_sigma_gate(contract_dir) {
sigma_gate::SigmaOutcome::Ran { result, findings } => (*result, findings),
sigma_gate::SigmaOutcome::NoSigma => (
skipped_gate("sigma", "no contracts/ontology.yaml"),
Vec::new(),
),
sigma_gate::SigmaOutcome::Malformed(e) => (
skipped_gate("sigma", &format!("Σ is malformed: {e}")),
Vec::new(),
),
}
}
fn evidence_result(contract_dir: &Path, validation_passed: bool) -> (GateResult, Vec<LintFinding>) {
if !validation_passed {
return (skipped_gate("evidence", "validation failed"), Vec::new());
}
match evidence_gate::run_evidence_gate(contract_dir) {
evidence_gate::EvidenceOutcome::Ran { result, findings } => (*result, findings),
evidence_gate::EvidenceOutcome::NoSigma => (
skipped_gate("evidence", "no contracts/ontology.yaml"),
Vec::new(),
),
evidence_gate::EvidenceOutcome::Malformed(e) => (
skipped_gate("evidence", &format!("Σ is malformed: {e}")),
Vec::new(),
),
evidence_gate::EvidenceOutcome::NoEvidence { contracts_checked } => (
skipped_gate(
"evidence",
&format!(
"no evidence block in {contracts_checked} contracts — R-2: zero is a decline"
),
),
Vec::new(),
),
}
}
fn valid_under_result(
contract_dir: &Path,
validation_passed: bool,
) -> (GateResult, Vec<LintFinding>) {
if !validation_passed {
return (skipped_gate("valid-under", "validation failed"), Vec::new());
}
match valid_under_gate::run_valid_under_gate(contract_dir) {
valid_under_gate::ValidUnderOutcome::Ran { result, findings } => (*result, findings),
valid_under_gate::ValidUnderOutcome::NoSigma => (
skipped_gate("valid-under", "no contracts/ontology.yaml"),
Vec::new(),
),
valid_under_gate::ValidUnderOutcome::Malformed(e) => (
skipped_gate("valid-under", &format!("Σ is malformed: {e}")),
Vec::new(),
),
valid_under_gate::ValidUnderOutcome::NoKernels { contracts_checked } => (
skipped_gate(
"valid-under",
&format!("no kernel-kind contract and no valid_under in {contracts_checked} contracts — R-2: zero is a decline"),
),
Vec::new(),
),
}
}
fn relations_result(
contract_dir: &Path,
validation_passed: bool,
) -> (GateResult, Vec<LintFinding>) {
if !validation_passed {
return (skipped_gate("relations", "validation failed"), Vec::new());
}
match relations_gate::run_relations_gate(contract_dir) {
relations_gate::RelationsOutcome::Ran { result, findings } => (*result, findings),
relations_gate::RelationsOutcome::NoSigma => (
skipped_gate("relations", "no contracts/ontology.yaml"),
Vec::new(),
),
relations_gate::RelationsOutcome::Malformed(e) => (
skipped_gate("relations", &format!("Σ is malformed: {e}")),
Vec::new(),
),
relations_gate::RelationsOutcome::NoRelations {
contracts_checked,
legacy_depends_on,
} => (
skipped_gate(
"relations",
&format!("no typed relations in {contracts_checked} contracts ({legacy_depends_on} legacy metadata.depends_on edges) — R-2: zero is a decline"),
),
Vec::new(),
),
}
}
fn shapes_result(contract_dir: &Path, validation_passed: bool) -> (GateResult, Vec<LintFinding>) {
if !validation_passed {
return (skipped_gate("shapes", "validation failed"), Vec::new());
}
match shapes_gate::run_shapes_gate(contract_dir) {
shapes_gate::ShapesOutcome::Ran { result, findings } => (*result, findings),
shapes_gate::ShapesOutcome::Unsupported(e) => (skipped_gate("shapes", &format!("{e}")), Vec::new()),
shapes_gate::ShapesOutcome::ExtractFailed(e) => (skipped_gate("shapes", &format!("{e}")), Vec::new()),
shapes_gate::ShapesOutcome::NoShapes { contracts_checked } => (
skipped_gate("shapes", &format!("no `shape:` block in {contracts_checked} contracts — R-2: zero is a decline")),
Vec::new(),
),
shapes_gate::ShapesOutcome::NoFocus { shapes_n } => (
skipped_gate("shapes", &format!("{shapes_n} shape(s), no focus node")),
Vec::new(),
),
shapes_gate::ShapesOutcome::WrongCorpus { shapes_n, expected, found, refused } => (
skipped_gate("shapes", &format!("extract:parity-receipt matched {found} focus node(s) and evidence/parity/EXPECTED_RECEIPTS says {expected} ({shapes_n} shape(s)){} — an extractor that saw the wrong corpus reports the same \"no violations\" as one that saw all of it", if refused.is_empty() { String::new() } else { format!("; refused: {}", refused.join("; ")) })),
Vec::new(),
),
shapes_gate::ShapesOutcome::HarnessBroken { causes } => (
skipped_gate("shapes", &format!("the CRUX harness measured itself, not apr: {}", causes.join("; "))),
Vec::new(),
),
shapes_gate::ShapesOutcome::NoReceipts { shapes_n, dir } => (
skipped_gate("shapes", &format!("{shapes_n} shape(s) resolve receipts and the tree holds none under {dir}/ — R-2: unmeasured is a decline")),
Vec::new(),
),
shapes_gate::ShapesOutcome::EmptyDomain { shapes_n } => (
skipped_gate("shapes", &format!("capability-cells: the required-cell domain D is empty ({shapes_n} shape(s)) — R-2: zero is a decline, never a pass")),
Vec::new(),
),
shapes_gate::ShapesOutcome::PositiveControlFailed { shapes_n, focus_nodes_n, which } => (
skipped_gate("shapes", &format!("positive control {which} did not fire ({shapes_n} shape(s), {focus_nodes_n} focus node(s)) — the gate cannot reject")),
Vec::new(),
),
shapes_gate::ShapesOutcome::Differential { shapes_n, focus_nodes_n, passed, n, failed } => {
let mut g = skipped_gate(
"shapes",
&format!(
"W3C SHACL-Core differential: {passed} of {n} vendored case(s) pass; failed: {} ({shapes_n} shape(s), {focus_nodes_n} focus node(s)) — the validator disagrees with the standard, so no corpus verdict",
failed.join(" | ")
),
);
g.verdict = Verdict::Unknown(crate::ontology::verdict::Reason::Differential);
(g, Vec::new())
}
}
}
fn consistency_result(
contract_dir: &Path,
validation_passed: bool,
) -> (GateResult, Vec<LintFinding>) {
if !validation_passed {
return (
skipped_gate(consistency_gate::GATE, "validation failed"),
Vec::new(),
);
}
let outcome = consistency_gate::run_consistency_gate(contract_dir);
if let consistency_gate::ConsistencyOutcome::Ran { result, findings } = outcome {
return (*result, findings);
}
let mut g = skipped_gate(consistency_gate::GATE, &consistency_gate::why(&outcome));
if let Some(reason) = consistency_gate::decline_reason(&outcome) {
g.verdict = Verdict::Unknown(reason);
}
(g, Vec::new())
}
fn refines_result(contract_dir: &Path, validation_passed: bool) -> (GateResult, Vec<LintFinding>) {
if !validation_passed {
return (
skipped_gate(refines_gate::GATE, "validation failed"),
Vec::new(),
);
}
let outcome = refines_gate::run_refines_gate(contract_dir);
if let refines_gate::RefinesOutcome::Ran { result, findings } = outcome {
return (*result, findings);
}
let mut g = skipped_gate(refines_gate::GATE, &refines_gate::why(&outcome));
if let Some(reason) = refines_gate::decline_reason(&outcome) {
g.verdict = Verdict::Unknown(reason);
}
(g, Vec::new())
}
fn challenge_result(contract_dir: &Path) -> GateResult {
const NAME: &str = "challenge-fresh";
let start = Instant::now();
let root = crate::ontology::extract::repo_root(contract_dir);
let Some(lean) = crate::ontology::extract::lean::base_under(&root) else {
return skipped_gate(NAME, "no Lean theorem base under the repo root");
};
let r = match crate::discharge::challenge::render(&lean, contract_dir) {
Ok(r) => r,
Err(e) => return skipped_gate(NAME, &format!("the Lean tree does not load: {e}")),
};
if r.files.is_empty() && r.unrestated.is_empty() {
return skipped_gate(
NAME,
"zero challenges: no contract binds a theorem — R-2: zero is a decline",
);
}
let stale = crate::discharge::challenge::diff(&lean, &r);
let unrestated: Vec<String> = r
.unrestated
.iter()
.map(|(c, fqn, why)| format!("{c}: {fqn}: {why}"))
.collect();
let passed = stale.is_empty() && unrestated.is_empty();
GateResult {
name: NAME.into(),
passed,
skipped: false,
verdict: Verdict::from_gate(passed, false),
duration_ms: u64::try_from(start.elapsed().as_millis()).unwrap_or(u64::MAX),
detail: GateDetail::Validate {
contracts: r.files.len(),
errors: stale.len() + unrestated.len(),
warnings: 0,
error_messages: stale.iter().chain(&unrestated).cloned().collect(),
},
extra: Some(GateExtra::ChallengeFresh {
lean_dir: lean.display().to_string(),
files: r.files.len(),
stale,
unrestated,
}),
}
}
fn skipped_gate(name: &str, reason: &str) -> GateResult {
GateResult {
name: name.into(),
passed: false,
skipped: true,
verdict: Verdict::from_gate(false, true),
duration_ms: 0,
detail: GateDetail::Skipped {
reason: reason.into(),
},
extra: None,
}
}
fn apply_suppressions(findings: &mut [LintFinding], config: &LintConfig) {
for f in findings.iter_mut() {
if config.suppressed_rules.iter().any(|r| r == &f.rule_id) {
f.suppressed = true;
f.suppression_reason = Some("Suppressed by --suppress-rule".into());
}
if let Some(ref stem) = f.contract_stem {
if config.suppressed_findings.iter().any(|s| s == stem) {
f.suppressed = true;
f.suppression_reason = Some("Suppressed by --suppress".into());
}
}
if config.suppressed_files.iter().any(|p| f.file.contains(p)) {
f.suppressed = true;
f.suppression_reason = Some("Suppressed by --suppress-file".into());
}
}
}
fn pv_state_dir(contract_dir: &Path) -> std::path::PathBuf {
contract_dir.parent().unwrap_or(contract_dir).join(".pv")
}
fn mark_new_findings(findings: &mut [LintFinding], contract_dir: &Path) {
let state_dir = pv_state_dir(contract_dir);
let previous_path = state_dir.join("lint-previous.json");
let previous: HashSet<String> = std::fs::read_to_string(&previous_path)
.ok()
.and_then(|s| serde_json::from_str(&s).ok())
.unwrap_or_default();
let mut current = HashSet::new();
for f in findings.iter_mut() {
let fp = f.fingerprint();
if !previous.contains(&fp) {
f.is_new = true;
}
current.insert(fp);
}
if let Err(e) = std::fs::create_dir_all(&state_dir) {
eprintln!("pv lint: cannot create {}: {e}", state_dir.display());
return;
}
if let Ok(json) = serde_json::to_string(¤t) {
let _ = std::fs::write(&previous_path, json);
}
}
fn apply_severity_overrides(findings: &mut [LintFinding], config: &LintConfig) {
for f in findings.iter_mut() {
if let Some(&sev) = config.severity_overrides.get(&f.rule_id) {
f.severity = sev;
}
}
if config.strict {
for f in findings.iter_mut() {
if f.severity == RuleSeverity::Warning {
f.severity = RuleSeverity::Error;
}
}
}
}
#[cfg(test)]
#[path = "mod_tests.rs"]
mod tests;