use crate::spec::graph::bfs::{encode_reached, parse_graph_input, reachable_from};
use crate::spec::types::{Convention, DataType, OpSignature, OpSpec};
pub const VYRE_OP_METADATA: vyre_spec::OpMetadata = vyre_spec::OpMetadata {
id: "graph.reachability",
layer: vyre_spec::Layer::L3,
category: vyre_spec::MetadataCategory::A,
version: 1,
description: "graph reachability",
signature: "(Bytes) -> Bytes",
strictness: "strict",
archetype_signature: "(Bytes) -> Bytes",
};
pub const GOLDEN: &[vyre_spec::GoldenSample] = &[vyre_spec::GoldenSample {
op_id: "graph.reachability",
input: &[],
expected: &[0x00, 0x00, 0x00, 0x00],
reason: "empty graph frame is rejected to the zero sentinel",
}];
pub const KAT: &[vyre_spec::KatVector] = &[
vyre_spec::KatVector {
input: &[],
expected: &[0x00, 0x00, 0x00, 0x00],
source: "empty input fails MAGIC check → parse_graph_input returns None → zero sentinel",
},
vyre_spec::KatVector {
input: b"VYR",
expected: &[0x00, 0x00, 0x00, 0x00],
source: "truncated magic (3 of 4 bytes) → None → zero sentinel",
},
vyre_spec::KatVector {
input: &[
0x56, 0x59, 0x52, 0x47, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ],
expected: &[
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
],
source: "2-node 1-edge single-source reachability: emits (0, 1, 1)",
},
vyre_spec::KatVector {
input: &[
0x56, 0x59, 0x52, 0x47, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, ],
expected: &[
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
],
source: "multi-source reachability trace from unit test `computes_multi_source_reachability_frame` at line 120",
},
];
pub const ADVERSARIAL: &[vyre_spec::AdversarialInput] = &[vyre_spec::AdversarialInput {
input: b"VYR",
reason: "truncated graph magic header exercises frame rejection",
}];
#[inline]
pub fn cpu(input: &[u8]) -> Vec<u8> {
let Some(graph) = parse_graph_input(input) else {
return vec![0; 4];
};
let mut reached = Vec::new();
for &source in &graph.sources {
let Ok(source_reached) = reachable_from(&graph, source) else {
return vec![0; 4];
};
reached.extend(source_reached);
}
encode_reached(&reached)
}
fn wgsl() -> String {
"fn vyre_op(index: u32, input_len: u32) -> u32 {
if index >= input_len { return 0u; }
return input.data[index];
}"
.to_string()
}
#[inline]
pub fn vyre_op() -> OpSpec {
let id = "graph.reachability";
OpSpec::builder(id)
.signature(OpSignature {
inputs: vec![DataType::Bytes],
output: DataType::Bytes,
})
.cpu_fn(cpu)
.wgsl_fn(wgsl)
.category(crate::Category::A {
composition_of: vec![id],
})
.laws(vec![crate::spec::law::AlgebraicLaw::Bounded {
lo: 0,
hi: u32::MAX,
}])
.strictness(crate::spec::types::Strictness::Strict)
.version(1)
.alt_wgsl_fns(vec![("category_a_handwritten", wgsl)])
.convention(Convention::default())
.workgroup_size(Some(1))
.boundary_values(vec![
crate::spec::types::BoundaryValue {
label: "empty",
inputs: vec![0],
},
crate::spec::types::BoundaryValue {
label: "single_element",
inputs: vec![1],
},
crate::spec::types::BoundaryValue {
label: "boundary",
inputs: vec![255],
},
crate::spec::types::BoundaryValue {
label: "max",
inputs: vec![u32::MAX],
},
])
.equivalence_classes(vec![
crate::spec::types::EquivalenceClass::specific("empty input", vec![0]),
crate::spec::types::EquivalenceClass::specific("typical input", vec![42]),
crate::spec::types::EquivalenceClass::specific("boundary input", vec![255]),
])
.expect("Fix: checked-in conform spec must satisfy the typestate builder")
}
#[cfg(test)]
mod tests {
use crate::spec::graph::bfs::{encode_reached, MAGIC};
use super::cpu;
#[test]
fn computes_multi_source_reachability_frame() {
let mut input = MAGIC.to_vec();
for word in [4u32, 3, 2, 8, 0, 1, 1, 2, 3, 2, 0, 3] {
input.extend_from_slice(&word.to_le_bytes());
}
assert_eq!(
cpu(&input),
encode_reached(&[(0, 1, 1), (0, 2, 2), (3, 2, 1)])
);
}
}
#[cfg(test)]
mod proptests {
#[test]
fn coverage_artifacts_are_registered() {
assert!(!super::KAT.is_empty());
assert!(!super::ADVERSARIAL.is_empty());
}
}