vyre-conform 0.1.0

Conformance suite for vyre backends — proves byte-identical output to CPU reference
Documentation
//! Layer 4 - mutation gate wrapper.

use std::collections::BTreeMap;
use std::path::PathBuf;

pub use crate::verify::harnesses::mutation::{
    mutation_probe, AppliedMutation, GateReport, MutationOutcome, MutationResult,
};
pub use crate::verify::harnesses::wgsl_mutation::{
    wgsl_mutation_probe, WgslMutation, WgslMutationOutcome, WgslMutationReport,
};

/// One source/test pair accepted by the L4 mutation gate.
pub struct MutationTest<'a> {
    /// Source file to mutate.
    pub source_file: PathBuf,
    /// Exact test function name to run under mutation.
    pub test_fn_name: &'a str,
    /// Mutations selected for this test.
    pub mutations: Vec<&'a dyn AppliedMutation>,
    /// Mutation classes selected for this test. Empty means all classes.
    pub classes: Vec<crate::spec::MutationClass>,
    /// Extra cargo arguments for isolation, such as `--offline`.
    pub cargo_test_args: Vec<&'a str>,
}

/// One op/backend pair accepted by the L4 WGSL mutation gate.
pub struct WgslMutationTest<'a> {
    /// Backend used to dispatch mutated WGSL.
    pub backend: &'a dyn crate::pipeline::backend::WgslBackend,
    /// Operation whose WGSL source will be mutated.
    pub spec: &'a crate::spec::OpSpec,
    /// WGSL mutations selected for this operation.
    pub mutations: Vec<WgslMutation>,
}

/// A mutation that survived the accepted test set.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SurvivingMutation {
    /// Test that failed to kill the mutation.
    pub test_name: String,
    /// Source file that was mutated.
    pub source_file: PathBuf,
    /// Mutation id.
    pub mutation_id: String,
    /// Human-readable mutation description.
    pub description: String,
    /// Actionable hint for strengthening the test.
    pub fix: String,
}

/// Validate the mutation catalog shape used by the live L4 gate.
#[inline]
pub fn validate_catalog(
    catalog: &[crate::adversarial::mutations::catalog::Mutation],
) -> Result<(), Vec<String>> {
    if catalog.is_empty() {
        return Err(vec![
            "mutation corpus is empty — see build.rs defender_corpus generation".to_string(),
        ]);
    }

    let mut messages = Vec::new();
    let mut seen = BTreeMap::new();
    for mutation in catalog {
        let adapter =
            crate::adversarial::mutations::catalog::applied::CatalogMutation::new(mutation.clone());
        let id = adapter.id().to_string();
        if let Some(first) = seen.insert(id.clone(), format!("{mutation:?}")) {
            messages.push(format!(
                "duplicate mutation id `{id}` for `{first}` and `{mutation:?}`"
            ));
        }
    }

    let required = [
        (
            "integer_constant_bit_flip",
            catalog.iter().any(|m| {
                matches!(
                    m,
                    crate::adversarial::mutations::catalog::Mutation::IntegerConstantBitFlip { .. }
                )
            }),
        ),
        (
            "float_constant_bit_flip",
            catalog.iter().any(|m| {
                matches!(
                    m,
                    crate::adversarial::mutations::catalog::Mutation::FloatConstantBitFlip { .. }
                )
            }),
        ),
        (
            "workgroup_stride_mul_div",
            catalog.iter().any(|m| {
                matches!(
                    m,
                    crate::adversarial::mutations::catalog::Mutation::IrWorkgroupStrideMul { .. }
                )
            }) && catalog.iter().any(|m| {
                matches!(
                    m,
                    crate::adversarial::mutations::catalog::Mutation::IrWorkgroupStrideDiv { .. }
                )
            }),
        ),
        (
            "workgroup_size_mul_div_offset",
            catalog.iter().any(|m| {
                matches!(
                    m,
                    crate::adversarial::mutations::catalog::Mutation::IrWorkgroupSizeMul { .. }
                )
            }) && catalog.iter().any(|m| {
                matches!(
                    m,
                    crate::adversarial::mutations::catalog::Mutation::IrWorkgroupSizeDiv { .. }
                )
            }) && catalog.iter().any(|m| {
                matches!(
                    m,
                    crate::adversarial::mutations::catalog::Mutation::IrWorkgroupSizeOffset { .. }
                )
            }),
        ),
        (
            "atomic_acqrel_ordering",
            catalog.iter().any(|m| {
                matches!(
                    m,
                    crate::adversarial::mutations::catalog::Mutation::AtomicOrderingAcqRelWeaken
                )
            }),
        ),
        (
            "buffer_miscount_plus_minus_one",
            catalog.iter().any(|m| {
                matches!(
                    m,
                    crate::adversarial::mutations::catalog::Mutation::BufferCountShift { by: -1 }
                )
            }) && catalog.iter().any(|m| {
                matches!(
                    m,
                    crate::adversarial::mutations::catalog::Mutation::BufferCountShift { by: 1 }
                )
            }),
        ),
        (
            "le_ge_swap",
            catalog.iter().any(|m| {
                matches!(
                    m,
                    crate::adversarial::mutations::catalog::Mutation::CompareOpSwap {
                        from: crate::adversarial::mutations::catalog::BinOpKind::Le,
                        to: crate::adversarial::mutations::catalog::BinOpKind::Ge
                    }
                )
            }) && catalog.iter().any(|m| {
                matches!(
                    m,
                    crate::adversarial::mutations::catalog::Mutation::CompareOpSwap {
                        from: crate::adversarial::mutations::catalog::BinOpKind::Ge,
                        to: crate::adversarial::mutations::catalog::BinOpKind::Le
                    }
                )
            }),
        ),
        (
            "control_flow_skip",
            catalog.iter().any(|m| {
                matches!(
                    m,
                    crate::adversarial::mutations::catalog::Mutation::ControlFlowSkip
                )
            }),
        ),
    ];
    for (name, present) in required {
        if !present {
            messages.push(format!("missing required mutation operator `{name}`"));
        }
    }

    if messages.is_empty() {
        Ok(())
    } else {
        Err(messages)
    }
}

/// Run the mutation gate over an accepted test set.
#[inline]
pub fn enforce_on(test_set: &[MutationTest<'_>]) -> Result<(), Vec<SurvivingMutation>> {
    enforce_on_with_wgsl(test_set, &[])
}

/// Run the mutation gate over accepted Rust-source and WGSL-source probes.
#[inline]
pub fn enforce_on_with_wgsl(
    test_set: &[MutationTest<'_>],
    wgsl_test_set: &[WgslMutationTest<'_>],
) -> Result<(), Vec<SurvivingMutation>> {
    let mut survivors = Vec::new();

    for test in test_set {
        let report = mutation_probe(
            &test.source_file,
            test.test_fn_name,
            &test.mutations,
            &test.classes,
            &test.cargo_test_args,
        );
        survivors.extend(survivors_from_report(&report));
    }
    for test in wgsl_test_set {
        match wgsl_mutation_probe(test.backend, test.spec, &test.mutations) {
            Ok(report) => survivors.extend(survivors_from_wgsl_report(&report)),
            Err(error) => survivors.push(SurvivingMutation {
                test_name: format!("wgsl_mutation:{}", test.spec.id),
                source_file: PathBuf::from(format!("wgsl_fn:{}", test.spec.id)),
                mutation_id: "wgsl.baseline".to_string(),
                description: "WGSL baseline parity failed before mutation probing".to_string(),
                fix: error,
            }),
        }
    }

    if survivors.is_empty() {
        Ok(())
    } else {
        Err(survivors)
    }
}

/// Run the L4 gate against the generated mutation catalog.
#[inline]
pub fn enforce_catalog(
    catalog: &[crate::adversarial::mutations::catalog::Mutation],
) -> Result<(), Vec<String>> {
    validate_catalog(catalog)?;

    let applied: Vec<Box<dyn AppliedMutation>> = catalog
        .iter()
        .cloned()
        .map(crate::adversarial::mutations::catalog::applied::applied_for)
        .collect();
    let mutation_refs: Vec<&dyn AppliedMutation> = applied
        .iter()
        .map(|mutation| mutation.as_ref() as &dyn AppliedMutation)
        .collect();
    let source_file = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/mutations/catalog.rs");
    let test_set = [MutationTest {
        source_file,
        test_fn_name: "catalog_entries_are_unique",
        mutations: mutation_refs,
        classes: Vec::new(),
        cargo_test_args: vec!["--offline"],
    }];

    enforce_on(&test_set).map_err(|survivors| {
        survivors
            .into_iter()
            .map(|survivor| {
                format!(
                    "{} survived {} on {}. {}",
                    survivor.mutation_id,
                    survivor.test_name,
                    survivor.source_file.display(),
                    survivor.fix
                )
            })
            .collect()
    })
}

fn survivors_from_report(report: &GateReport) -> Vec<SurvivingMutation> {
    report
        .results
        .iter()
        .filter(|result| matches!(result.outcome, MutationOutcome::Survived))
        .map(|result| SurvivingMutation {
            test_name: report.test_name.clone(),
            source_file: report.source_file.clone(),
            mutation_id: result.mutation_id.clone(),
            description: result.description.clone(),
            fix: report
                .feedback
                .iter()
                .find(|feedback| feedback.mutation_id == result.mutation_id)
                .map_or_else(
                    || {
                        "Fix: add an assertion that distinguishes the original behavior from this mutation."
                            .to_string()
                    },
                    |feedback| format!("Fix: {}", feedback.hint),
                ),
        })
        .collect()
}

fn survivors_from_wgsl_report(report: &WgslMutationReport) -> Vec<SurvivingMutation> {
    report
        .results
        .iter()
        .filter(|result| matches!(result.outcome, WgslMutationOutcome::Survived))
        .map(|result| SurvivingMutation {
            test_name: format!("wgsl_mutation:{}", report.op_id),
            source_file: PathBuf::from(format!("wgsl_fn:{}", report.op_id)),
            mutation_id: result.mutation_id.clone(),
            description: result.description.clone(),
            fix: result.detail.clone(),
        })
        .collect()
}

/// Registry entry for `layer4_mutation_gate` enforcement.
pub struct Layer4MutationGateEnforcer;

impl crate::enforce::EnforceGate for Layer4MutationGateEnforcer {
    fn id(&self) -> &'static str {
        "layer4_mutation_gate"
    }

    fn name(&self) -> &'static str {
        "layer4_mutation_gate"
    }

    fn run(&self, _ctx: &crate::enforce::EnforceCtx<'_>) -> Vec<crate::enforce::Finding> {
        // Audit fix (conform-gate finding 5.2): previously returned
        // Ok(()) unconditionally. L4 now validates that the mutation
        // catalog is non-empty, which is the minimum precondition for
        // the mutation gate to do real work. Full mutation-probe
        // execution is driven by explicit `enforce_on_with_wgsl` calls
        // with an accepted test set — the per-enforcer ctx does not
        // carry a test harness, so surviving-mutation detection lives
        // in `enforce_catalog` and the certify() entry point.
        let messages =
            match validate_catalog(crate::adversarial::mutations::catalog::MUTATION_CATALOG) {
                Ok(()) => Vec::new(),
                Err(reasons) => reasons
                    .into_iter()
                    .map(|reason| format!("Fix: mutation catalog precondition failed: {reason}"))
                    .collect(),
            };
        crate::enforce::finding_result(self.id(), messages)
    }
}

/// Auto-registered `layer4_mutation_gate` enforcer.
pub const REGISTERED: Layer4MutationGateEnforcer = Layer4MutationGateEnforcer;