vyre-conform 0.1.0

Conformance suite for vyre backends — proves byte-identical output to CPU reference
Documentation
use crate::spec::law::canonical_law_id;
use crate::spec::types::SpecSource;
use crate::OpSpec;

use super::error::CoverageError;

pub(super) fn check_rows(spec: &OpSpec, errors: &mut Vec<CoverageError>) {
    let output_min = spec.signature.output.min_bytes();
    let output_expected = if output_min > 0 {
        Some(output_min)
    } else {
        spec.expected_output_bytes
    };
    for (row_index, row) in spec.spec_table.iter().enumerate() {
        if row.rationale.trim().is_empty() {
            errors.push(CoverageError::InvalidSpecRow {
                op_id: spec.id.to_string(),
                row: row_index,
                message: "empty rationale. Fix: explain why this row is load-bearing.".to_string(),
            });
        }
        if row.inputs.len() != spec.signature.inputs.len() {
            errors.push(CoverageError::InvalidSpecRow {
                op_id: spec.id.to_string(),
                row: row_index,
                message: format!(
                    "input arity {} does not match signature arity {}. Fix: provide one input byte slice per declared input.",
                    row.inputs.len(),
                    spec.signature.inputs.len()
                ),
            });
        } else {
            for (input_index, (input, ty)) in row
                .inputs
                .iter()
                .zip(spec.signature.inputs.iter())
                .enumerate()
            {
                let expected = ty.min_bytes();
                if expected > 0 && input.len() != expected {
                    errors.push(CoverageError::InvalidSpecRow {
                        op_id: spec.id.to_string(),
                        row: row_index,
                        message: format!(
                            "input {input_index} has {} bytes but signature requires {expected}. Fix: encode exactly one `{ty:?}` value.",
                            input.len()
                        ),
                    });
                }
            }
        }
        match output_expected {
            Some(expected) if row.expected.len() != expected => {
                let source = if output_min > 0 {
                    "signature output"
                } else {
                    "expected_output_bytes"
                };
                errors.push(CoverageError::InvalidSpecRow {
                    op_id: spec.id.to_string(),
                    row: row_index,
                    message: format!(
                        "expected output has {} bytes but {source} requires {expected}. Fix: encode the full expected output.",
                        row.expected.len()
                    ),
                });
            }
            None => {
                errors.push(CoverageError::InvalidSpecRow {
                    op_id: spec.id.to_string(),
                    row: row_index,
                    message: "variable-output op must declare expected_output_bytes. Fix: set expected_output_bytes on the OpSpec.".to_string(),
                });
            }
            _ => {}
        }
        if let Err(message) = row.source.validate() {
            errors.push(CoverageError::InvalidSource {
                op_id: spec.id.to_string(),
                message: message.to_string(),
            });
        }
        if let SpecSource::DerivedFromLaw(id) = row.source {
            let declared = spec
                .laws
                .iter()
                .any(|law| canonical_law_id(law) == id || law.name() == id);
            if !declared {
                errors.push(CoverageError::InvalidSource {
                    op_id: spec.id.to_string(),
                    message: format!(
                        "row references undeclared law `{id}`. Fix: use a law id from this op's declared laws."
                    ),
                });
            }
        }
    }
}