open-standards/joule-code/joule-code-rs/crates/joule-code-reviewer/src/cascade.rs
Transaction Science 0ae73b17d3 joule-code-reviewer: review-skill amortization — the model's joules, paid once
The fix fleet proved the moat on fixes: a fix that ships through the gate at
the model tier is induced into a deterministic skill, and every recurrence
applies it model-free. This puts the same moat on REVIEW.

When the semantic (model) tier REJECTS an op and names the generalizable
cause, the cascade induces a ReviewSkill — a learned defect marker. It becomes
a new L0 "learned" tier, the cheapest of all: a known-marker match is an
instant model-free rejection. A recurring defect that first cost a model
decode (~5 J) is caught on recurrence for ~10 µJ — joules paid once, behind a
gate, never re-paid.

The asymmetry that keeps it honest: ONLY REJECTIONS AMORTIZE, NEVER APPROVALS.
A wrong learned rejection is a false request-for-changes an author overrides;
it cannot wave bad code through. A cached approval would be the silent-failure
lane the whole edifice closes, so every approval is paid fresh. The model
teaches the cheap tier to say no; never to say yes.

ReviewSkill/ReviewSkills are plain auditable JSON (.joule/review-skills.json),
deduped on (marker, stage). The semantic tier returns SemanticOutcome
{ reviews, induced } — inductions accompany a rejection only. Reviewer::reject
signs a learned-tier rejection (self-review still refused). Per-tier Estimator
energy: learned 10 µJ, structural 50 µJ, semantic 5 J. 9 tests; clippy clean.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-11 11:20:50 -04:00

375 lines
15 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 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<ReviewSkill>,
}
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<SignedReview>,
/// Reject-rules the model identified as generalizable (empty on approval).
pub induced: Vec<ReviewSkill>,
}
/// 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<SemanticOutcome, ReviewerError>;
}
/// The outcome of cascading one op through the review ladder.
pub struct CascadeReview {
/// The signed reviews to record.
pub reviews: Vec<SignedReview>,
/// 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<ReviewSkill>,
}
/// A cheapest-sufficient, learning review ladder.
pub struct ReviewCascade {
structural: Reviewer,
semantic: Option<Box<dyn SemanticReviewer>>,
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<dyn SemanticReviewer>) -> 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<CascadeReview, ReviewerError> {
// 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<String> =
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<SemanticOutcome, ReviewerError> {
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);
}
}