//! The **review cascade** with skill amortization: cheapest-sufficient review //! that learns, the analog of the fix cascade and its induced skills. //! //! The fix fleet measured both halves of the moat: a deterministic L1 fix is //! ~136 J, a local-model L3 fix is ~17,904 J (~132×), and a fix that ships //! through the gate at the model tier is **induced into a deterministic skill** //! so every recurrence applies it model-free — the model's joules paid once, //! behind a gate, never re-paid. Review has the same shape, and this brings it //! the same ladder *and* the same amortization. //! //! ## The ladder //! //! 1. **Learned** ([`ReviewTier::Learned`]) — known defect patterns the model //! has already taught the cheap tier. A marker match is an instant, //! near-zero-energy rejection: no structural scan, no model. //! 2. **Structural** — the deterministic reviewer's built-in checks. //! 3. **Semantic** — a model judgment, reached only when the cheaper tiers //! find no defect. //! //! ## The asymmetry that keeps it honest //! //! **Only rejections amortize. Approvals never do.** When the semantic tier //! *rejects* an op and names the generalizable cause, that becomes a learned //! skill — a cheap structural check that catches the same defect next time. //! The worst case of a wrong learned rejection is a false request-for-changes //! an author overrides; it cannot wave bad code through. An *approval*, by //! contrast, is never cached: a stale approval is exactly the silent-failure //! lane the whole edifice exists to close, so every approval is paid fresh — //! at the model tier, or as the shallow structural floor. The model teaches //! the cheap tier to say *no*; it never teaches it to say *yes*. use joule_code_cargo::SignedOp; use joule_code_review::{ReviewStage, ReviewVerdict, SignedReview}; use joule_code_spec::SignedSpec; use serde::{Deserialize, Serialize}; use crate::{Reviewer, ReviewerError}; /// Which tier produced a review outcome. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ReviewTier { /// A learned defect pattern (induced from a prior model rejection). The /// cheapest tier — a known-marker match. Learned, /// The deterministic, model-free structural reviewer. Structural, /// A model judgment of whether the code means what the clause requires. Semantic, } impl ReviewTier { /// A stable label. pub fn label(self) -> &'static str { match self { ReviewTier::Learned => "learned", ReviewTier::Structural => "structural", ReviewTier::Semantic => "semantic", } } /// An order-of-magnitude energy estimate, µJ, honest `Estimator` class — /// a marker, not a measurement (this crate holds no meter). The ratios /// mirror the measured fix ladder: a learned-marker match and a structural /// scan are file-I/O class; a semantic judgment is a model decode. pub fn estimate_uj(self) -> u64 { match self { ReviewTier::Learned => 10, // a known-marker match ReviewTier::Structural => 50, // a byte scan ReviewTier::Semantic => 5_000_000, // a model decode } } } /// A learned review skill: a defect marker the model taught the cheap tier to /// catch, with the finding to report and the stage it belongs to. Plain, /// auditable data — read it, diff it, delete it. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ReviewSkill { /// A literal substring whose presence in an op's written bytes signals the /// defect (e.g. `eval(`). The skill fires on any edit containing it. pub marker: String, /// The stage the rejection belongs to. pub stage: ReviewStage, /// The finding to report (the model's reason, generalized). pub finding: String, } impl ReviewSkill { /// Whether this skill fires on `op` — any edit's bytes contain the marker. pub fn matches(&self, op: &SignedOp) -> bool { op.op .edits .iter() .any(|e| String::from_utf8_lossy(&e.replacement).contains(&self.marker)) } } /// The learned-skill set — persistable as plain JSON (`.joule/review-skills.json`). #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct ReviewSkills { /// The induced reject-rules, applied newest-last. pub skills: Vec, } impl ReviewSkills { /// The skills that fire on `op`. pub fn matching(&self, op: &SignedOp) -> Vec<&ReviewSkill> { self.skills.iter().filter(|s| s.matches(op)).collect() } /// Add a skill, de-duplicating on (marker, stage). Returns whether it was /// new (a recurrence of a known marker teaches nothing further). pub fn learn(&mut self, skill: ReviewSkill) -> bool { if self.skills.iter().any(|s| s.marker == skill.marker && s.stage == skill.stage) { return false; } self.skills.push(skill); true } } /// What a semantic reviewer returns: its signed reviews, plus any reject-rules /// it identified for amortization. Inductions are offered **only** with a /// rejection — there is no inducible signature of an approval. pub struct SemanticOutcome { /// The signed two-stage reviews. pub reviews: Vec, /// Reject-rules the model identified as generalizable (empty on approval). pub induced: Vec, } /// A semantic (model) reviewer tier: a distinct principal that judges an op /// with a model, signing reviews with its own key and naming the /// generalizable cause of any rejection so the cheap tier can learn it. pub trait SemanticReviewer { /// Judge `op` against `spec` for the `claimed` clauses. fn review( &self, op: &SignedOp, spec: &SignedSpec, claimed: &[String], ) -> Result; } /// The outcome of cascading one op through the review ladder. pub struct CascadeReview { /// The signed reviews to record. pub reviews: Vec, /// The tier that produced them. pub tier: ReviewTier, /// Whether the cheap tiers escalated to the semantic tier. pub escalated: bool, /// Estimated joules across the tiers consulted (Estimator class). pub estimated_uj: u64, /// Reject-rules the semantic tier produced this run — the caller persists /// them with [`ReviewCascade::learn`] so recurrences are caught cheaply. pub induced: Vec, } /// A cheapest-sufficient, learning review ladder. pub struct ReviewCascade { structural: Reviewer, semantic: Option>, skills: ReviewSkills, } impl ReviewCascade { /// A cascade with the structural floor and no learned skills yet. pub fn new(structural: Reviewer) -> Self { Self { structural, semantic: None, skills: ReviewSkills::default() } } /// Register the semantic tier — the only source of new learned skills. pub fn with_semantic(mut self, semantic: Box) -> Self { self.semantic = Some(semantic); self } /// Seed the learned skills (e.g. loaded from `.joule/review-skills.json`). pub fn with_skills(mut self, skills: ReviewSkills) -> Self { self.skills = skills; self } /// The current learned skills, for persistence. pub fn skills(&self) -> &ReviewSkills { &self.skills } /// Fold an induced skill into the set (de-duplicated). Returns whether it /// was new. pub fn learn(&mut self, skill: ReviewSkill) -> bool { self.skills.learn(skill) } /// Review `op` cheapest-first: learned skills, then structural, then — /// only if both find no defect — the semantic tier. A learned or /// structural rejection never consults the model. pub fn review( &self, op: &SignedOp, spec: &SignedSpec, claimed: &[String], ) -> Result { // L0 — learned defects: an instant, model-free rejection. let hits = self.skills.matching(op); if !hits.is_empty() { let mut reviews = Vec::new(); for stage in [ReviewStage::SpecCompliance, ReviewStage::Quality] { let findings: Vec = hits.iter().filter(|s| s.stage == stage).map(|s| s.finding.clone()).collect(); if !findings.is_empty() { reviews.push(self.structural.reject(op, spec, stage, findings)?); } } return Ok(CascadeReview { reviews, tier: ReviewTier::Learned, escalated: false, estimated_uj: ReviewTier::Learned.estimate_uj(), induced: vec![], }); } // L1 — structural. let structural = self.structural.review(op, spec, claimed)?; let rejected = structural.iter().any(|r| r.review.verdict == ReviewVerdict::RequestChanges); if rejected || self.semantic.is_none() { return Ok(CascadeReview { reviews: structural, tier: ReviewTier::Structural, escalated: false, estimated_uj: ReviewTier::Structural.estimate_uj(), induced: vec![], }); } // L3 — semantic: the real approval (or a rejection that teaches). let outcome = self.semantic.as_ref().unwrap().review(op, spec, claimed)?; Ok(CascadeReview { reviews: outcome.reviews, tier: ReviewTier::Semantic, escalated: true, estimated_uj: ReviewTier::Structural.estimate_uj() + ReviewTier::Semantic.estimate_uj(), induced: outcome.induced, }) } } #[cfg(test)] mod tests { use super::*; use ed25519_dalek::SigningKey; use joule_code_cargo::{Anchor, CodingOp, Edit}; use joule_code_review::{approval, Review, ReviewStage, ALG_ED25519 as RALG}; use joule_code_schema::{ContentId, Tier}; use joule_code_spec::{Clause, ClauseKind, Spec, ALG_ED25519 as SALG}; fn sk(s: u8) -> SigningKey { SigningKey::from_bytes(&[s; 32]) } fn spec(author: &SigningKey) -> SignedSpec { Spec { alg: SALG, intent: "x".into(), clauses: vec![Clause { id: "C1".into(), text: "persist".into(), kind: ClauseKind::Requirement }], parent: None, author: author.verifying_key().to_bytes(), } .sign(author) .unwrap() } fn op(author: &SigningKey, body: &str) -> SignedOp { CodingOp { alg: 0x01, prior: ContentId::of_bytes(body.as_bytes()), edits: vec![Edit { path: "src/s.rs".into(), anchor: Anchor::ByteRange { start: 0, end: 0 }, replacement: body.as_bytes().to_vec() }], author: author.verifying_key().to_bytes(), intent: "persist".into(), tier: Tier::L3, } .sign(author) .unwrap() } /// A test-double semantic reviewer: rejects ops containing `eval(` and /// induces a skill for it; approves everything else. (Stands in for a /// model judgment, deterministically, in-test.) struct EvalRejectingSemantic(SigningKey); impl SemanticReviewer for EvalRejectingSemantic { fn review(&self, op: &SignedOp, spec: &SignedSpec, _claimed: &[String]) -> Result { let op_id = op.op.op_id()?; let spec_id = spec.verify()?; let bad = op.op.edits.iter().any(|e| String::from_utf8_lossy(&e.replacement).contains("eval(")); if bad { let r = Review { alg: RALG, op_id, stage: ReviewStage::Quality, spec_id: None, verdict: ReviewVerdict::RequestChanges, findings: vec!["uses eval() on untrusted input".into()], round: 1, reviewer: self.0.verifying_key().to_bytes(), }.sign(&self.0).unwrap(); Ok(SemanticOutcome { reviews: vec![r], induced: vec![ReviewSkill { marker: "eval(".into(), stage: ReviewStage::Quality, finding: "uses eval() on untrusted input (learned)".into() }], }) } else { let reviews = [ReviewStage::SpecCompliance, ReviewStage::Quality].into_iter().map(|stage| { Review { alg: RALG, op_id: op_id.clone(), stage, spec_id: if stage == ReviewStage::SpecCompliance { Some(spec_id.clone()) } else { None }, verdict: ReviewVerdict::Approve, findings: vec![], round: 1, reviewer: self.0.verifying_key().to_bytes(), }.sign(&self.0).unwrap() }).collect(); Ok(SemanticOutcome { reviews, induced: vec![] }) } } } #[test] fn the_model_teaches_the_cheap_tier_a_defect_then_recurrences_are_caught_for_nanojoules() { let author = sk(1); let mut cascade = ReviewCascade::new(Reviewer::new(sk(2))) .with_semantic(Box::new(EvalRejectingSemantic(sk(3)))); // first encounter: structural finds nothing wrong, escalates to the // model, which rejects AND induces a skill. let first = op(&author, "fn run() { eval(input); }"); let out = cascade.review(&first, &spec(&author), &["C1".into()]).unwrap(); assert_eq!(out.tier, ReviewTier::Semantic); assert!(out.estimated_uj >= ReviewTier::Semantic.estimate_uj()); // paid the model assert_eq!(out.induced.len(), 1); assert!(!approval(&first, &out.reviews).is_approved()); // learn it (as a deployment would, persisting to review-skills.json) for s in out.induced { assert!(cascade.learn(s)); } // recurrence (a different op, same defect): caught at the LEARNED tier, // model never consulted, ~5 J → 10 µJ. let again = op(&author, "fn other() { eval(x); }"); let out2 = cascade.review(&again, &spec(&author), &["C1".into()]).unwrap(); assert_eq!(out2.tier, ReviewTier::Learned); assert!(!out2.escalated); assert_eq!(out2.estimated_uj, ReviewTier::Learned.estimate_uj()); assert!(out2.estimated_uj * 100 < ReviewTier::Semantic.estimate_uj()); // orders cheaper assert!(!approval(&again, &out2.reviews).is_approved()); } #[test] fn an_approval_is_never_learned() { let author = sk(1); let cascade = ReviewCascade::new(Reviewer::new(sk(2))) .with_semantic(Box::new(EvalRejectingSemantic(sk(3)))); let clean = op(&author, "fn run() { write_to_disk(); }"); let out = cascade.review(&clean, &spec(&author), &["C1".into()]).unwrap(); assert_eq!(out.tier, ReviewTier::Semantic); assert!(approval(&clean, &out.reviews).is_approved()); assert!(out.induced.is_empty()); // approvals induce nothing — never cached } #[test] fn skills_persist_as_plain_json_and_dedupe() { let mut skills = ReviewSkills::default(); let s = ReviewSkill { marker: "eval(".into(), stage: ReviewStage::Quality, finding: "f".into() }; assert!(skills.learn(s.clone())); assert!(!skills.learn(s.clone())); // same marker+stage → not new let json = serde_json::to_string(&skills).unwrap(); let back: ReviewSkills = serde_json::from_str(&json).unwrap(); assert_eq!(back.skills, skills.skills); } }