Files
lumbridge-code/crates/lumbridge-core/src/usage.rs
T
Metal AgentandClaude Opus 5 ef52aa7ce2 Replace the footer's placeholder usage with a real observation ledger
The footer showed invented percentages. It now shows what two harnesses
actually report, or says it does not know.

lumbridge-core gains an append-only per-profile UsageLedger and a projection
that labels every derived value estimated, withholds a burn rate from a single
sample, withholds a window fraction with no reported ceiling, withholds an
exhaustion estimate that lands after the reset, and reports an expired window
as rolled over rather than freezing its last percentage. A missing fact renders
as missing, never as zero. (0012)

lumbridge-harness is the impure side: processes, clocks, and untrusted wire
text in, observations out. Three adapters:

- Codex's account/rateLimits/read over the app-server's JSON-RPC stdio. The
  client cannot express a request outside a two-variant enum and answers every
  server-to-client request with -32601, so a harness asking Lumbridge for a
  credential is refused by construction. (0013)
- Claude Code's session transcripts, as a byte-offset tail follower that
  reports nothing until the backlog is read to EOF — a partially-read backlog
  is indistinguishable from a burst of spend, and the first run against 20 MB
  reported forty-six billion tokens an hour. The parser models four counters,
  so the conversations in those files are not representable. (0014)
- Claude Code's five-hour and seven-day subscription windows, via a bridge
  installed as its statusLine command. 0014 had claimed no such surface
  existed; it does, and the record is corrected in place rather than quietly
  edited. Lumbridge does not read the OAuth credential to call the account
  usage endpoint, which is what comparable tools do — AGENTS.md forbids it,
  and 0015 says so rather than leaving the gap unexplained.

Also in here: a capability-check ordering fix in the workspace reducer, where
the applied-request replay table was consulted before the capability check and
so answered questions the caller had no right to ask; the GPUI spike wired to
the live probes with per-harness gauges and provenance chips; and a launcher
that matches its own window by PID, because GPUI sets WM_NAME but not
_NET_WM_NAME and a title match never succeeded.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 21:47:11 -07:00

1372 lines
44 KiB
Rust

//! Append-only usage observations and the derived footer projection.
//!
//! Decision 0002 requires every usage value to carry its provenance, and the
//! product footer must answer five questions without inventing precision:
//! which profile is active, how much of the window is consumed, when the window
//! resets, what the burn rate is, and how trustworthy those numbers are.
//!
//! This module stores facts, derives views from them, and reports what it
//! cannot know. Nothing here reads a clock: callers supply timestamps so the
//! projection is deterministic and testable. All arithmetic is integer
//! arithmetic; fractions are carried as permille so a rendered percentage never
//! drifts from the stored fact.
//!
//! ```
//! use lumbridge_core::{
//! AccountProfile, FooterUsage, UsageLedger, UsageObservation, UsageProvenance, UsageUnit,
//! UsageWindow,
//! };
//!
//! let profile = AccountProfile::new("codex-plus", "Codex", "ChatGPT", "gpt-5.2", "work")?;
//! let window = UsageWindow::new(0, 18_000_000)?;
//! let mut ledger = UsageLedger::new();
//!
//! for (at_ms, consumed) in [(0, 0), (3_600_000, 620_000)] {
//! ledger.record(
//! UsageObservation::counted(
//! profile.id().clone(),
//! UsageUnit::Tokens,
//! consumed,
//! UsageProvenance::ProviderReported,
//! at_ms,
//! )?
//! .with_limit(1_000_000)?
//! .with_window(window)?,
//! )?;
//! }
//!
//! let projection = ledger.project(profile.id(), 3_600_000);
//! assert_eq!(projection.consumed_permille(), Some(620));
//! assert_eq!(projection.burn_per_hour(), Some(620_000));
//! let footer = FooterUsage::new(&profile, &projection);
//! assert_eq!(footer.window(), "62.0% used");
//! # Ok::<(), lumbridge_core::UsageError>(())
//! ```
use std::collections::{BTreeMap, VecDeque};
use std::fmt;
use thiserror::Error;
const MAX_IDENTITY_BYTES: usize = 256;
const DEFAULT_RETENTION_PER_PROFILE: usize = 64;
const MIN_RETENTION_PER_PROFILE: usize = 2;
const DEFAULT_STALENESS_BUDGET_MS: u64 = 300_000;
const MIN_BURN_SAMPLE_MS: u64 = 1_000;
const MILLIS_PER_HOUR: u128 = 3_600_000;
const MILLIS_PER_MINUTE: u64 = 60_000;
const MILLIS_PER_SECOND: u64 = 1_000;
const MINUTES_PER_HOUR: u64 = 60;
const HOURS_PER_DAY: u64 = 24;
const PERMILLE_SCALE: u128 = 1_000;
const MAX_PERMILLE: u64 = 1_000;
/// Failures produced while recording or shaping usage facts.
#[derive(Clone, Debug, Eq, Error, PartialEq)]
pub enum UsageError {
#[error("invalid {0}")]
InvalidIdentifier(&'static str),
#[error("a usage window must reset after it starts")]
EmptyWindow,
#[error("a usage limit must be greater than zero")]
ZeroLimit,
#[error("observation at {observed_at_ms} ms precedes the newest observation at {newest_ms} ms")]
OutOfOrderObservation { observed_at_ms: u64, newest_ms: u64 },
#[error("observation at {observed_at_ms} ms falls outside its declared window")]
ObservationOutsideWindow { observed_at_ms: u64 },
#[error("retention must keep at least two observations per profile")]
RetentionTooSmall,
#[error("an unavailable observation cannot carry a counted value")]
UnavailableWithValue,
}
/// Where a usage number came from.
///
/// The ordering is deliberate: variants are declared from most to least
/// authoritative so [`UsageProvenance::weakest`] can take the maximum.
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum UsageProvenance {
/// The provider's own account or quota API reported this value.
ProviderReported,
/// The upstream harness reported it through a documented surface.
HarnessReported,
/// Lumbridge counted it locally from traffic it owns.
LocallyMeasured,
/// Lumbridge derived it from other observations.
Estimated,
/// No usable value exists. This is a fact, not a zero.
Unavailable,
}
impl UsageProvenance {
/// The label kept in detail views and exports.
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::ProviderReported => "provider-reported",
Self::HarnessReported => "harness-reported",
Self::LocallyMeasured => "locally measured",
Self::Estimated => "estimated",
Self::Unavailable => "unavailable",
}
}
/// The short label a compact surface such as the footer may substitute.
#[must_use]
pub const fn compact_label(self) -> &'static str {
match self {
Self::ProviderReported => "provider",
Self::HarnessReported => "harness",
Self::LocallyMeasured => "local",
Self::Estimated => "est",
Self::Unavailable => "none",
}
}
/// Whether an external party stated this value rather than Lumbridge.
#[must_use]
pub const fn is_reported(self) -> bool {
matches!(self, Self::ProviderReported | Self::HarnessReported)
}
#[must_use]
pub const fn is_available(self) -> bool {
!matches!(self, Self::Unavailable)
}
#[must_use]
pub const fn confidence(self) -> UsageConfidence {
match self {
Self::ProviderReported => UsageConfidence::Exact,
Self::HarnessReported | Self::LocallyMeasured => UsageConfidence::Approximate,
Self::Estimated => UsageConfidence::Projected,
Self::Unavailable => UsageConfidence::Unknown,
}
}
/// The less authoritative of two provenances.
///
/// A derived value is only as trustworthy as its weakest input.
#[must_use]
pub fn weakest(self, other: Self) -> Self {
self.max(other)
}
}
/// How much a displayed number can be trusted.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum UsageConfidence {
Exact,
Approximate,
Projected,
Unknown,
}
impl UsageConfidence {
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::Exact => "exact",
Self::Approximate => "approximate",
Self::Projected => "projected",
Self::Unknown => "unknown",
}
}
}
/// What a usage number counts.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum UsageUnit {
Tokens,
Requests,
/// A window fraction the provider reports directly, in permille.
WindowPermille,
}
impl UsageUnit {
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::Tokens => "tokens",
Self::Requests => "requests",
Self::WindowPermille => "window",
}
}
/// Renders an amount in this unit without inflating its precision.
#[must_use]
pub fn format_amount(self, amount: u64) -> String {
match self {
Self::Tokens => format_count(amount, "tokens"),
Self::Requests => format_count(amount, "requests"),
Self::WindowPermille => format_permille(amount),
}
}
}
fn format_count(amount: u64, suffix: &str) -> String {
if amount >= 1_000_000 {
format!(
"{}.{}M {suffix}",
amount / 1_000_000,
(amount % 1_000_000) / 100_000
)
} else if amount >= 1_000 {
format!("{}.{}k {suffix}", amount / 1_000, (amount % 1_000) / 100)
} else {
format!("{amount} {suffix}")
}
}
fn format_permille(permille: u64) -> String {
format!("{}.{}%", permille / 10, permille % 10)
}
/// Renders a duration for a compact surface. Never rounds up to a false zero.
#[must_use]
pub fn format_duration_ms(duration_ms: u64) -> String {
if duration_ms < MILLIS_PER_MINUTE {
return format!("{}s", duration_ms / MILLIS_PER_SECOND);
}
let minutes = duration_ms / MILLIS_PER_MINUTE;
if minutes < MINUTES_PER_HOUR {
return format!("{minutes}m");
}
let hours = minutes / MINUTES_PER_HOUR;
if hours < HOURS_PER_DAY {
return format!("{hours}h {}m", minutes % MINUTES_PER_HOUR);
}
// Weekly and monthly quota windows are common, and "151h 30m" is not a
// duration anyone reads at a glance.
format!("{}d {}h", hours / HOURS_PER_DAY, hours % HOURS_PER_DAY)
}
fn validate_identity(value: &str, label: &'static str) -> Result<(), UsageError> {
if value.trim().is_empty()
|| value.len() > MAX_IDENTITY_BYTES
|| value.chars().any(char::is_control)
{
return Err(UsageError::InvalidIdentifier(label));
}
Ok(())
}
/// A stable identifier for one harness/provider/account combination.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct AccountProfileId(String);
impl AccountProfileId {
/// Creates a stable, non-empty account profile identifier.
///
/// # Errors
///
/// Returns [`UsageError::InvalidIdentifier`] for an empty, oversized, or
/// control-bearing identifier.
pub fn new(value: impl Into<String>) -> Result<Self, UsageError> {
let value = value.into();
validate_identity(&value, "account profile ID")?;
Ok(Self(value))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for AccountProfileId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&self.0)
}
}
/// The active harness, provider, model, and account label shown in the footer.
///
/// These are display names only. Credentials stay in the operating-system
/// credential store and are never part of this type.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AccountProfile {
id: AccountProfileId,
harness: String,
provider: String,
model: String,
account: String,
}
impl AccountProfile {
/// Creates a footer-ready account profile.
///
/// # Errors
///
/// Returns [`UsageError::InvalidIdentifier`] when any field is empty,
/// oversized, or contains control characters.
pub fn new(
id: impl Into<String>,
harness: impl Into<String>,
provider: impl Into<String>,
model: impl Into<String>,
account: impl Into<String>,
) -> Result<Self, UsageError> {
let harness = harness.into();
let provider = provider.into();
let model = model.into();
let account = account.into();
validate_identity(&harness, "harness name")?;
validate_identity(&provider, "provider name")?;
validate_identity(&model, "model name")?;
validate_identity(&account, "account label")?;
Ok(Self {
id: AccountProfileId::new(id)?,
harness,
provider,
model,
account,
})
}
#[must_use]
pub const fn id(&self) -> &AccountProfileId {
&self.id
}
#[must_use]
pub fn harness(&self) -> &str {
&self.harness
}
#[must_use]
pub fn provider(&self) -> &str {
&self.provider
}
#[must_use]
pub fn model(&self) -> &str {
&self.model
}
#[must_use]
pub fn account(&self) -> &str {
&self.account
}
/// The footer's first answer: which harness, provider, and model is active.
#[must_use]
pub fn identity_line(&self) -> String {
format!("{} · {} · {}", self.harness, self.provider, self.model)
}
}
/// A provider-declared usage window.
///
/// The start is optional because providers report these fields independently:
/// a reset time can arrive without a window length. Knowing when a window ends
/// is the useful half — it bounds the exhaustion forecast and identifies which
/// window an observation belongs to — so discarding it for want of a start
/// would throw away a fact the provider actually stated.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct UsageWindow {
started_at_ms: Option<u64>,
resets_at_ms: u64,
}
impl UsageWindow {
/// Creates a window that resets strictly after it starts.
///
/// # Errors
///
/// Returns [`UsageError::EmptyWindow`] when the reset is not after the start.
pub const fn new(started_at_ms: u64, resets_at_ms: u64) -> Result<Self, UsageError> {
if resets_at_ms <= started_at_ms {
return Err(UsageError::EmptyWindow);
}
Ok(Self {
started_at_ms: Some(started_at_ms),
resets_at_ms,
})
}
/// Creates a window whose reset is known and whose start is not.
///
/// Two of these compare equal when they reset at the same moment, which is
/// what lets a burn rate be scoped to one window without inventing a start.
#[must_use]
pub const fn until(resets_at_ms: u64) -> Self {
Self {
started_at_ms: None,
resets_at_ms,
}
}
/// When the window opened, if the provider said.
#[must_use]
pub const fn started_at_ms(self) -> Option<u64> {
self.started_at_ms
}
#[must_use]
pub const fn resets_at_ms(self) -> u64 {
self.resets_at_ms
}
#[must_use]
pub const fn contains(self, at_ms: u64) -> bool {
if at_ms >= self.resets_at_ms {
return false;
}
match self.started_at_ms {
Some(started_at_ms) => at_ms >= started_at_ms,
None => true,
}
}
#[must_use]
pub const fn has_expired(self, now_ms: u64) -> bool {
now_ms >= self.resets_at_ms
}
#[must_use]
pub const fn remaining_ms(self, now_ms: u64) -> u64 {
self.resets_at_ms.saturating_sub(now_ms)
}
}
/// One recorded usage fact. Observations are never edited, only appended.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct UsageObservation {
profile: AccountProfileId,
unit: Option<UsageUnit>,
consumed: u64,
limit: Option<u64>,
window: Option<UsageWindow>,
provenance: UsageProvenance,
observed_at_ms: u64,
}
impl UsageObservation {
/// Records a counted usage value with its provenance.
///
/// # Errors
///
/// Returns [`UsageError::UnavailableWithValue`] when the caller labels a
/// counted value as unavailable. Use [`UsageObservation::unavailable`] to
/// record a genuine gap.
pub fn counted(
profile: AccountProfileId,
unit: UsageUnit,
consumed: u64,
provenance: UsageProvenance,
observed_at_ms: u64,
) -> Result<Self, UsageError> {
if !provenance.is_available() {
return Err(UsageError::UnavailableWithValue);
}
Ok(Self {
profile,
unit: Some(unit),
consumed,
limit: None,
window: None,
provenance,
observed_at_ms,
})
}
/// Records that no usage value could be obtained at this moment.
///
/// This is deliberately a first-class observation. Recording it stops the
/// projection from presenting an older reading as current.
#[must_use]
pub const fn unavailable(profile: AccountProfileId, observed_at_ms: u64) -> Self {
Self {
profile,
unit: None,
consumed: 0,
limit: None,
window: None,
provenance: UsageProvenance::Unavailable,
observed_at_ms,
}
}
/// Attaches the quota ceiling this observation was measured against.
///
/// # Errors
///
/// Returns [`UsageError::ZeroLimit`] for a zero limit and
/// [`UsageError::UnavailableWithValue`] for an unavailable observation.
pub fn with_limit(mut self, limit: u64) -> Result<Self, UsageError> {
if !self.provenance.is_available() {
return Err(UsageError::UnavailableWithValue);
}
if limit == 0 {
return Err(UsageError::ZeroLimit);
}
self.limit = Some(limit);
Ok(self)
}
/// Attaches the provider window this observation belongs to.
///
/// # Errors
///
/// Returns [`UsageError::ObservationOutsideWindow`] when the observation
/// time is not inside the window, and [`UsageError::UnavailableWithValue`]
/// for an unavailable observation.
pub fn with_window(mut self, window: UsageWindow) -> Result<Self, UsageError> {
if !self.provenance.is_available() {
return Err(UsageError::UnavailableWithValue);
}
if !window.contains(self.observed_at_ms) {
return Err(UsageError::ObservationOutsideWindow {
observed_at_ms: self.observed_at_ms,
});
}
self.window = Some(window);
Ok(self)
}
#[must_use]
pub const fn profile(&self) -> &AccountProfileId {
&self.profile
}
#[must_use]
pub const fn unit(&self) -> Option<UsageUnit> {
self.unit
}
#[must_use]
pub const fn consumed(&self) -> u64 {
self.consumed
}
#[must_use]
pub const fn limit(&self) -> Option<u64> {
self.limit
}
#[must_use]
pub const fn window(&self) -> Option<UsageWindow> {
self.window
}
#[must_use]
pub const fn provenance(&self) -> UsageProvenance {
self.provenance
}
#[must_use]
pub const fn observed_at_ms(&self) -> u64 {
self.observed_at_ms
}
/// Units left before the ceiling, when a ceiling is known.
#[must_use]
pub fn remaining(&self) -> Option<u64> {
self.limit.map(|limit| limit.saturating_sub(self.consumed))
}
}
/// A bounded, append-only observation stream per account profile.
#[derive(Clone, Debug)]
pub struct UsageLedger {
retention_per_profile: usize,
staleness_budget_ms: u64,
observations: BTreeMap<AccountProfileId, VecDeque<UsageObservation>>,
}
impl Default for UsageLedger {
fn default() -> Self {
Self::new()
}
}
impl UsageLedger {
#[must_use]
pub fn new() -> Self {
Self {
retention_per_profile: DEFAULT_RETENTION_PER_PROFILE,
staleness_budget_ms: DEFAULT_STALENESS_BUDGET_MS,
observations: BTreeMap::new(),
}
}
/// Creates a ledger that retains a bounded history per profile.
///
/// # Errors
///
/// Returns [`UsageError::RetentionTooSmall`] below two observations, which
/// is the minimum a burn rate can be derived from.
pub fn with_retention(retention_per_profile: usize) -> Result<Self, UsageError> {
if retention_per_profile < MIN_RETENTION_PER_PROFILE {
return Err(UsageError::RetentionTooSmall);
}
Ok(Self {
retention_per_profile,
..Self::new()
})
}
/// Sets how old the newest observation may be before it reads as stale.
#[must_use]
pub const fn with_staleness_budget_ms(mut self, staleness_budget_ms: u64) -> Self {
self.staleness_budget_ms = staleness_budget_ms;
self
}
/// Appends an observation to its profile's history.
///
/// # Errors
///
/// Returns [`UsageError::OutOfOrderObservation`] when the observation is
/// older than the newest one already recorded for that profile. The stream
/// is append-only, so time must not move backwards.
pub fn record(&mut self, observation: UsageObservation) -> Result<(), UsageError> {
let history = self
.observations
.entry(observation.profile.clone())
.or_default();
if let Some(newest) = history.back()
&& observation.observed_at_ms < newest.observed_at_ms
{
return Err(UsageError::OutOfOrderObservation {
observed_at_ms: observation.observed_at_ms,
newest_ms: newest.observed_at_ms,
});
}
history.push_back(observation);
while history.len() > self.retention_per_profile {
history.pop_front();
}
Ok(())
}
#[must_use]
pub fn latest(&self, profile: &AccountProfileId) -> Option<&UsageObservation> {
self.observations.get(profile).and_then(VecDeque::back)
}
#[must_use]
pub fn observation_count(&self, profile: &AccountProfileId) -> usize {
self.observations.get(profile).map_or(0, VecDeque::len)
}
pub fn profiles(&self) -> impl Iterator<Item = &AccountProfileId> {
self.observations.keys()
}
/// Derives the current view of a profile's usage.
///
/// Reported facts keep their own provenance. Anything this method computes
/// is labelled [`UsageProvenance::Estimated`], even when every input was
/// provider-reported: a derived rate is not a reported fact.
#[must_use]
pub fn project(&self, profile: &AccountProfileId, now_ms: u64) -> UsageProjection {
let Some(history) = self.observations.get(profile) else {
return UsageProjection::unavailable(None);
};
let Some(latest) = history.back() else {
return UsageProjection::unavailable(None);
};
if !latest.provenance.is_available() {
return UsageProjection::unavailable(Some(latest.observed_at_ms));
}
if let Some(window) = latest.window
&& window.has_expired(now_ms)
{
return UsageProjection::window_rolled(latest.observed_at_ms);
}
let consumed_permille = latest
.limit
.map(|limit| permille_of(latest.consumed, limit))
.or_else(|| {
matches!(latest.unit, Some(UsageUnit::WindowPermille))
.then_some(latest.consumed.min(MAX_PERMILLE))
});
let burn_per_hour = burn_per_hour(history, latest);
let resets_in_ms = latest.window.map(|window| window.remaining_ms(now_ms));
let exhaustion_in_ms = burn_per_hour
.and_then(|rate| exhaustion_in_ms(latest.remaining()?, rate, resets_in_ms));
UsageProjection {
unit: latest.unit,
consumed: Some(latest.consumed),
limit: latest.limit,
consumed_permille,
resets_in_ms,
burn_per_hour,
exhaustion_in_ms,
provenance: latest.provenance,
stale: now_ms.saturating_sub(latest.observed_at_ms) > self.staleness_budget_ms,
window_rolled: false,
observed_at_ms: Some(latest.observed_at_ms),
}
}
}
fn permille_of(consumed: u64, limit: u64) -> u64 {
if limit == 0 {
return 0;
}
let permille = u128::from(consumed) * PERMILLE_SCALE / u128::from(limit);
u64::try_from(permille)
.unwrap_or(MAX_PERMILLE)
.min(MAX_PERMILLE)
}
/// Averages consumption across the newest observation's window.
///
/// Returns `None` unless two observations share a window and a unit, are far
/// enough apart to divide by, and did not move backwards. A single reading
/// yields no rate at all rather than a rate of zero.
fn burn_per_hour(history: &VecDeque<UsageObservation>, latest: &UsageObservation) -> Option<u64> {
let baseline = history.iter().find(|candidate| {
candidate.window == latest.window
&& candidate.unit == latest.unit
&& candidate.provenance.is_available()
&& candidate.observed_at_ms < latest.observed_at_ms
})?;
if latest.consumed < baseline.consumed {
return None;
}
let elapsed_ms = latest.observed_at_ms - baseline.observed_at_ms;
if elapsed_ms < MIN_BURN_SAMPLE_MS {
return None;
}
let consumed_delta = u128::from(latest.consumed - baseline.consumed);
let rate = consumed_delta * MILLIS_PER_HOUR / u128::from(elapsed_ms);
u64::try_from(rate).ok()
}
/// Projects when a ceiling is reached, unless the window resets first.
fn exhaustion_in_ms(remaining: u64, burn_per_hour: u64, resets_in_ms: Option<u64>) -> Option<u64> {
if burn_per_hour == 0 {
return None;
}
let millis = u128::from(remaining) * MILLIS_PER_HOUR / u128::from(burn_per_hour);
let millis = u64::try_from(millis).ok()?;
match resets_in_ms {
Some(resets_in_ms) if millis >= resets_in_ms => None,
_ => Some(millis),
}
}
/// The derived view a footer or detail panel renders.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct UsageProjection {
unit: Option<UsageUnit>,
consumed: Option<u64>,
limit: Option<u64>,
consumed_permille: Option<u64>,
resets_in_ms: Option<u64>,
burn_per_hour: Option<u64>,
exhaustion_in_ms: Option<u64>,
provenance: UsageProvenance,
stale: bool,
window_rolled: bool,
observed_at_ms: Option<u64>,
}
impl UsageProjection {
const fn unavailable(observed_at_ms: Option<u64>) -> Self {
Self {
unit: None,
consumed: None,
limit: None,
consumed_permille: None,
resets_in_ms: None,
burn_per_hour: None,
exhaustion_in_ms: None,
provenance: UsageProvenance::Unavailable,
stale: false,
window_rolled: false,
observed_at_ms,
}
}
const fn window_rolled(observed_at_ms: u64) -> Self {
Self {
stale: true,
window_rolled: true,
..Self::unavailable(Some(observed_at_ms))
}
}
#[must_use]
pub const fn unit(self) -> Option<UsageUnit> {
self.unit
}
#[must_use]
pub const fn consumed(self) -> Option<u64> {
self.consumed
}
#[must_use]
pub const fn limit(self) -> Option<u64> {
self.limit
}
/// Share of the window consumed, in permille, when a ceiling is known.
#[must_use]
pub const fn consumed_permille(self) -> Option<u64> {
self.consumed_permille
}
#[must_use]
pub const fn resets_in_ms(self) -> Option<u64> {
self.resets_in_ms
}
/// Units per hour averaged over the current window.
#[must_use]
pub const fn burn_per_hour(self) -> Option<u64> {
self.burn_per_hour
}
/// Time until the ceiling is reached, absent unless it precedes the reset.
#[must_use]
pub const fn exhaustion_in_ms(self) -> Option<u64> {
self.exhaustion_in_ms
}
/// The provenance of the reported facts in this projection.
#[must_use]
pub const fn provenance(self) -> UsageProvenance {
self.provenance
}
/// The provenance of the burn rate, which is always derived.
#[must_use]
pub const fn burn_provenance(self) -> UsageProvenance {
match self.burn_per_hour {
Some(_) => UsageProvenance::Estimated,
None => UsageProvenance::Unavailable,
}
}
/// The trust classification a detail view must keep.
#[must_use]
pub const fn confidence(self) -> UsageConfidence {
if self.stale {
return UsageConfidence::Unknown;
}
self.provenance.confidence()
}
/// Whether the newest observation is older than the staleness budget.
#[must_use]
pub const fn is_stale(self) -> bool {
self.stale
}
/// Whether the last known window ended before this projection was taken.
#[must_use]
pub const fn window_has_rolled(self) -> bool {
self.window_rolled
}
#[must_use]
pub const fn observed_at_ms(self) -> Option<u64> {
self.observed_at_ms
}
#[must_use]
pub const fn is_available(self) -> bool {
self.provenance.is_available()
}
}
/// Renders the five footer answers for one profile.
///
/// Every method returns an explicit unavailable phrase instead of a placeholder
/// number, so a missing fact reads as missing rather than as zero.
#[derive(Clone, Copy, Debug)]
pub struct FooterUsage<'a> {
profile: &'a AccountProfile,
projection: &'a UsageProjection,
}
impl<'a> FooterUsage<'a> {
#[must_use]
pub const fn new(profile: &'a AccountProfile, projection: &'a UsageProjection) -> Self {
Self {
profile,
projection,
}
}
/// Which harness, provider, model, and account profile is active.
#[must_use]
pub fn identity(&self) -> String {
format!(
"{} · {}",
self.profile.identity_line(),
self.profile.account()
)
}
/// How much of the current usage window has been consumed.
#[must_use]
pub fn window(&self) -> String {
match (
self.projection.consumed_permille(),
self.projection.consumed(),
self.projection.unit(),
) {
(Some(permille), _, _) => format!("{} used", format_permille(permille)),
(None, Some(consumed), Some(unit)) => {
format!(
"{} used · no ceiling reported",
unit.format_amount(consumed)
)
}
_ if self.projection.window_has_rolled() => "window rolled over".to_owned(),
_ => "usage unavailable".to_owned(),
}
}
/// When the window resets.
#[must_use]
pub fn reset(&self) -> String {
self.projection.resets_in_ms().map_or_else(
|| "reset time unavailable".to_owned(),
|remaining| format!("resets in {}", format_duration_ms(remaining)),
)
}
/// The recent burn rate and, when meaningful, the projected exhaustion.
#[must_use]
pub fn burn(&self) -> String {
let Some(rate) = self.projection.burn_per_hour() else {
return "burn rate unavailable".to_owned();
};
let unit = self.projection.unit().unwrap_or(UsageUnit::Tokens);
let rate = format!("~{}/hr est", unit.format_amount(rate));
self.projection
.exhaustion_in_ms()
.map_or(rate.clone(), |exhaustion| {
format!("{rate} · empty in {}", format_duration_ms(exhaustion))
})
}
/// How trustworthy the numbers above are.
#[must_use]
pub fn trust(&self) -> String {
let provenance = self.projection.provenance();
if !provenance.is_available() {
return if self.projection.window_has_rolled() {
"awaiting a new window reading".to_owned()
} else {
"no usage source".to_owned()
};
}
let base = format!(
"{} · {}",
provenance.compact_label(),
self.projection.confidence().label()
);
if self.projection.is_stale() {
return format!("{base} · stale");
}
base
}
}
impl fmt::Display for FooterUsage<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"{} · {} · {} · {} · {}",
self.identity(),
self.window(),
self.reset(),
self.burn(),
self.trust()
)
}
}
#[cfg(test)]
mod tests {
use super::{
AccountProfile, AccountProfileId, FooterUsage, UsageConfidence, UsageError, UsageLedger,
UsageObservation, UsageProvenance, UsageUnit, UsageWindow, format_duration_ms,
};
const HOUR_MS: u64 = 3_600_000;
fn profile() -> AccountProfile {
AccountProfile::new("codex-plus", "Codex", "ChatGPT", "gpt-5.2", "work")
.expect("the fixture profile is valid")
}
fn window() -> UsageWindow {
UsageWindow::new(0, 5 * HOUR_MS).expect("the fixture window is valid")
}
fn observation(consumed: u64, at_ms: u64, provenance: UsageProvenance) -> UsageObservation {
UsageObservation::counted(
AccountProfileId::new("codex-plus").expect("valid ID"),
UsageUnit::Tokens,
consumed,
provenance,
at_ms,
)
.expect("a counted observation is available")
.with_limit(1_000_000)
.expect("a positive limit is valid")
.with_window(window())
.expect("the observation is inside the window")
}
fn ledger_with(samples: &[(u64, u64)]) -> UsageLedger {
let mut ledger = UsageLedger::new();
for &(consumed, at_ms) in samples {
ledger
.record(observation(
consumed,
at_ms,
UsageProvenance::ProviderReported,
))
.expect("fixture observations are ordered");
}
ledger
}
#[test]
fn provenance_labels_and_confidence_are_stable() {
assert_eq!(
UsageProvenance::ProviderReported.label(),
"provider-reported"
);
assert_eq!(UsageProvenance::Estimated.compact_label(), "est");
assert!(UsageProvenance::HarnessReported.is_reported());
assert!(!UsageProvenance::LocallyMeasured.is_reported());
assert_eq!(
UsageProvenance::ProviderReported.confidence(),
UsageConfidence::Exact
);
assert_eq!(
UsageProvenance::Unavailable.confidence(),
UsageConfidence::Unknown
);
}
#[test]
fn the_weakest_provenance_wins() {
assert_eq!(
UsageProvenance::ProviderReported.weakest(UsageProvenance::Estimated),
UsageProvenance::Estimated
);
assert_eq!(
UsageProvenance::Estimated.weakest(UsageProvenance::Unavailable),
UsageProvenance::Unavailable
);
}
#[test]
fn a_counted_value_cannot_be_labelled_unavailable() {
let error = UsageObservation::counted(
AccountProfileId::new("codex-plus").expect("valid ID"),
UsageUnit::Tokens,
10,
UsageProvenance::Unavailable,
0,
)
.expect_err("unavailable counted values are rejected");
assert_eq!(error, UsageError::UnavailableWithValue);
}
#[test]
fn windows_and_limits_are_validated() {
assert_eq!(UsageWindow::new(10, 10), Err(UsageError::EmptyWindow));
let observation = UsageObservation::counted(
AccountProfileId::new("codex-plus").expect("valid ID"),
UsageUnit::Tokens,
10,
UsageProvenance::ProviderReported,
9 * HOUR_MS,
)
.expect("a counted observation is available");
assert_eq!(
observation.clone().with_limit(0),
Err(UsageError::ZeroLimit)
);
assert!(matches!(
observation.with_window(window()),
Err(UsageError::ObservationOutsideWindow { .. })
));
}
#[test]
fn the_ledger_refuses_to_move_backwards() {
let mut ledger = ledger_with(&[(0, 0), (100, HOUR_MS)]);
let error = ledger
.record(observation(
50,
HOUR_MS / 2,
UsageProvenance::ProviderReported,
))
.expect_err("an older observation is rejected");
assert!(matches!(
error,
UsageError::OutOfOrderObservation { newest_ms, .. } if newest_ms == HOUR_MS
));
}
#[test]
fn retention_is_bounded_and_keeps_the_newest_samples() {
let mut ledger = UsageLedger::with_retention(2).expect("two samples are allowed");
for step in 0..5 {
ledger
.record(observation(
step * 1_000,
step * HOUR_MS / 4,
UsageProvenance::ProviderReported,
))
.expect("ordered observations are accepted");
}
let id = AccountProfileId::new("codex-plus").expect("valid ID");
assert_eq!(ledger.observation_count(&id), 2);
assert_eq!(
ledger.latest(&id).map(UsageObservation::consumed),
Some(4_000)
);
assert!(matches!(
UsageLedger::with_retention(1),
Err(UsageError::RetentionTooSmall)
));
}
#[test]
fn a_single_observation_yields_no_burn_rate() {
let ledger = ledger_with(&[(620_000, 0)]);
let projection = ledger.project(profile().id(), 0);
assert_eq!(projection.consumed_permille(), Some(620));
assert_eq!(projection.burn_per_hour(), None, "a rate needs two samples");
assert_eq!(projection.exhaustion_in_ms(), None);
}
#[test]
fn burn_and_exhaustion_are_derived_from_the_window() {
let ledger = ledger_with(&[(0, 0), (300_000, HOUR_MS)]);
let projection = ledger.project(profile().id(), HOUR_MS);
assert_eq!(projection.burn_per_hour(), Some(300_000));
assert_eq!(projection.consumed_permille(), Some(300));
assert_eq!(projection.resets_in_ms(), Some(4 * HOUR_MS));
assert_eq!(
projection.exhaustion_in_ms(),
Some(2 * HOUR_MS + 20 * 60_000),
"700k remaining at 300k/hr is reached before the four-hour reset"
);
assert_eq!(projection.burn_provenance(), UsageProvenance::Estimated);
assert_eq!(projection.provenance(), UsageProvenance::ProviderReported);
}
#[test]
fn exhaustion_is_withheld_when_the_window_resets_first() {
let ledger = ledger_with(&[(0, 0), (10_000, HOUR_MS)]);
let projection = ledger.project(profile().id(), HOUR_MS);
assert_eq!(projection.burn_per_hour(), Some(10_000));
assert_eq!(
projection.exhaustion_in_ms(),
None,
"the window resets long before the ceiling is reached"
);
}
#[test]
fn an_unavailable_observation_invalidates_older_facts() {
let mut ledger = ledger_with(&[(0, 0), (200_000, HOUR_MS)]);
ledger
.record(UsageObservation::unavailable(
AccountProfileId::new("codex-plus").expect("valid ID"),
2 * HOUR_MS,
))
.expect("a gap is recordable");
let projection = ledger.project(profile().id(), 2 * HOUR_MS);
assert!(!projection.is_available());
assert_eq!(projection.consumed_permille(), None);
assert_eq!(projection.burn_per_hour(), None);
assert_eq!(projection.confidence(), UsageConfidence::Unknown);
}
#[test]
fn an_expired_window_is_not_presented_as_current() {
let ledger = ledger_with(&[(0, 0), (200_000, HOUR_MS)]);
let projection = ledger.project(profile().id(), 6 * HOUR_MS);
assert!(projection.window_has_rolled());
assert!(projection.is_stale());
assert_eq!(projection.consumed_permille(), None);
assert_eq!(projection.burn_per_hour(), None);
}
#[test]
fn an_unknown_profile_projects_as_unavailable() {
let ledger = UsageLedger::new();
let projection = ledger.project(profile().id(), 0);
assert!(!projection.is_available());
assert_eq!(projection.observed_at_ms(), None);
}
#[test]
fn staleness_demotes_confidence_without_erasing_the_fact() {
let ledger = UsageLedger::new().with_staleness_budget_ms(1_000);
let mut ledger = ledger;
ledger
.record(observation(200_000, 0, UsageProvenance::ProviderReported))
.expect("ordered");
ledger
.record(observation(
400_000,
HOUR_MS,
UsageProvenance::ProviderReported,
))
.expect("ordered");
let projection = ledger.project(profile().id(), HOUR_MS + 5_000);
assert!(projection.is_stale());
assert_eq!(projection.consumed_permille(), Some(400));
assert_eq!(projection.confidence(), UsageConfidence::Unknown);
}
#[test]
fn the_footer_answers_five_questions() {
let ledger = ledger_with(&[(0, 0), (620_000, HOUR_MS)]);
let projection = ledger.project(profile().id(), HOUR_MS);
let profile = profile();
let footer = FooterUsage::new(&profile, &projection);
assert_eq!(footer.identity(), "Codex · ChatGPT · gpt-5.2 · work");
assert_eq!(footer.window(), "62.0% used");
assert_eq!(footer.reset(), "resets in 4h 0m");
assert!(footer.burn().starts_with("~620.0k tokens/hr est"));
assert_eq!(footer.trust(), "provider · exact");
}
#[test]
fn the_footer_says_unavailable_instead_of_zero() {
let ledger = UsageLedger::new();
let projection = ledger.project(profile().id(), 0);
let profile = profile();
let footer = FooterUsage::new(&profile, &projection);
assert_eq!(footer.window(), "usage unavailable");
assert_eq!(footer.reset(), "reset time unavailable");
assert_eq!(footer.burn(), "burn rate unavailable");
assert_eq!(footer.trust(), "no usage source");
assert!(!footer.to_string().contains('%'));
}
#[test]
fn a_locally_measured_count_without_a_ceiling_says_so() {
let mut ledger = UsageLedger::new();
ledger
.record(
UsageObservation::counted(
AccountProfileId::new("codex-plus").expect("valid ID"),
UsageUnit::Requests,
42,
UsageProvenance::LocallyMeasured,
0,
)
.expect("available"),
)
.expect("ordered");
let projection = ledger.project(profile().id(), 0);
let profile = profile();
let footer = FooterUsage::new(&profile, &projection);
assert_eq!(footer.window(), "42 requests used · no ceiling reported");
assert_eq!(footer.trust(), "local · approximate");
}
#[test]
fn durations_render_without_false_precision() {
assert_eq!(format_duration_ms(45_000), "45s");
assert_eq!(format_duration_ms(14 * 60_000), "14m");
assert_eq!(format_duration_ms(2 * HOUR_MS + 14 * 60_000), "2h 14m");
// A weekly quota window is a real provider shape, not a hypothetical.
assert_eq!(format_duration_ms(10_080 * 60_000), "7d 0h");
assert_eq!(format_duration_ms(151 * HOUR_MS + 30 * 60_000), "6d 7h");
}
#[test]
fn a_window_can_know_its_reset_without_knowing_its_start() {
let window = UsageWindow::until(5 * HOUR_MS);
assert_eq!(window.started_at_ms(), None);
assert_eq!(window.resets_at_ms(), 5 * HOUR_MS);
assert!(window.contains(0), "an unknown start bounds nothing below");
assert!(window.contains(5 * HOUR_MS - 1));
assert!(
!window.contains(5 * HOUR_MS),
"the reset still bounds it above"
);
assert!(window.has_expired(5 * HOUR_MS));
assert_eq!(window.remaining_ms(HOUR_MS), 4 * HOUR_MS);
}
#[test]
fn windows_with_the_same_reset_share_an_identity() {
assert_eq!(UsageWindow::until(HOUR_MS), UsageWindow::until(HOUR_MS));
assert_ne!(UsageWindow::until(HOUR_MS), UsageWindow::until(2 * HOUR_MS));
assert_ne!(
UsageWindow::until(HOUR_MS),
UsageWindow::new(0, HOUR_MS).expect("a bounded window is valid"),
"a known start is a different fact from an unknown one"
);
}
#[test]
fn a_start_only_window_still_scopes_a_burn_rate_to_one_period() {
// The provider reported a reset but no window length, then the quota
// rolled. A baseline must not be drawn across the two periods.
let id = AccountProfileId::new("codex-plus").expect("valid ID");
let mut ledger = UsageLedger::new();
for (consumed, at_ms, resets_at_ms) in [
(800, 0, 5 * HOUR_MS),
(50, 5 * HOUR_MS + 1_000, 10 * HOUR_MS),
(850, 6 * HOUR_MS, 10 * HOUR_MS),
] {
ledger
.record(
UsageObservation::counted(
id.clone(),
UsageUnit::WindowPermille,
consumed,
UsageProvenance::ProviderReported,
at_ms,
)
.expect("available")
.with_limit(1_000)
.expect("a positive limit")
.with_window(UsageWindow::until(resets_at_ms))
.expect("the observation precedes its reset"),
)
.expect("ordered");
}
let projection = ledger.project(&id, 6 * HOUR_MS);
assert_eq!(projection.resets_in_ms(), Some(4 * HOUR_MS));
assert_eq!(
projection.burn_per_hour(),
Some(800),
"the baseline must be the reading from this window, not the last one"
);
assert_eq!(
projection.exhaustion_in_ms(),
Some(675_000),
"150 permille left at 800 per hour is about eleven minutes"
);
}
#[test]
fn identities_reject_empty_and_control_values() {
assert!(matches!(
AccountProfile::new("id", "", "p", "m", "a"),
Err(UsageError::InvalidIdentifier("harness name"))
));
assert!(matches!(
AccountProfileId::new("bad\nid"),
Err(UsageError::InvalidIdentifier("account profile ID"))
));
}
}