Module budget

Module budget 

Source
Expand description

AI token budgets (DW-078): tokens/min and cost/day per consumer or per policy-shared team.

A budget is a LIMIT OF TOTALS, not a rate: it caps how many provider-reported tokens (or how much priced cost) one budget holder may consume inside a window. It composes with — never replaces — the request-count limiter (DW-017) and the request quotas (DW-033): those count REQUESTS, this counts TOKENS.

§Check-then-spend (the locked no-estimation decision)

The gateway never estimates a request’s cost before the provider answers, so enforcement is two-phase:

  1. Pre-check (before any provider contact): a holder whose window is ALREADY exhausted is rejected with 429 ai_budget_exceeded and a Retry-After to the window boundary.
  2. Spend (after usage is known): the provider-REPORTED usage is added to the window — non-streaming from the translated response, streaming from the accumulated DW-077 usage events.

The contract is therefore: reject when the window is exhausted, spend what the provider reports. Overrun within one request is bounded by that one request’s usage — a holder at the limit can always complete one more request, and the next pre-check rejects.

§Mid-stream cutoff

While a stream is being forwarded, the provider’s usage report is spent as it GROWS — each reported token exactly once — and checked against the window after every batch. When a dialect reports usage EARLY (Anthropic carries input tokens in message_start), a crossing is detected mid-stream: forwarding stops, the client receives the documented ai_budget_exceeded SSE event and the terminator, and the provider body is dropped AT ONCE — the upstream cancel propagates immediately, so even a stalled client cannot keep the provider generating. Dialects that only report usage at the END (OpenAI without include_usage would be one; the gateway forces include_usage, whose chunk is terminal) cannot cut off mid-stream: their usage is spent at stream end and enforced by the next pre-check. Documented, honest boundary.

§Windows and keys

tokens_per_min is a fixed 60-second window aligned to the epoch minute (deterministic in tests; Retry-After counts to the next boundary). cost_per_day_micros is a UTC calendar day — the same window shape as the DW-033 quotas. Cost is integer MICRO-USD (no floating-point money). The budget KEY is the consumer name (scope: consumer) or, for a shared team budget, the POLICY name (scope: policy) — every consumer attaching that policy spends from one ledger entry.

§Pricing seam (DW-079)

Cost enforcement is wired end to end but reads prices through cost_micros, which returns 0 until the pricing tables land in DW-079 — enforced-but-inert, the honest seam.

§Lifetime

The ledger (windows) lives on the dataplane and SURVIVES config reloads (a reload must not reset a 60-second window or a daily spend); only the limits resolve per generation. A reload PRUNES the keys the new generation can no longer derive (a removed or renamed holder’s windows) — carried-forward state is bounded by the config, not the deployment’s history. Unbudgeted consumers (no policy with a token_budget binds them) are unlimited — fail-open, the DW-017 default posture. An ANONYMOUS request (a route without auth_required) cannot bind a consumer-scoped budget — the walk falls through to the next candidate, so a less-specific policy-scoped (team) budget still binds rather than silently unlimiting the request.

Structs§

AiBudgetEngine
The per-generation budget rules plus the reload-surviving ledger.
BudgetGuard
The resolved budget for one request: a live guard over the ledger.
BudgetLedger
The shared spend ledger: one entry per (budget key, window kind). A single mutex guards the map — AI request rates are orders of magnitude below the GCRA path’s sharding needs, and the critical section is two integer adds (the DW-033 store pays an fsync per request; this pays none).

Enums§

BudgetKind
Which budget window denied (the kind label of dwara_ai_budget_denied_total).
BudgetVerdict
The pre-check verdict.

Functions§

consumer_policies_of
Resolve a consumer’s attached policies from the config (by name).
cost_micros
The DW-079 pricing seam: micro-USD for one provider-model call with this usage. Reads prices through a DEFAULT (empty) pricing table, so this free function always returns 0 (the unknown-model allow policy) — it exists for test/backward-compat call sites that do not have a dataplane handle. The live path uses the dataplane’s compiled PricingTable (stored on the DataPlane as an ArcSwap, refreshed per generation).
listener_policies_of
The listener’s policy list (by name).
service_policies_of
The route’s service’s policy list (empty when the service is missing — validation rejects that elsewhere).