use crate::spec::OpSpec;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReferenceTrustReport {
pub checked_ops: Vec<String>,
pub findings: Vec<ReferenceTrustFinding>,
}
impl ReferenceTrustReport {
#[inline]
pub fn passed(&self) -> bool {
self.findings.is_empty()
}
#[inline]
pub fn messages(&self) -> Vec<String> {
self.findings
.iter()
.map(ReferenceTrustFinding::message)
.collect()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReferenceTrustFinding {
pub op_id: String,
pub criterion: ReferenceTrustCriterion,
pub input_label: String,
pub original_input: Vec<u8>,
pub minimal_input: Vec<u8>,
pub detail: String,
}
impl ReferenceTrustFinding {
#[inline]
pub fn new(
op_id: impl Into<String>,
criterion: ReferenceTrustCriterion,
input_label: impl Into<String>,
detail: impl Into<String>,
) -> Self {
Self {
op_id: op_id.into(),
criterion,
input_label: input_label.into(),
original_input: Vec::new(),
minimal_input: Vec::new(),
detail: detail.into(),
}
}
#[inline]
pub fn with_original_input(mut self, input: &[u8]) -> Self {
self.original_input = input.to_vec();
self
}
#[inline]
pub fn with_minimal_input(mut self, input: Vec<u8>) -> Self {
self.minimal_input = input;
self
}
#[inline]
pub fn message(&self) -> String {
let input_evidence = if self.original_input.is_empty() && self.minimal_input.is_empty() {
String::new()
} else {
format!(
" original_input=0x{}, minimal_input=0x{}.",
hex_bytes(&self.original_input),
hex_bytes(&self.minimal_input)
)
};
format!(
"reference_trust({}): {:?} failed on {}:{} {}",
self.op_id, self.criterion, self.input_label, input_evidence, self.detail
)
}
}
fn hex_bytes(input: &[u8]) -> String {
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut out = String::with_capacity(input.len() * 2);
for byte in input {
out.push(HEX[(byte >> 4) as usize] as char);
out.push(HEX[(byte & 0x0F) as usize] as char);
}
out
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReferenceTrustCriterion {
Differential,
LawDerived,
Boundary,
Property,
}
#[inline]
pub fn enforce_registry(specs: &[OpSpec]) -> ReferenceTrustReport {
let mut findings = Vec::new();
let mut checked_ops = Vec::with_capacity(specs.len());
for spec in specs {
findings.extend(validate_spec(spec));
checked_ops.push(spec.id.to_string());
}
ReferenceTrustReport {
checked_ops,
findings,
}
}
#[inline]
pub fn validate_spec(spec: &OpSpec) -> Vec<ReferenceTrustFinding> {
let mut findings = external::check(spec);
findings.extend(law::check(spec));
findings.extend(boundary::check(spec));
findings.extend(property::check(spec));
findings
}
pub mod shrinker {
pub const DEFAULT_MAX_ITERATIONS: usize = 100;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ShrinkResult<T> {
pub minimal: T,
pub iterations: usize,
pub timed_out: bool,
}
#[inline]
pub fn shrink_bytes<F>(input: &[u8], fails: F) -> ShrinkResult<Vec<u8>>
where
F: Fn(&[u8]) -> bool,
{
shrink_bytes_with_limit(input, DEFAULT_MAX_ITERATIONS, fails)
}
#[inline]
pub fn shrink_bytes_with_limit<F>(
input: &[u8],
max_iterations: usize,
fails: F,
) -> ShrinkResult<Vec<u8>>
where
F: Fn(&[u8]) -> bool,
{
let mut current = input.to_vec();
let mut iterations = 0;
let mut timed_out = false;
while current.len() > 1 {
if iterations >= max_iterations {
timed_out = true;
break;
}
let midpoint = current.len() / 2;
let halves = [current[..midpoint].to_vec(), current[midpoint..].to_vec()];
let mut changed = false;
for half in halves {
iterations += 1;
if fails(&half) {
current = half;
changed = true;
break;
}
if iterations >= max_iterations {
timed_out = true;
break;
}
}
if timed_out || !changed {
break;
}
}
if !timed_out && current.len() > 1 {
let result =
delta_debug_slice_with_state(current, iterations, max_iterations, |candidate| {
fails(candidate)
});
current = result.minimal;
iterations = result.iterations;
timed_out = result.timed_out;
}
ShrinkResult {
minimal: current,
iterations,
timed_out,
}
}
#[inline]
pub fn delta_debug_slice<T, F>(items: &[T], fails: F) -> ShrinkResult<Vec<T>>
where
T: Clone,
F: Fn(&[T]) -> bool,
{
delta_debug_slice_with_limit(items, DEFAULT_MAX_ITERATIONS, fails)
}
#[inline]
pub fn delta_debug_slice_with_limit<T, F>(
items: &[T],
max_iterations: usize,
fails: F,
) -> ShrinkResult<Vec<T>>
where
T: Clone,
F: Fn(&[T]) -> bool,
{
delta_debug_slice_with_state(items.to_vec(), 0, max_iterations, fails)
}
fn delta_debug_slice_with_state<T, F>(
mut current: Vec<T>,
mut iterations: usize,
max_iterations: usize,
fails: F,
) -> ShrinkResult<Vec<T>>
where
T: Clone,
F: Fn(&[T]) -> bool,
{
let mut granularity = 2usize;
let mut timed_out = false;
while current.len() > 1 {
let chunk = current.len().div_ceil(granularity);
let mut changed = false;
let mut start = 0;
while start < current.len() {
if iterations >= max_iterations {
timed_out = true;
break;
}
let end = (start + chunk).min(current.len());
let mut candidate =
Vec::with_capacity(current.len().saturating_sub(end.saturating_sub(start)));
candidate.extend_from_slice(¤t[..start]);
candidate.extend_from_slice(¤t[end..]);
iterations += 1;
if !candidate.is_empty() && fails(&candidate) {
current = candidate;
granularity = 2;
changed = true;
break;
}
start = end;
}
if timed_out {
break;
}
if !changed {
if granularity >= current.len() {
break;
}
granularity = (granularity * 2).min(current.len());
}
}
ShrinkResult {
minimal: current,
iterations,
timed_out,
}
}
}
mod boundary {
use std::panic::{catch_unwind, AssertUnwindSafe};
use crate::generate::probes;
use crate::spec::types::DataType;
use crate::spec::OpSpec;
use crate::enforce::enforcers::reference_trust::{
shrinker, ReferenceTrustCriterion, ReferenceTrustFinding,
};
pub(super) fn check(spec: &OpSpec) -> Vec<ReferenceTrustFinding> {
probes::boundary_inputs(&spec.signature)
.into_iter()
.flat_map(|(label, bytes)| check_one(spec, &label, &bytes))
.collect()
}
fn check_one(spec: &OpSpec, label: &str, input: &[u8]) -> Vec<ReferenceTrustFinding> {
let output = catch_unwind(AssertUnwindSafe(|| (spec.cpu_fn)(input)));
match output {
Ok(output) => shape_findings(spec, label, &output),
Err(_) => {
let minimal = shrinker::shrink_bytes(input, |candidate| {
catch_unwind(AssertUnwindSafe(|| (spec.cpu_fn)(candidate))).is_err()
});
vec![
ReferenceTrustFinding::new(
spec.id,
ReferenceTrustCriterion::Boundary,
label,
"cpu_fn panicked on a generated boundary input. Fix: make cpu_fn total for zero, min/max, alternating, and random boundary values.",
)
.with_original_input(input)
.with_minimal_input(minimal.minimal),
]
}
}
}
fn shape_findings(spec: &OpSpec, label: &str, output: &[u8]) -> Vec<ReferenceTrustFinding> {
output_shape_error(&spec.signature.output, output).map_or_else(Vec::new, |detail| {
vec![ReferenceTrustFinding::new(
spec.id,
ReferenceTrustCriterion::Boundary,
label,
detail,
)]
})
}
pub(super) fn output_shape_error(output_type: &DataType, output: &[u8]) -> Option<String> {
if let Some(max) = output_type.max_bytes() {
if output.len() > max {
return Some(format!(
"cpu_fn returned {} bytes for output type `{output_type}`, exceeding max {max}. Fix: cap or validate the CPU reference output width.",
output.len()
));
}
}
if let Some(element_size) = output_type.element_size() {
if element_size == 0 {
return Some(
"array output declares a zero-byte element. Fix: declare a positive element_size."
.to_string(),
);
}
if output.len() % element_size != 0 {
return Some(format!(
"cpu_fn returned {} bytes for array<{element_size}B>, not a multiple of the element size. Fix: emit whole array elements.",
output.len()
));
}
}
let min = output_type.min_bytes();
if min > 0 && output.len() != min {
return Some(format!(
"cpu_fn returned {} bytes for fixed output type `{output_type}`, expected {min}. Fix: return exactly the declared output width.",
output.len()
));
}
None
}
}
mod external {
use std::panic::{catch_unwind, AssertUnwindSafe};
use percent_encoding::{percent_decode, percent_encode, NON_ALPHANUMERIC};
use sha2::{Digest, Sha256};
use crate::enforce::enforcers::reference_trust::{
shrinker, ReferenceTrustCriterion, ReferenceTrustFinding,
};
use crate::{generate::probes, spec::OpSpec};
type ReferenceFn = fn(&[u8]) -> Vec<u8>;
pub(super) fn check(spec: &OpSpec) -> Vec<ReferenceTrustFinding> {
let Some(reference) = reference_for(spec.id) else {
return Vec::new();
};
reference_inputs(spec.id)
.into_iter()
.enumerate()
.filter_map(|(idx, input)| {
let expected = reference(&input);
let input_label = format!("external-{idx}-len-{}", input.len());
let actual = match catch_unwind(AssertUnwindSafe(|| (spec.cpu_fn)(&input))) {
Ok(actual) => actual,
Err(_) => {
let minimal = shrinker::shrink_bytes(&input, |candidate| {
catch_unwind(AssertUnwindSafe(|| (spec.cpu_fn)(candidate))).is_err()
});
return Some(
ReferenceTrustFinding::new(
spec.id,
ReferenceTrustCriterion::Differential,
input_label,
"cpu_fn panicked while comparing against an independent reference. Fix: make the CPU reference total for all generated reference inputs.",
)
.with_original_input(&input)
.with_minimal_input(minimal.minimal),
);
}
};
(actual != expected).then(|| {
let minimal = shrinker::shrink_bytes(&input, |candidate| {
let expected = reference(candidate);
match catch_unwind(AssertUnwindSafe(|| (spec.cpu_fn)(candidate))) {
Ok(actual) => actual != expected,
Err(_) => true,
}
});
ReferenceTrustFinding::new(
spec.id,
ReferenceTrustCriterion::Differential,
input_label,
format!(
"cpu_fn output differs from independent reference: expected {} bytes, got {} bytes. Fix: replace the cpu_fn with the real operation semantics.",
expected.len(),
actual.len()
),
)
.with_original_input(&input)
.with_minimal_input(minimal.minimal)
})
})
.collect()
}
fn reference_for(op_id: &str) -> Option<ReferenceFn> {
match op_id {
"primitive.hash.crc32c" => Some(crc32c),
"primitive.hash.fnv1a32" => Some(fnv1a32),
"primitive.hash.murmur3_32" => Some(murmur3_32),
"primitive.hash.sha256" | "primitive.crypto.sha256" => Some(sha256),
"primitive.hash.blake3" | "primitive.crypto.blake3" => Some(blake3_hash),
"primitive.encoding.base64" => Some(base64_decode),
"primitive.encoding.url" => Some(url_decode),
"primitive.encoding.hex" => Some(hex_decode),
_ => None,
}
}
fn reference_inputs(op_id: &str) -> Vec<Vec<u8>> {
match op_id {
"primitive.encoding.base64" => probes::byte_reference_inputs(32)
.into_iter()
.map(|input| base64_encode(&input).into_bytes())
.collect(),
"primitive.encoding.url" => probes::byte_reference_inputs(32)
.into_iter()
.map(|input| url_encode(&input).into_bytes())
.collect(),
"primitive.encoding.hex" => probes::byte_reference_inputs(32)
.into_iter()
.map(|input| hex_encode(&input).into_bytes())
.collect(),
_ => probes::byte_reference_inputs(32),
}
}
fn crc32c(input: &[u8]) -> Vec<u8> {
const TABLE: [u32; 16] = [
0x0000_0000,
0x105E_C76F,
0x20BD_8EDE,
0x30E3_49B1,
0x417B_1DBC,
0x5125_DAD3,
0x61C6_9362,
0x7198_540D,
0x82F6_3B78,
0x92A8_FC17,
0xA24B_B5A6,
0xB215_72C9,
0xC38D_26C4,
0xD3D3_E1AB,
0xE330_A81A,
0xF36E_6F75,
];
let mut crc = 0xFFFF_FFFFu32;
for byte in input {
crc = (crc >> 4) ^ TABLE[((crc ^ u32::from(*byte)) & 0x0F) as usize];
crc = (crc >> 4) ^ TABLE[((crc ^ (u32::from(*byte) >> 4)) & 0x0F) as usize];
}
(crc ^ 0xFFFF_FFFF).to_le_bytes().to_vec()
}
fn fnv1a32(input: &[u8]) -> Vec<u8> {
let mut hash = 0x811C_9DC5u32;
for byte in input {
hash ^= u32::from(*byte);
hash = hash.wrapping_mul(0x0100_0193);
}
hash.to_le_bytes().to_vec()
}
fn murmur3_32(input: &[u8]) -> Vec<u8> {
let mut h1 = 0u32;
for chunk in input.chunks_exact(4) {
let mut k1 = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
k1 = k1.wrapping_mul(0xCC9E_2D51);
k1 = k1.rotate_left(15);
k1 = k1.wrapping_mul(0x1B87_3593);
h1 ^= k1;
h1 = h1.rotate_left(13);
h1 = h1.wrapping_mul(5).wrapping_add(0xE654_6B64);
}
let tail = input.chunks_exact(4).remainder();
let mut k1 = 0u32;
for (shift, byte) in tail.iter().enumerate() {
k1 ^= u32::from(*byte) << (shift * 8);
}
if !tail.is_empty() {
k1 = k1.wrapping_mul(0xCC9E_2D51);
k1 = k1.rotate_left(15);
k1 = k1.wrapping_mul(0x1B87_3593);
h1 ^= k1;
}
h1 ^= input.len() as u32;
h1 ^= h1 >> 16;
h1 = h1.wrapping_mul(0x85EB_CA6B);
h1 ^= h1 >> 13;
h1 = h1.wrapping_mul(0xC2B2_AE35);
h1 ^= h1 >> 16;
h1.to_le_bytes().to_vec()
}
fn sha256(input: &[u8]) -> Vec<u8> {
Sha256::digest(input).to_vec()
}
fn blake3_hash(input: &[u8]) -> Vec<u8> {
blake3::hash(input).as_bytes().to_vec()
}
fn base64_decode(input: &[u8]) -> Vec<u8> {
let mut out = Vec::new();
for quartet in input.chunks(4) {
if quartet.len() < 4 {
break;
}
let a = base64_value(quartet[0]);
let b = base64_value(quartet[1]);
let c = base64_value(quartet[2]);
let d = base64_value(quartet[3]);
let packed = (u32::from(a.unwrap_or(0)) << 18)
| (u32::from(b.unwrap_or(0)) << 12)
| (u32::from(c.unwrap_or(0)) << 6)
| u32::from(d.unwrap_or(0));
if a.is_some() && b.is_some() {
out.push((packed >> 16) as u8);
}
if c.is_some() {
out.push((packed >> 8) as u8);
}
if d.is_some() {
out.push(packed as u8);
}
}
out
}
pub(super) fn base64_encode(input: &[u8]) -> String {
let alphabet = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut out = String::new();
for chunk in input.chunks(3) {
let b0 = chunk[0];
let b1 = chunk.get(1).copied().unwrap_or(0);
let b2 = chunk.get(2).copied().unwrap_or(0);
let packed = (u32::from(b0) << 16) | (u32::from(b1) << 8) | u32::from(b2);
out.push(alphabet[((packed >> 18) & 0x3F) as usize] as char);
out.push(alphabet[((packed >> 12) & 0x3F) as usize] as char);
out.push(if chunk.len() > 1 {
alphabet[((packed >> 6) & 0x3F) as usize] as char
} else {
'='
});
out.push(if chunk.len() > 2 {
alphabet[(packed & 0x3F) as usize] as char
} else {
'='
});
}
out
}
fn base64_value(value: u8) -> Option<u8> {
match value {
b'A'..=b'Z' => Some(value - b'A'),
b'a'..=b'z' => Some(value - b'a' + 26),
b'0'..=b'9' => Some(value - b'0' + 52),
b'+' => Some(62),
b'/' => Some(63),
_ => None,
}
}
fn url_decode(input: &[u8]) -> Vec<u8> {
percent_decode(input).collect()
}
pub(super) fn url_encode(input: &[u8]) -> String {
percent_encode(input, NON_ALPHANUMERIC).to_string()
}
fn hex_decode(input: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(input.len() / 2);
for pair in input.chunks_exact(2) {
if let (Some(hi), Some(lo)) = (hex_value(pair[0]), hex_value(pair[1])) {
out.push((hi << 4) | lo);
}
}
out
}
pub(super) fn hex_encode(input: &[u8]) -> String {
let mut out = String::with_capacity(input.len() * 2);
for byte in input {
out.push(hex_digit(byte >> 4));
out.push(hex_digit(byte & 0x0F));
}
out
}
fn hex_value(value: u8) -> Option<u8> {
match value {
b'0'..=b'9' => Some(value - b'0'),
b'A'..=b'F' => Some(value - b'A' + 10),
b'a'..=b'f' => Some(value - b'a' + 10),
_ => None,
}
}
fn hex_digit(value: u8) -> char {
match value {
0..=9 => (b'0' + value) as char,
10..=15 => (b'A' + value - 10) as char,
_ => '0',
}
}
}
mod law {
use std::panic::{catch_unwind, AssertUnwindSafe};
use crate::proof::algebra::verify_laws_witnessed;
use crate::spec::OpSpec;
use crate::enforce::enforcers::reference_trust::{
ReferenceTrustCriterion, ReferenceTrustFinding,
};
const LAW_WITNESSES: u64 = 128;
pub(super) fn check(spec: &OpSpec) -> Vec<ReferenceTrustFinding> {
if spec.laws.is_empty() {
return Vec::new();
}
let is_binary = spec.signature.inputs.len() == 2;
let results = catch_unwind(AssertUnwindSafe(|| {
verify_laws_witnessed(spec.id, spec.cpu_fn, &spec.laws, is_binary, LAW_WITNESSES)
}));
match results {
Ok(results) => results
.into_iter()
.filter(|result| !result.passed())
.map(|result| {
let detail = if let Some(violation) = result.violation {
format!(
"declared law `{}` was violated: {}. Fix: replace cpu_fn with the real reference semantics or remove the invalid law declaration.",
result.law_name, violation.message
)
} else {
format!(
"declared law `{}` executed zero witness cases. Fix: make the law applicable to this op signature or add an executable checker for this law.",
result.law_name
)
};
ReferenceTrustFinding::new(
spec.id,
ReferenceTrustCriterion::LawDerived,
format!("law:{}", result.law_name),
detail,
)
})
.collect(),
Err(_) => vec![ReferenceTrustFinding::new(
spec.id,
ReferenceTrustCriterion::LawDerived,
"law-derived-witnesses",
"cpu_fn panicked while exercising declared algebraic laws. Fix: make cpu_fn total over generated law witnesses.",
)],
}
}
}
mod property {
use std::panic::{catch_unwind, AssertUnwindSafe};
use crate::generate::probes;
use crate::spec::OpSpec;
use crate::enforce::enforcers::reference_trust::boundary::output_shape_error;
use crate::enforce::enforcers::reference_trust::{
external, shrinker, ReferenceTrustCriterion, ReferenceTrustFinding,
};
pub(super) fn check(spec: &OpSpec) -> Vec<ReferenceTrustFinding> {
let mut findings = Vec::new();
for (label, input) in probes::property_inputs(&spec.signature) {
findings.extend(check_deterministic_and_shaped(spec, &label, &input));
}
findings.extend(check_round_trips(spec));
findings
}
fn check_deterministic_and_shaped(
spec: &OpSpec,
label: &str,
input: &[u8],
) -> Vec<ReferenceTrustFinding> {
let first = run_cpu(spec, label, input);
let second = run_cpu(spec, label, input);
match (first, second) {
(Ok(first), Ok(second)) if first == second => {
output_shape_error(&spec.signature.output, &first).map_or_else(Vec::new, |detail| {
let minimal = shrinker::shrink_bytes(input, |candidate| {
run_cpu(spec, label, candidate)
.ok()
.and_then(|output| output_shape_error(&spec.signature.output, &output))
.is_some()
});
vec![ReferenceTrustFinding::new(
spec.id,
ReferenceTrustCriterion::Property,
label,
detail,
)
.with_original_input(input)
.with_minimal_input(minimal.minimal)]
})
}
(Ok(first), Ok(second)) => {
let minimal = shrinker::shrink_bytes(input, |candidate| {
matches!(
(run_cpu(spec, label, candidate), run_cpu(spec, label, candidate)),
(Ok(left), Ok(right)) if left != right
)
});
vec![
ReferenceTrustFinding::new(
spec.id,
ReferenceTrustCriterion::Property,
label,
format!(
"cpu_fn is nondeterministic for identical input: first output {} bytes, second output {} bytes. Fix: remove time, randomness, global state, or data races from the CPU reference.",
first.len(),
second.len()
),
)
.with_original_input(input)
.with_minimal_input(minimal.minimal),
]
}
(Err(finding), _) | (_, Err(finding)) => vec![*finding],
}
}
fn check_round_trips(spec: &OpSpec) -> Vec<ReferenceTrustFinding> {
let Some(encoder) = round_trip_encoder(spec.id) else {
return Vec::new();
};
probes::byte_reference_inputs(32)
.into_iter()
.enumerate()
.filter_map(|(idx, raw)| {
let encoded = encoder(&raw);
match run_cpu(spec, &format!("round-trip-{idx}"), &encoded) {
Ok(decoded) if decoded == raw => None,
Ok(decoded) => {
let minimal = shrinker::shrink_bytes(&raw, |candidate| {
let encoded = encoder(candidate);
matches!(run_cpu(spec, "round-trip-shrink", &encoded), Ok(decoded) if decoded != candidate)
});
Some(
ReferenceTrustFinding::new(
spec.id,
ReferenceTrustCriterion::Property,
format!("round-trip-{idx}"),
format!(
"decode(encode(x)) changed bytes: expected {} bytes, got {} bytes. Fix: implement the canonical codec semantics in cpu_fn.",
raw.len(),
decoded.len()
),
)
.with_original_input(&encoded)
.with_minimal_input(encoder(&minimal.minimal)),
)
}
Err(finding) => Some(*finding),
}
})
.collect()
}
fn round_trip_encoder(op_id: &str) -> Option<fn(&[u8]) -> Vec<u8>> {
match op_id {
"primitive.encoding.base64" => {
Some(|input| external::base64_encode(input).into_bytes())
}
"primitive.encoding.url" => Some(|input| external::url_encode(input).into_bytes()),
"primitive.encoding.hex" => Some(|input| external::hex_encode(input).into_bytes()),
_ => None,
}
}
fn run_cpu(
spec: &OpSpec,
label: &str,
input: &[u8],
) -> Result<Vec<u8>, Box<ReferenceTrustFinding>> {
catch_unwind(AssertUnwindSafe(|| (spec.cpu_fn)(input))).map_err(|_| {
let minimal = shrinker::shrink_bytes(input, |candidate| {
catch_unwind(AssertUnwindSafe(|| (spec.cpu_fn)(candidate))).is_err()
});
Box::new(
ReferenceTrustFinding::new(
spec.id,
ReferenceTrustCriterion::Property,
label,
"cpu_fn panicked while exercising deterministic property probes. Fix: make cpu_fn total over generated property inputs.",
)
.with_original_input(input)
.with_minimal_input(minimal.minimal),
)
})
}
}
pub struct ReferenceTrustEnforcer;
impl crate::enforce::EnforceGate for ReferenceTrustEnforcer {
fn id(&self) -> &'static str {
"reference_trust"
}
fn name(&self) -> &'static str {
"reference_trust"
}
fn run(&self, ctx: &crate::enforce::EnforceCtx<'_>) -> Vec<crate::enforce::Finding> {
let mut messages = Vec::new();
for spec in ctx.specs {
messages.extend(
validate_spec(spec)
.into_iter()
.map(|finding| finding.message()),
);
}
crate::enforce::finding_result(self.id(), messages)
}
}
pub const REGISTERED: ReferenceTrustEnforcer = ReferenceTrustEnforcer;