use std::path::PathBuf;
use serde_json::Value;
use super::{Cost, Prices};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Ceiling {
limit: Option<u64>,
}
impl Ceiling {
pub fn from_json(value: Option<&Value>) -> Self {
Self {
limit: super::prices::quoted(value),
}
}
pub fn refusal(&self, workspaces: &[PathBuf], prices: &Prices) -> Option<String> {
self.verdict(super::of_world(workspaces, prices))
}
pub fn verdict(&self, spent: Option<Cost>) -> Option<String> {
let limit = self.limit?;
let cost = spent?;
(cost.micro_usd >= limit).then(|| {
let ceiling = Cost {
micro_usd: limit,
unpriced_tokens: 0,
};
format!(
"spend ceiling reached: this world has spent {} across every workspace \
against a {} ceiling (ui.json `ceiling`), so nothing new is started \
anywhere. Everything already running is untouched — raise the ceiling or \
delete the key to spawn again.",
cost.usd(),
ceiling.usd(),
)
})
}
}
#[cfg(test)]
mod tests {
use super::Ceiling;
use crate::spend::Prices;
use serde_json::json;
use std::path::{Path, PathBuf};
const CONV: &str = "20260803T120000Z-root";
fn table() -> Prices {
Prices::from_json(&json!({ "opus": { "input": 1 } }))
}
fn spent(dir: &Path, input: u64) {
let step = dir.join("steps").join(CONV).join("001");
std::fs::create_dir_all(&step).unwrap();
std::fs::write(
step.join("response.json"),
format!(r#"{{"type":"usage","input_tokens":{input}}}"#),
)
.unwrap();
std::fs::write(step.join("request.json"), r#"{"model":"opus"}"#).unwrap();
}
fn world(dir: &Path, input: u64) -> Vec<PathBuf> {
spent(dir, input);
vec![dir.to_path_buf()]
}
#[test]
fn an_absent_or_malformed_key_is_no_gate() {
for value in [None, Some(json!("lots")), Some(json!(-1))] {
let ceiling = Ceiling::from_json(value.as_ref());
assert_eq!(ceiling, Ceiling::default());
let dir = tempfile::tempdir().unwrap();
let roster = world(dir.path(), 9_000_000);
assert!(ceiling.refusal(&roster, &table()).is_none());
}
}
#[test]
fn an_unpriced_world_gates_nothing() {
let dir = tempfile::tempdir().unwrap();
let roster = world(dir.path(), 9_000_000);
let ceiling = Ceiling::from_json(Some(&json!(1)));
assert!(ceiling.refusal(&roster, &Prices::default()).is_none());
}
#[test]
fn under_the_ceiling_flies() {
let dir = tempfile::tempdir().unwrap();
let roster = world(dir.path(), 2_000_000);
let ceiling = Ceiling::from_json(Some(&json!(2.5)));
assert!(ceiling.refusal(&roster, &table()).is_none());
}
#[test]
fn at_the_ceiling_refuses_and_names_both_figures() {
let dir = tempfile::tempdir().unwrap();
let roster = world(dir.path(), 3_000_000);
let refusal = Ceiling::from_json(Some(&json!(2.5)))
.refusal(&roster, &table())
.unwrap();
assert!(refusal.contains("$3.00"), "{refusal}");
assert!(refusal.contains("$2.50"), "{refusal}");
assert!(refusal.contains("untouched"), "{refusal}");
}
#[test]
fn two_workspaces_under_the_number_are_over_it_together() {
let (one, two) = (tempfile::tempdir().unwrap(), tempfile::tempdir().unwrap());
let mut roster = world(one.path(), 2_000_000);
roster.extend(world(two.path(), 2_000_000));
let ceiling = Ceiling::from_json(Some(&json!(2.5)));
assert!(
ceiling.refusal(&roster[..1], &table()).is_none(),
"$2 alone is under a $2.50 ceiling"
);
let refusal = ceiling.refusal(&roster, &table()).unwrap();
assert!(refusal.contains("$4.00"), "{refusal}");
assert!(refusal.contains("every workspace"), "{refusal}");
}
#[test]
fn zero_is_the_hard_stop_and_an_empty_world_spends_nothing() {
assert!(
Ceiling::from_json(Some(&json!(0)))
.refusal(&[], &table())
.is_some(),
"a ceiling of 0 refuses a birth into an unspent world"
);
assert!(
Ceiling::from_json(Some(&json!(0.01)))
.refusal(&[], &table())
.is_none()
);
}
}