vyre-conform 0.1.0

Conformance suite for vyre backends — proves byte-identical output to CPU reference
Documentation
use super::{ConformanceLevel, CoverageMetrics, OpOutcome, OpResult, TrackReport};
use crate::pipeline::CertificateTrack;
use crate::spec::types::OpSpec;

pub(super) fn optional_track(
    routed_ops: &[(CertificateTrack, &OpSpec, OpResult)],
    target: CertificateTrack,
    parity_level: ConformanceLevel,
    algebraic_level: ConformanceLevel,
) -> Option<TrackReport> {
    let track = build_track(routed_ops, target, parity_level, algebraic_level);
    (!track.ops.is_empty()).then_some(track)
}

pub(super) fn build_track(
    routed_ops: &[(CertificateTrack, &OpSpec, OpResult)],
    target: CertificateTrack,
    parity_level: ConformanceLevel,
    algebraic_level: ConformanceLevel,
) -> TrackReport {
    let specs: Vec<_> = routed_ops
        .iter()
        .filter_map(|(track, spec, _)| (*track == target).then_some(*spec))
        .collect();
    let ops: Vec<_> = routed_ops
        .iter()
        .filter_map(|(track, _, op_result)| (*track == target).then_some(op_result.clone()))
        .collect();
    let supported: Vec<_> = ops
        .iter()
        .zip(specs.iter())
        .filter(|(op, _)| !matches!(op.outcome, OpOutcome::Unsupported | OpOutcome::Pending))
        .collect();
    let all_parity = !supported.is_empty() && supported.iter().all(|(op, _)| op.parity_passed);
    let all_laws = supported
        .iter()
        .all(|(op, spec)| op.laws_failed.is_empty() && op.laws_verified.len() == spec.laws.len());
    let level = if supported.is_empty() {
        None
    } else if all_parity && all_laws {
        Some(algebraic_level)
    } else if all_parity {
        Some(parity_level)
    } else {
        None
    };

    TrackReport {
        level,
        coverage: coverage_for_refs(&specs, &ops),
        unsupported_ops: unsupported_ops(&ops),
        ops,
    }
}

pub(super) fn coverage_for(specs: &[OpSpec], ops: &[OpResult]) -> CoverageMetrics {
    let spec_refs: Vec<_> = specs.iter().collect();
    coverage_for_refs(&spec_refs, ops)
}

pub(super) fn coverage_for_refs(specs: &[&OpSpec], ops: &[OpResult]) -> CoverageMetrics {
    let laws_total = specs
        .iter()
        .zip(ops)
        .filter(|(_, op)| !matches!(op.outcome, OpOutcome::Unsupported | OpOutcome::Pending))
        .map(|(spec, _)| spec.laws.len() as u64)
        .sum();
    CoverageMetrics {
        ops_total: ops.len() as u64,
        ops_parity_passed: ops
            .iter()
            .filter(|op| op.outcome == OpOutcome::Passed)
            .count() as u64,
        ops_unsupported: ops
            .iter()
            .filter(|op| op.outcome == OpOutcome::Unsupported)
            .count() as u64,
        ops_laws_passed: ops
            .iter()
            .filter(|op| {
                !matches!(op.outcome, OpOutcome::Unsupported | OpOutcome::Pending)
                    && op.laws_failed.is_empty()
            })
            .count() as u64,
        laws_total,
        laws_passed: ops.iter().map(|op| op.laws_verified.len() as u64).sum(),
        equivalence_classes_total: specs
            .iter()
            .map(|spec| spec.equivalence_classes.len() as u64)
            .sum(),
        boundary_values_total: specs
            .iter()
            .map(|spec| spec.boundary_values.len() as u64)
            .sum(),
        cases_tested: ops.iter().map(|op| op.cases_tested).sum(),
    }
}

pub(super) fn unsupported_ops(ops: &[OpResult]) -> Vec<String> {
    ops.iter()
        .filter(|op| op.outcome == OpOutcome::Unsupported)
        .map(|op| op.id.clone())
        .collect()
}

pub(super) fn build_certificate_hash(
    backend_id: &str,
    specs: &[OpSpec],
    registry_hash: &[u8; 32],
) -> [u8; 32] {
    let mut hasher = blake3::Hasher::new();
    hasher.update(b"vyre-conform-certificate-v1");
    hasher.update(backend_id.as_bytes());
    hasher.update(registry_hash);
    for spec in specs {
        hasher.update(spec.id.as_bytes());
        hasher.update(&spec.version.to_le_bytes());
        hasher.update(&spec.signature.min_input_bytes().to_le_bytes());
    }
    *hasher.finalize().as_bytes()
}