diff --git a/jouleclaw/jouleclaw-rs/crates/jouleclaw-cascade/src/router.rs b/jouleclaw/jouleclaw-rs/crates/jouleclaw-cascade/src/router.rs index 0e6fb96eb..cf24ce879 100644 --- a/jouleclaw/jouleclaw-rs/crates/jouleclaw-cascade/src/router.rs +++ b/jouleclaw/jouleclaw-rs/crates/jouleclaw-cascade/src/router.rs @@ -55,6 +55,16 @@ pub struct RoutingPlan { /// Joules spent producing this plan. Counted against the query /// budget. + /// + /// **Ungraded.** This is a bare `f64`, so a figure a router measured and a + /// declared constant are indistinguishable here — and because it is a + /// debit, a figure below the work actually done under-charges the budget, + /// which is the unsafe direction at a ceiling (`jouleclaw/SPEC.md` §1.3). + /// A router whose routing cost scales with its own state, or that runs a + /// model to choose a route, must report what it spent rather than a + /// constant; see `jouleclaw_routing::ROUTING_JOULES` for why the flat + /// constant is a floor. Carrying a provenance beside this field is what + /// would let a verifier tell the two apart. pub router_joules: f64, /// Human-readable explanation of WHY this ordering. Surfaces in diff --git a/jouleclaw/jouleclaw-rs/crates/jouleclaw-routing/src/lib.rs b/jouleclaw/jouleclaw-rs/crates/jouleclaw-routing/src/lib.rs index acb1a2a30..8f213ade9 100644 --- a/jouleclaw/jouleclaw-rs/crates/jouleclaw-routing/src/lib.rs +++ b/jouleclaw/jouleclaw-rs/crates/jouleclaw-routing/src/lib.rs @@ -43,7 +43,31 @@ pub use privacy::{ SensitivityLevel, TierSensitivityPolicy, PRIVACY_FILTER_JOULES, }; -/// The (negligible) joule cost charged for producing a routing plan. -/// L5 lives in the meta-cognitive control plane; its energy is client -/// CPU only and is dwarfed by every execution tier it orders. +/// The joule cost charged for producing a routing plan: an **`Estimator`-class +/// declared floor**, not a measurement. +/// +/// L5 lives in the meta-cognitive control plane, and for a table-driven or +/// heuristic router its energy really is client CPU only and dwarfed by every +/// execution tier it orders. Two things keep that from being the whole story, +/// and both matter because [`RoutingPlan::router_joules`] is **counted against +/// the query budget**, so a charge below the work done under-debits it — the +/// unsafe direction at a ceiling (`jouleclaw/SPEC.md` §1.3). +/// +/// 1. **It does not scale with the work.** [`LearnedRouter`]'s `route` tokenizes +/// the query and scans its episode memory for neighbours, which is work +/// proportional to the number of episodes; this constant is flat. Nothing +/// bounds that memory, so the gap grows silently with it. +/// 2. **A router that runs a model must not use it.** A classifier or an LLM +/// choosing a route costs orders more than this, and charging the constant +/// would report a model inference as picojoules. +/// +/// A router whose routing cost scales with its own state, or that runs a +/// model, **MUST** report what it actually spent rather than this constant. +/// Nothing enforces that today: `router_joules` is a bare `f64` and carries no +/// provenance, so a measured figure and this floor are indistinguishable in +/// the type. Giving [`RoutingPlan`] a provenance field is what would make the +/// distinction checkable instead of documented. +/// +/// [`RoutingPlan`]: jouleclaw_cascade::router::RoutingPlan +/// [`RoutingPlan::router_joules`]: jouleclaw_cascade::router::RoutingPlan::router_joules pub const ROUTING_JOULES: f64 = 8e-9;