diff --git a/crates/busbar-kernel/src/metrics/door_cells.rs b/crates/busbar-kernel/src/metrics/door_cells.rs index 9ec979d69b..025db77996 100644 --- a/crates/busbar-kernel/src/metrics/door_cells.rs +++ b/crates/busbar-kernel/src/metrics/door_cells.rs @@ -15,8 +15,14 @@ //! when the egress is sealed; this module renders those strings as they were stated and never builds //! one. //! +//! THE MONEY GAUGE'S `model` LABEL IS STATED THE SAME WAY. A door plane's money rows are ledgered +//! under its internal lane keys (`"\u{1f}"`, and its fee lane `"\u{1f}"`); the +//! egress carries, beside its breaker labels, the `model` label each of those keys is scraped under +//! ([`DoorBreaker::models`]). Those are kept across generations ([`DoorCells::model_labels`]): the +//! ledger keeps a key's history after an apply retires the entry, so its label stays known. +//! //! A door plane whose breaker is the kernel's own lane store (the plane serving the `pools` map) -//! publishes no unit: its cells are scraped from the store. +//! publishes no unit: its cells are scraped from the store, and its money rows are keyed bare. //! //! The set is carried across config applies with the `Arc` the `App` holds, and a plane's newer //! generation replaces its older one, so a retired generation's cells are never scraped. @@ -42,12 +48,17 @@ pub struct DoorBreaker { pub pools: HashMap, /// Member → the stated `lane` label. pub lanes: HashMap, + /// Money ledger lane key → the stated `model` label its `busbar_bucket_tokens` series carry. + pub models: HashMap, } -/// Every served door plane's breaker cells, by plane. +/// Every served door plane's breaker cells, by plane, and the `model` label every door plane has +/// stated for a money ledger lane key. #[derive(Default)] pub struct DoorCells { planes: RwLock>, + /// Sticky: a newer generation adds or restates labels and never withdraws one. + models: RwLock>, } impl DoorCells { @@ -60,27 +71,41 @@ impl DoorCells { /// Publish `plane`'s current generation: the breaker its egress walk trips, with its stated /// labels. Replaces the generation published before it. An egress whose breaker is the kernel's /// own lane store (no unit of its own) withdraws the plane. + /// The `model` labels the generation states are kept past it ([`Self::model_labels`]). pub fn publish(&self, plane: &str, egress: &Egress) { - let mut planes = self.planes.write().unwrap_or_else(|e| e.into_inner()); match &egress.cells { - Some(breaker) => { - planes.insert(plane.to_string(), breaker.clone()); - } + Some(breaker) => self.publish_breaker(plane, breaker.clone()), None => { - planes.remove(plane); + self.planes + .write() + .unwrap_or_else(|e| e.into_inner()) + .remove(plane); } } } /// Publish `plane`'s breaker directly: what [`Self::publish`] reads off an egress. - #[cfg(test)] pub(crate) fn publish_breaker(&self, plane: &str, breaker: DoorBreaker) { + self.models + .write() + .unwrap_or_else(|e| e.into_inner()) + .extend(breaker.models.iter().map(|(k, v)| (k.clone(), v.clone()))); self.planes .write() .unwrap_or_else(|e| e.into_inner()) .insert(plane.to_string(), breaker); } + /// READ-ONLY: the `model` label every door plane has stated for a money ledger lane key, as + /// stated, including a retired generation's (its ledger history is still scraped). + #[must_use] + pub fn model_labels(&self) -> HashMap { + self.models + .read() + .unwrap_or_else(|e| e.into_inner()) + .clone() + } + /// READ-ONLY: every cell the published units have materialized, with its own state and /// remaining cooldown, under its stated labels: `(pool, lane, state, cooldown)`. A destination /// whose lifetime budget is spent reads Open with a never-elapsing cooldown, as a dead lane does. diff --git a/crates/busbar-kernel/src/metrics/money.rs b/crates/busbar-kernel/src/metrics/money.rs index 12bd294c39..fca5127e9f 100644 --- a/crates/busbar-kernel/src/metrics/money.rs +++ b/crates/busbar-kernel/src/metrics/money.rs @@ -9,6 +9,15 @@ //! derived from the ledger x the CURRENT rate card (reprice-on-read). //! * `busbar_bucket_tokens` — per (bucket, model, tier), the counts every spend figure is priced from. //! +//! # THE `model` LABEL IS STATED, NEVER AN INTERNAL KEY +//! +//! A money row is ledgered under its lane key. The plane serving the `pools` map keys its rows by +//! the bare model name, which is the label 1.5.5 served. A door plane keys them +//! `"\u{1f}"` (and its fee lane `"\u{1f}"`), an internal key no scrape may +//! carry: the plane states the label each of its keys is scraped under, and this file renders that +//! string as stated ([`model_label`]). A key nobody stated that is not a plain name (a control byte, +//! or the plane separator) emits no series at all. +//! //! # INTEGERS UP TO ONE NAMED BOUNDARY //! //! Every figure here is an integer — cents are `i64`, token counts `u64` — from the ledger read to @@ -33,6 +42,7 @@ use crate::diagnostics::{ METRICS_SCRAPE_GROUP_LEDGER_READ_FAILED, METRICS_SCRAPE_KEY_USAGE_READ_FAILED, METRICS_SCRAPE_LIST_KEYS_FAILED, }; +use crate::governance::PLANE_LANE_SEP; use crate::state::App; /// THE MONEY EGRESS BOUNDARY — the one function in this file that may name a float. @@ -45,11 +55,27 @@ pub(super) fn set_gauge(gauge: metrics::Gauge, value: impl Into) { gauge.set(exact as f64); } +/// THE `model` LABEL a money row's ledger lane `key` is scraped under: the label a door plane stated +/// for it (`stated`, [`crate::metrics::door_cells::DoorCells::model_labels`]), else the key itself +/// when it is a plain name (1.5.5's bare model name). `None` — no series — for an unstated key that +/// carries a control byte or the plane separator, and for a stated label that carries one: an +/// internal key never reaches the exposition. +pub(super) fn model_label<'k>( + stated: &'k std::collections::HashMap, + key: &'k str, +) -> Option<&'k str> { + let label = stated.get(key).map_or(key, String::as_str); + let internal = label.bytes().any(|b| b < 0x20) || label.contains(PLANE_LANE_SEP); + (!internal).then_some(label) +} + /// Refresh every money gauge for this scrape. No-op when governance is disabled. `now` is the /// scrape's single clock read, shared with the lane gauges. pub(super) fn refresh_money_gauges(app: &App, now: u64) { // ── Governance: per-key spend, budget-remaining, tokens ──────────────────────────────────── if let Some(gov) = &app.governance { + // The `model` label every door plane stated for its ledger lane keys, read once. + let stated = app.door_cells.model_labels(); // `all_keys()` lists every VirtualKey from the SQLite store; this is a low-frequency scrape // path. On error we skip only the PER-KEY gauge refresh below (by treating the key list as // empty) rather than returning a stale/wrong per-key value. This must NOT `return` out of @@ -142,9 +168,12 @@ pub(super) fn refresh_money_gauges(app: &App, now: u64) { // Per-(bucket, model, tier) token gauges from the key bucket's ledger (the raw // material any external per-model cost dashboard multiplies by its own catalog). The // key's attribution bucket accrues in the all-time window. - for (model, tokens) in + for (lane, tokens) in gov.bucket_model_tokens(&app.cost, &key.id, crate::governance::WINDOW_TOTAL, now) { + let Some(model) = model_label(&stated, &lane) else { + continue; + }; let tier_v = |u: &str| tokens.get(u).copied().unwrap_or(0); for (tier, v) in [ ("input", tier_v(busbar_contract::records::UNIT_INPUT)), @@ -163,7 +192,7 @@ pub(super) fn refresh_money_gauges(app: &App, now: u64) { for (k, val) in &key.labels { labels.push(metrics::Label::new(k.clone(), val.clone())); } - labels.push(metrics::Label::new("model", model.clone())); + labels.push(metrics::Label::new("model", model.to_string())); labels.push(metrics::Label::new("tier", tier)); set_gauge(metrics::gauge!(BUCKET_TOKENS, labels), v); } @@ -215,9 +244,12 @@ pub(super) fn refresh_money_gauges(app: &App, now: u64) { remaining, ); } - for (model, tokens) in + for (lane, tokens) in gov.bucket_model_tokens(&app.cost, &bucket.bucket_id, bucket.window, now) { + let Some(model) = model_label(&stated, &lane) else { + continue; + }; let tier_v = |u: &str| tokens.get(u).copied().unwrap_or(0); for (tier, v) in [ ("input", tier_v(busbar_contract::records::UNIT_INPUT)), @@ -234,7 +266,7 @@ pub(super) fn refresh_money_gauges(app: &App, now: u64) { set_gauge( metrics::gauge!( BUCKET_TOKENS, - dims(&[("model", model.clone()), ("tier", tier.to_string())]) + dims(&[("model", model.to_string()), ("tier", tier.to_string())]) ), v, ); @@ -244,3 +276,7 @@ pub(super) fn refresh_money_gauges(app: &App, now: u64) { } } } + +#[cfg(test)] +#[path = "../tests/money_label_tests.rs"] +mod tests; diff --git a/crates/busbar-kernel/src/tests/door_cells_tests.rs b/crates/busbar-kernel/src/tests/door_cells_tests.rs index 27674b81e1..0a17f88717 100644 --- a/crates/busbar-kernel/src/tests/door_cells_tests.rs +++ b/crates/busbar-kernel/src/tests/door_cells_tests.rs @@ -23,6 +23,7 @@ fn tripped(pool: &str, stated: &str) -> DoorBreaker { unit, pools: HashMap::from([(pool.to_string(), stated.to_string())]), lanes: HashMap::from([(d(7), "a".to_string()), (d(9), "b".to_string())]), + models: HashMap::new(), } } @@ -59,6 +60,7 @@ fn a_spent_destination_reads_open_for_good() { unit, pools: HashMap::from([("solo".to_string(), "sec/solo".to_string())]), lanes: HashMap::from([(d(3), "solo".to_string())]), + models: HashMap::new(), }, ); assert_eq!( @@ -84,6 +86,7 @@ fn a_newer_generation_replaces_the_older() { unit: fresh, pools: HashMap::from([("key-c".to_string(), "sec/c".to_string())]), lanes: HashMap::from([(d(7), "a".to_string())]), + models: HashMap::new(), }, ); assert_eq!( @@ -110,6 +113,7 @@ fn a_door_planes_tripped_cell_is_on_the_scrape() { unit, pools: HashMap::from([("k\u{1f}scrape".to_string(), "sec-d/scrape".to_string())]), lanes: HashMap::from([(d(7), "ok-m".to_string()), (d(9), "down-m".to_string())]), + models: HashMap::new(), }, ); crate::metrics::refresh_scrape_gauges(&app); diff --git a/crates/busbar-kernel/src/tests/money_label_tests.rs b/crates/busbar-kernel/src/tests/money_label_tests.rs new file mode 100644 index 0000000000..acc8f29ff8 --- /dev/null +++ b/crates/busbar-kernel/src/tests/money_label_tests.rs @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Busbar Inc and contributors + +//! Tests for `crates/busbar-kernel/src/metrics/money.rs`: the `model` label a money row's ledger +//! lane key is scraped under. + +use std::collections::{BTreeMap, HashMap}; +use std::sync::Arc; + +use super::*; +use crate::governance::{GovState, MemoryStore, RecordStore, VirtualKey}; +use crate::metrics::door_cells::DoorBreaker; + +/// THE RULE: a stated label is rendered as stated; an unstated plain name (1.5.5's bare model) is +/// its own label, byte for byte; an unstated key carrying the plane separator or any control byte, +/// and a stated label carrying one, emit no series. +#[test] +fn the_model_label_is_stated_or_plain_and_never_an_internal_key() { + let sep = PLANE_LANE_SEP; + let stated = HashMap::from([ + (format!("door{sep}e"), "sec/e".to_string()), + (format!("door{sep}"), "sec".to_string()), + (format!("bad{sep}e"), "bad\u{7}label".to_string()), + ]); + assert_eq!(model_label(&stated, &format!("door{sep}e")), Some("sec/e")); + assert_eq!( + model_label(&stated, &format!("door{sep}")), + Some("sec"), + "the plane's fee lane: its scope alone" + ); + assert_eq!( + model_label(&stated, "gpt-5"), + Some("gpt-5"), + "the llm plane's bare model keeps its 1.5.5 bytes" + ); + assert_eq!( + model_label(&stated, "claude-3.5/sonnet:v2"), + Some("claude-3.5/sonnet:v2") + ); + assert_eq!( + model_label(&stated, &format!("other{sep}e")), + None, + "an unstated separator-bearing key is suppressed" + ); + assert_eq!( + model_label(&stated, &format!("other{sep}")), + None, + "an unstated fee lane is suppressed" + ); + assert_eq!(model_label(&stated, "tab\there"), None, "a control byte"); + assert_eq!( + model_label(&stated, &format!("bad{sep}e")), + None, + "a stated label carrying a control byte is suppressed too" + ); +} + +fn key(id: &str) -> VirtualKey { + VirtualKey { + id: id.to_string(), + generation_hash: format!("hash-{id}"), + name: format!("key-{id}"), + enabled: true, + created_at: 1_700_000_000, + revision: 1, + ..Default::default() + } +} + +/// ON THE SCRAPE: a door plane's rows are `busbar_bucket_tokens` series under the labels it stated, +/// kept after a newer generation stops stating them (the ledger keeps the history); an unstated +/// internal key is no series; the llm plane's bare model is its own label; no money series carries +/// the separator. RED: the ledger key rendered verbatim (`model="door\u{1f}e"`). +#[test] +fn a_door_planes_money_rows_are_scraped_under_its_stated_labels() { + crate::metrics::init(); + let sep = PLANE_LANE_SEP; + let vk = key("vk_money_label_scrape"); + let store = Arc::new(MemoryStore::new()); + store.put_key(&vk).expect("the key"); + let gov = Arc::new(GovState::new(store, None).expect("governance")); + let cost = crate::cost::CostModel::flat(1); + let input = |n: u64| BTreeMap::from([(busbar_contract::records::UNIT_INPUT.to_string(), n)]); + for (lane, n) in [ + ("gpt-5-money-label".to_string(), 11), + (format!("door{sep}e"), 12), + (format!("door{sep}"), 13), + (format!("unstated{sep}x"), 14), + ] { + gov.record_usage(&cost, &vk, "", &lane, &input(n), 1_700_000_000); + } + let app = crate::test_support::TestApp::new() + .governance(gov) + .cost(cost) + .build(); + let models = |pairs: &[(String, &str)]| -> HashMap { + pairs + .iter() + .map(|(k, v)| (k.clone(), (*v).to_string())) + .collect() + }; + let breaker = |stated: HashMap| DoorBreaker { + unit: Arc::new(busbar_kernel_breaker::BreakerUnit::new()), + pools: HashMap::new(), + lanes: HashMap::new(), + models: stated, + }; + app.door_cells.publish_breaker( + "door", + breaker(models(&[ + (format!("door{sep}e"), "sec/e"), + (format!("door{sep}"), "sec"), + ])), + ); + // A newer generation states only the fee lane: the entry's label is still known. + app.door_cells + .publish_breaker("door", breaker(models(&[(format!("door{sep}"), "sec")]))); + crate::metrics::refresh_scrape_gauges(&app); + let out = crate::metrics::render(); + let ours: Vec<&str> = out + .lines() + .filter(|l| { + l.starts_with("busbar_bucket_tokens{") + && l.contains("bucket=\"vk_money_label_scrape\"") + && l.contains("tier=\"input\"") + }) + .collect(); + let value = |model: &str| { + ours.iter() + .find(|l| l.contains(&format!("model=\"{model}\""))) + .and_then(|l| l.rsplit(' ').next()) + .map(str::to_string) + }; + assert_eq!( + value("gpt-5-money-label").as_deref(), + Some("11"), + "the bare model: {ours:?}" + ); + assert_eq!(value("sec/e").as_deref(), Some("12"), "the entry: {ours:?}"); + assert_eq!( + value("sec").as_deref(), + Some("13"), + "the fee lane: {ours:?}" + ); + assert_eq!( + ours.len(), + 3, + "the unstated internal key is no series: {ours:?}" + ); + for line in out + .lines() + .filter(|l| l.starts_with("busbar_bucket_tokens{")) + { + assert!( + !line.contains(sep), + "no money series carries the separator: {line}" + ); + } +} diff --git a/crates/busbar/src/root/door_steps.rs b/crates/busbar/src/root/door_steps.rs index a4deeecb01..6cef3af442 100644 --- a/crates/busbar/src/root/door_steps.rs +++ b/crates/busbar/src/root/door_steps.rs @@ -1241,6 +1241,7 @@ pub fn compose_egress( unit: breaker.unit(), pools: pool_labels.clone(), lanes: names.iter().cloned().collect(), + models: door_money_labels(facts, pools.entries()), }); Ok(busbar_kernel::plane_driver::Egress { caller, @@ -1287,6 +1288,36 @@ pub fn door_pool_labels<'e>( .collect() } +/// THE `model` LABEL EACH OF A DOOR PLANE'S MONEY LEDGER LANE KEYS IS SCRAPED UNDER, `(key, label)` +/// (`busbar_bucket_tokens`): an entry's key (`plane_lane`) is labelled `"/"`, and the +/// plane's fee lane (`busbar_kernel_ledger::cost::plane_fee_lane`, no entry) its scope alone. The +/// plane serving the `pools` map keys its rows by the bare model name, 1.5.5's own label, and +/// states nothing; a plane with no stated scope states no fee lane label (its rows are then not +/// scraped rather than scraped under its internal key). +pub fn door_money_labels<'e>( + facts: &DoorFacts, + entries: impl IntoIterator, +) -> HashMap { + let scope = facts.metric_scope.as_str(); + if facts.plane.is_empty() { + return HashMap::new(); + } + let stated = |entry: &str| match scope { + "" => entry.to_string(), + scope => format!("{scope}/{entry}"), + }; + entries + .into_iter() + .map(|entry| (plane_lane(&facts.plane, entry), stated(entry))) + .chain((!scope.is_empty()).then(|| { + ( + busbar_kernel_ledger::cost::plane_fee_lane(&facts.plane), + scope.to_string(), + ) + })) + .collect() +} + /// A CONFIG APPLY KEEPS WHAT THE BREAKER LEARNED (v1.5.5 `main.rs:3095-3109`: an apply rebuilds the /// health store "with every surviving lane's learned health state RESTORED BY STABLE IDENTITY", /// `main.rs:1749-1752`): every cell of `prior`'s breaker unit whose member survives into `next`, diff --git a/crates/busbar/src/root/tests/serve_door.rs b/crates/busbar/src/root/tests/serve_door.rs index f1384a9daf..6ecb2c898b 100644 --- a/crates/busbar/src/root/tests/serve_door.rs +++ b/crates/busbar/src/root/tests/serve_door.rs @@ -869,6 +869,125 @@ async fn a_door_planes_tripped_member_is_on_the_metrics_scrape() { } } +/// Every label value on one exposition line, unescaped (`\\`, `\"`, `\n`) as the text format +/// escapes them; empty for a comment or a sample with no labels. +#[cfg(feature = "plane-decisions")] +fn label_values(line: &str) -> Vec { + let Some((_, rest)) = line.split_once('{') else { + return Vec::new(); + }; + let mut values = Vec::new(); + let mut chars = rest.chars(); + while let Some(c) = chars.next() { + if c != '"' { + if c == '}' { + break; + } + continue; + } + let mut value = String::new(); + while let Some(c) = chars.next() { + match c { + '\\' => match chars.next() { + Some('n') => value.push('\n'), + Some(other) => value.push(other), + None => break, + }, + '"' => break, + other => value.push(other), + } + } + values.push(value); + } + values +} + +#[cfg(feature = "plane-decisions")] +/// NO LABEL ON `/metrics` CARRIES A CONTROL BYTE OR THE LANE SEPARATOR, WHATEVER THE FAMILY: every +/// door plane this build composes serves keyed traffic with governance on (the plane serving the +/// `pools` map through its rig, the decisions door through its served route), the scrape gauges +/// are refreshed over both generations, and every sample line of the one exposition is scanned +/// label by label. The money gauge's `model` label is each plane's own: the llm plane's bare model +/// (1.5.5's bytes), and the decisions door's section alone for its fee lane (it bills no token +/// class; an entry's `"
/"` is pinned by the kernel's `metrics::money::tests`). Covers +/// the two planes this base serves through doors; mcp, a2a and streaming are still HOT-lane here +/// and join the scan when their flips land. RED: the money ledger's lane key rendered verbatim +/// (`busbar_bucket_tokens{..,model="\u{1f}",..}`). +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn no_label_on_the_scrape_carries_a_control_byte_or_the_lane_separator() { + let _one = PUBLISHING.lock().await; + let (llm_instance, door_instance) = ("scrape-labels-llm", "scrape-labels-decisions"); + let _llm_published = Published(llm_instance); + let _door_published = Published(door_instance); + busbar_kernel::metrics::init(); + + // THE PLANE SERVING THE `pools` MAP: one keyed chat completion on pool `p` (model `m0`). + let far = far_end_answering(200, SERVED_BY_TWIN).await; + let llm = rig( + llm_instance, + RigOpts { + members: &[(far.port, 1)], + budget_cents: Some(1_000), + ..RigOpts::default() + }, + ) + .await; + assert_eq!(llm.chat().await.0, 200, "the llm plane served"); + assert_eq!(llm.ledger_after(1).await.3, 1, "its unit settled"); + + // THE DECISIONS DOOR: one keyed call on its claim. + let (port, _heard) = far_end().await; + let door = serve_over(&crate::LINKED, door_instance, port).await; + assert_eq!( + send(&door.router, CLAIMED, Some(&door.token)) + .await + .status(), + StatusCode::OK, + "the decisions door served" + ); + + // The scrape, refreshed over both generations (bounded: a unit's line settles write-behind). + // The decisions door bills a per-request fee and no token class, so its token rows are its fee + // lane's: labelled by its section. (An entry's `
/` label is pinned by the kernel's + // `metrics::money::tests`.) + let fee = door.section.to_string(); + let money = |scrape: &str, model: &str| { + scrape.lines().any(|l| { + l.starts_with("busbar_bucket_tokens{") && l.contains(&format!("model=\"{model}\"")) + }) + }; + let mut scrape = String::new(); + for _ in 0..200 { + busbar_kernel::metrics::refresh_scrape_gauges(&llm.app); + busbar_kernel::metrics::refresh_scrape_gauges(&door.handle.load()); + scrape = busbar_kernel::metrics::render(); + if money(&scrape, "m0") && money(&scrape, &fee) { + break; + } + tokio::time::sleep(std::time::Duration::from_millis(10)).await; + } + // THE CLASS: no label value of any sample line carries a control byte or the separator. + let mut scanned = 0usize; + for line in scrape.lines().filter(|l| !l.starts_with('#')) { + for value in label_values(line) { + scanned += 1; + assert!( + !value.bytes().any(|b| b < 0x20) && !value.contains(PLANE_LANE_SEP), + "a label value carries a control byte or the lane separator ({value:?}): {line}" + ); + } + } + assert!(scanned > 0, "the scrape had labels to scan:\n{scrape}"); + assert!( + money(&scrape, "m0"), + "the llm plane's money row, under its bare model:\n{scrape}" + ); + assert!( + money(&scrape, &fee), + "the decisions door's fee lane, under its section:\n{scrape}" + ); +} + // ── THE DOOR SERVING THE `pools` MAP: ITS CAPABILITY CELLS (Q128 U14) ─────────────────────────── // // The capability-equality matrix's root column for this plane, witnessed on its door path: each