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."
),
});
}
}
}
}