vyre-conform 0.1.0

Conformance suite for vyre backends — proves byte-identical output to CPU reference
Documentation
//! Auto-collected enforcer registry and compatibility orchestration.
//!
//! At build time `vyre-build-scan` walks `src/enforce/enforcers/` and emits
//! `enforcers_registry.rs` into `$OUT_DIR`. This module includes that generated
//! file and wraps it with the `EnforcementReport` data model and the
//! `enforce_all` top-level orchestration that CI calls.

use std::path::PathBuf;

use crate::enforce::{finding_result, EnforceCtx, Finding, RegisteredEnforcer};

include!(concat!(env!("OUT_DIR"), "/enforcers_registry.rs"));

/// Status for one enforcement step.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LayerStatus {
    /// Enforcement completed without findings.
    Pass,
    /// Enforcement found a correctness or stability failure.
    Fail,
}

/// One actionable layer finding.
#[derive(Clone)]
pub struct LayerFinding {
    /// Layer identifier.
    pub layer: &'static str,
    /// Actionable message.
    pub message: String,
    /// Typed source error for callers that downcast.
    pub source: Option<std::sync::Arc<dyn std::error::Error + Send + Sync>>,
}

impl PartialEq for LayerFinding {
    fn eq(&self, other: &Self) -> bool {
        self.layer == other.layer
            && self.message == other.message
            && self.source.as_ref().map(std::string::ToString::to_string)
                == other.source.as_ref().map(std::string::ToString::to_string)
    }
}

impl core::fmt::Debug for LayerFinding {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("LayerFinding")
            .field("layer", &self.layer)
            .field("message", &self.message)
            .field("source", &self.source.as_ref().map(|err| err.to_string()))
            .finish()
    }
}

/// Report for one layer.
#[derive(Debug, Clone, PartialEq)]
pub struct LayerCheck {
    /// Layer identifier.
    pub layer: &'static str,
    /// Pass/fail status.
    pub status: LayerStatus,
    /// Findings emitted by the layer.
    pub findings: Vec<LayerFinding>,
}

/// Full enforcement report.
#[derive(Debug, Clone, PartialEq)]
pub struct EnforcementReport {
    /// Ordered layer results.
    pub checks: Vec<LayerCheck>,
}

impl EnforcementReport {
    /// True when every check passed.
    ///
    /// A success means that every registered enforcer returned `Ok(())` and
    /// no layer reported a finding. This is the binary verdict that `certify()`
    /// relies on: there is no "mostly pass" state.
    #[inline]
    pub fn is_success(&self) -> bool {
        self.checks
            .iter()
            .all(|check| check.status == LayerStatus::Pass)
    }

    /// Return every hard-failure finding.
    ///
    /// This is a convenience for CI scripts that want to print only the
    /// actionable failures without the noise of passing layers.
    #[inline]
    pub fn failures(&self) -> Vec<&LayerFinding> {
        self.checks
            .iter()
            .filter(|check| check.status == LayerStatus::Fail)
            .flat_map(|check| check.findings.iter())
            .collect()
    }
}

/// All auto-registered enforcers.
///
/// The slice is generated at build time by `vyre-build-scan`. Every file
/// in `src/enforce/enforcers/` that exports `pub const REGISTERED` becomes
/// an entry in this slice. Adding a new gate is a zero-registry-edit
/// operation: create the file, export the constant, and rebuild.
#[inline]
pub fn registered() -> &'static [&'static dyn RegisteredEnforcer] {
    ALL_ENFORCERS
}

/// Run every registered enforcer and return structured findings.
///
/// The GPU-mandatory enforcer runs first as a fast-fail gate: if the
/// workspace contains patterns that make GPU optional, the function returns
/// immediately without running the heavier conformance checks.
#[inline]
pub fn enforce(ctx: &EnforceCtx<'_>) -> Vec<Finding> {
    let gpu_span = tracing::info_span!(
        "vyre_conform.gpu_mandatory",
        gate = "enforcer_gpu_mandatory",
        findings = tracing::field::Empty,
        verdict = tracing::field::Empty
    );
    let _entered = gpu_span.enter();
    let gpu_findings =
        crate::enforce::enforcers::enforcer_gpu_mandatory::REGISTERED.run_registered(ctx);
    gpu_span.record("findings", gpu_findings.len());
    if !gpu_findings.is_empty() {
        gpu_span.record("verdict", "fail");
        tracing::warn!(
            gate = "enforcer_gpu_mandatory",
            findings = gpu_findings.len(),
            "Fix: remove Category C CPU fallbacks before running downstream enforcement."
        );
        return gpu_findings;
    }
    gpu_span.record("verdict", "pass");
    drop(_entered);
    registered()
        .iter()
        .filter(|enforcer| enforcer.id() != "enforcer_gpu_mandatory")
        .flat_map(|enforcer| enforcer.run_registered(ctx))
        .collect()
}

/// Run all layers and return a compatibility report.
///
/// This is the top-level entry point used by `certify()`. It constructs
/// an `EnforceCtx` from the current registry and workspace root, runs the
/// GPU-mandatory gate first, then iterates every registered enforcer and
/// appends a `LayerCheck` to the report.
///
/// # Examples
///
/// ```no_run
/// use vyre_conform::enforce::op_registry::enforce_all;
///
/// let report = enforce_all();
/// if report.is_success() {
///     println!("All conformance layers passed.");
/// } else {
///     for failure in report.failures() {
///         eprintln!("{}: {}", failure.layer, failure.message);
///     }
/// }
/// ```
#[must_use]
#[inline]
pub fn enforce_all() -> EnforcementReport {
    let span = tracing::info_span!("vyre_conform.enforce_all", verdict = tracing::field::Empty);
    let _entered = span.enter();
    let specs = crate::spec::op_registry::all_specs();
    let workspace = crate::enforce::workspace_root();
    let ctx = EnforceCtx::new(&specs, None, &workspace);
    let gpu_mandatory = &crate::enforce::enforcers::enforcer_gpu_mandatory::REGISTERED;
    let gpu_span = tracing::info_span!(
        "vyre_conform.gpu_mandatory",
        gate = gpu_mandatory.id(),
        findings = tracing::field::Empty,
        verdict = tracing::field::Empty
    );
    let _gpu_entered = gpu_span.enter();
    let gpu_findings = gpu_mandatory.run_registered(&ctx);
    gpu_span.record("findings", gpu_findings.len());
    if !gpu_findings.is_empty() {
        gpu_span.record("verdict", "fail");
        tracing::warn!(
            gate = gpu_mandatory.id(),
            findings = gpu_findings.len(),
            "Fix: remove Category C CPU fallbacks before issuing a certificate."
        );
        let report = EnforcementReport {
            checks: vec![fail(
                gpu_mandatory.id(),
                gpu_findings
                    .into_iter()
                    .map(|finding| finding.message())
                    .collect(),
            )],
        };
        span.record("verdict", "fail");
        return report;
    }
    gpu_span.record("verdict", "pass");
    drop(_gpu_entered);
    let mut checks = vec![pass(gpu_mandatory.id())];
    checks.extend(
        registered()
            .iter()
            .filter(|enforcer| enforcer.id() != "enforcer_gpu_mandatory")
            .map(|enforcer| {
                let findings = enforcer.run_registered(&ctx);
                if findings.is_empty() {
                    pass(enforcer.id())
                } else {
                    fail(
                        enforcer.id(),
                        findings
                            .into_iter()
                            .map(|finding| finding.message())
                            .collect(),
                    )
                }
            }),
    );
    let report = EnforcementReport { checks };
    span.record("verdict", if report.is_success() { "pass" } else { "fail" });
    report
}

#[inline]
pub(crate) fn pass(layer: &'static str) -> LayerCheck {
    LayerCheck {
        layer,
        status: LayerStatus::Pass,
        findings: Vec::new(),
    }
}

#[inline]
pub(crate) fn fail(layer: &'static str, messages: Vec<String>) -> LayerCheck {
    LayerCheck {
        layer,
        status: LayerStatus::Fail,
        findings: messages
            .into_iter()
            .map(|message| LayerFinding {
                layer,
                message,
                source: None,
            })
            .collect(),
    }
}

#[inline]
pub(crate) fn push_per_op_check(
    checks: &mut Vec<LayerCheck>,
    layer: &'static str,
    specs: &[crate::OpSpec],
    mut run: impl FnMut(&crate::OpSpec) -> Vec<String>,
) {
    let mut findings = Vec::new();
    for spec in specs {
        let span = tracing::info_span!(
            "vyre_conform.layer",
            layer_id = layer,
            op_id = spec.id,
            verdict = tracing::field::Empty
        );
        let _entered = span.enter();
        let op_findings = run(spec);
        span.record(
            "verdict",
            if op_findings.is_empty() {
                "pass"
            } else {
                "fail"
            },
        );
        findings.extend(op_findings);
    }
    checks.push(if findings.is_empty() {
        pass(layer)
    } else {
        fail(layer, findings)
    });
}

#[inline]
pub(crate) fn replace_layer_check(
    checks: &mut [LayerCheck],
    layer: &'static str,
    messages: Vec<String>,
) {
    if let Some(check) = checks.iter_mut().find(|check| check.layer == layer) {
        *check = if messages.is_empty() {
            pass(layer)
        } else {
            fail(layer, messages)
        };
    }
}

#[inline]
pub(crate) fn layer_findings(checks: &[LayerCheck], layer: &'static str) -> Vec<String> {
    checks
        .iter()
        .filter(|check| check.layer == layer)
        .flat_map(|check| check.findings.iter().map(|finding| finding.message.clone()))
        .collect()
}

#[inline]
pub(crate) fn zero_stub_roots() -> Result<Vec<PathBuf>, String> {
    match std::env::var("VYRE_CONFORM_ZERO_STUB_ROOTS") {
        Ok(explicit) => Ok(explicit
            .split(':')
            .filter(|piece| !piece.is_empty())
            .map(PathBuf::from)
            .collect()),
        Err(_) => Ok(Vec::new()),
    }
}

/// Convert messages into a boxed finding result.
///
/// This is a thin wrapper around [`finding_result`] that uses the
/// `source` string as the enforcer id. It exists so that generated
/// registry code can emit `op_registry::result(id, messages)` without
/// importing the `enforcer` module directly.
#[inline]
pub fn result(source: &'static str, messages: Vec<String>) -> Vec<Finding> {
    finding_result(source, messages)
}