use serde::{Deserialize, Serialize};
use tatara_lisp::DeriveTataraDomain;
use crate::perf::{earned_tier, ProofTier, Technique};
use crate::SpecError;
#[derive(DeriveTataraDomain, Serialize, Deserialize, Debug, Clone)]
#[tatara(keyword = "defeager-class")]
pub struct EagerClassRule {
pub name: String,
pub shape: String,
pub technique: Technique,
#[serde(default)]
pub notes: String,
}
impl EagerClassRule {
#[must_use]
pub fn earned_tier(&self) -> ProofTier {
earned_tier(self.technique)
}
#[must_use]
pub fn is_byte_patchable(&self) -> bool {
self.earned_tier() == ProofTier::ByteSufficient
}
pub fn validate(&self) -> Result<(), SpecError> {
if self.is_byte_patchable() {
Ok(())
} else {
Err(SpecError::Interp {
phase: "eager_class::validate".to_string(),
message: format!(
"eager-class rule `{}` REFUSED: technique {:?} earns {:?}, not ByteSufficient — a \
force-order-shaped knob can change eval bytes, so it is not live-patchable until \
corpus-gated",
self.name,
self.technique,
self.earned_tier()
),
})
}
}
}
const CANONICAL_EAGER_CLASS_LISP: &str = include_str!("../specs/eager_class.lisp");
pub fn load_canonical() -> Result<Vec<EagerClassRule>, SpecError> {
crate::loader::load_all::<EagerClassRule>(CANONICAL_EAGER_CLASS_LISP)
}
#[cfg(test)]
mod tests {
use super::{load_canonical, EagerClassRule};
use crate::perf::{ProofTier, Technique};
use crate::SpecError;
fn rule(name: &str, technique: Technique) -> EagerClassRule {
EagerClassRule {
name: name.to_string(),
shape: "test-shape".to_string(),
technique,
notes: String::new(),
}
}
#[test]
fn bytesufficient_techniques_are_patchable_and_validate() {
for t in [
Technique::ReprSwap,
Technique::DropUnobservedOrder,
Technique::SkipRedundantStore,
Technique::MemoizeIdempotentQuery,
] {
let r = rule("ok", t);
assert_eq!(r.earned_tier(), ProofTier::ByteSufficient, "{t:?} earns ByteSufficient");
assert!(r.is_byte_patchable(), "{t:?} must be byte-patchable");
assert!(r.validate().is_ok(), "{t:?} must validate");
}
}
#[test]
fn force_order_and_weaker_knobs_are_refused_at_the_border() {
for t in [
Technique::ForceOrderChange, Technique::HoistInvariant, Technique::ResolutionChange, ] {
let r = rule("bad", t);
assert!(!r.is_byte_patchable(), "{t:?} must NOT be byte-patchable");
let err = r.validate().expect_err("a non-ByteSufficient eager-class must be REFUSED");
assert!(matches!(err, SpecError::Interp { .. }), "typed refusal, got {err:?}");
}
}
#[test]
fn canonical_specs_parse_and_every_authored_rule_is_byte_safe() {
let rules = load_canonical().expect("eager_class canonical specs must compile");
assert!(!rules.is_empty(), "at least one authored eager-class rule");
for r in &rules {
r.validate()
.unwrap_or_else(|e| panic!("authored eager-class `{}` is not byte-safe: {e:?}", r.name));
}
}
}