Skip to content

g1t/services/billing/src/margin.rs

2,278 lines109,446 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1//! What g1t earns on each thing it sells, measured against what
2//! Cloudflare actually charged for it.
3//!
4//! Once a day, after `costs` has read Cloudflare's bill, the reconciler
5//! puts three figures side by side for every day and each of g1t's
6//! products (a "bucket": sandboxes, deployments, git, repository storage,
7//! …):
8//!
9//! 1. **What Cloudflare charged**: the day's cost lines `cost_map` gives
10//! the bucket.
11//! 2. **What g1t's meters recorded**: the cost on the ledger's entries for
12//! it (the price book's cost at the time) and, where a mapping names
13//! one, g1t's own count of the same units (git operations).
14//! 3. **What customers were charged**: the entries' value at price, before
15//! the plan's included usage, a trial or a pool paid part of it; and of
16//! that, what workspaces paid. Month-end meters (git, storage, scans,
17//! embeddings, the actions cache) come from daily snapshots of what they
18//! had come to (`pending_days`). The plan's price is the `platform`
19//! bucket's: the plan pays for running g1t.
20//!
21//! From those: margin per product (value against cost) and for all of g1t
22//! (money in against every cost); drift (counts or costs that disagree past
23//! a mapping's threshold, and leaks: cost with no revenue, or a Cloudflare
24//! meter no one mapped); each workspace's cost, Cloudflare's figure shared
25//! out by each workspace's own meters; and price proposals when a unit's
26//! real cost has moved (`pricing`). Alerts go to staff by email and as a
27//! banner in sudo. See docs/BILLING_OPERATIONS.md.
28
29use std::collections::{BTreeMap, BTreeSet};
30
31use g1t_contracts::billing::*;
32use g1t_contracts::{FailureCode, Outcome, new_id};
33use g1t_contracts::time::rfc3339;
34use g1t_kit::now_ms;
35use serde::{Deserialize, Serialize};
36use worker::wasm_bindgen::JsValue;
37use worker::{Env, Result};
38
39use crate::Billing;
40use crate::costs::{self, ARTIFACTS_OPERATIONS, DAY_MS, Rule, SOURCE_ARTIFACTS, SOURCE_BILLABLE, UNMAPPED};
41
42/// Buckets that are the cost of running g1t, paid by the plan rather than
43/// sold by the unit: never a leak for having no revenue of their own.
44pub(crate) const OVERHEAD: [&str; 1] = ["platform"];
45/// Buckets Cloudflare does not bill: their cost is g1t's own figure.
46pub(crate) const NOT_CLOUDFLARE: [&str; 1] = ["models"];
47/// The days drift is judged over.
48const DRIFT_DAYS: u64 = 7;
49/// The days a workspace's cost is set against its revenue.
50const ANOMALY_DAYS: u64 = 30;
51/// The days a unit's cost is measured over.
52const MEASURE_DAYS: u64 = 30;
53/// Fewer of g1t's units than this say nothing about cost per unit.
54const MIN_UNITS: f64 = 1_000.0;
55/// An open alert is emailed again after this long.
56const REMIND_MS: u64 = 7 * DAY_MS;
57
58// ---------------------------------------------------------------------
59// The arithmetic, apart from the database so it can be tested.
60// ---------------------------------------------------------------------
61
62/// One of g1t's products on one day.
63#[derive(Clone, Debug, Default, PartialEq)]
64pub(crate) struct ProductDay {
65 pub day: String,
66 pub bucket: String,
67 /// What Cloudflare charged g1t, in millionths of a dollar.
68 pub cf_cost_micros: i64,
69 /// What g1t's meters recorded it cost (the price book's cost).
70 pub own_cost_micros: i64,
71 /// What customers were charged for it at price, before what paid.
72 pub value_micros: i64,
73 /// Of that, what workspaces paid themselves.
74 pub cash_micros: i64,
75 /// Units Cloudflare counted and units g1t counted, where a mapping
76 /// says they are the same units.
77 pub cf_quantity: f64,
78 pub own_quantity: f64,
Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it79 /// Of `cost()`, what went on usage g1t gave away (the workspaces'
80 /// `WorkspaceDay::given`, added up).
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running81 pub given: Given,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily82}
83
84impl ProductDay {
85 /// What it cost: Cloudflare's figure where Cloudflare bills it, else
86 /// g1t's own (models are billed by their providers, through the gateway).
87 pub fn cost(&self) -> i64 {
88 if NOT_CLOUDFLARE.contains(&self.bucket.as_str()) { self.own_cost_micros } else { self.cf_cost_micros }
89 }
90}
91
92/// A line of Cloudflare's bill, as stored.
93#[derive(Clone, Debug, Deserialize)]
94pub(crate) struct LineRow {
95 pub day: String,
96 pub source: String,
97 pub product: String,
98 pub meter: String,
99 pub quantity: f64,
100 pub cost_usd: f64,
101}
102
103/// A count of g1t's own, as stored.
104#[derive(Clone, Debug, Deserialize)]
105pub(crate) struct OwnRow {
106 pub day: String,
107 pub meter: String,
108 pub workspace: String,
109 pub quantity: f64,
110}
111
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running112/// What g1t gave away, by why: its own comped workspaces, free use (a
113/// free period, free allowances, overruns g1t covered), the trial, and the
Merge branch 'worktree-agent-a633ac0f7f66d419d'114/// open-source pool, and discounts on an account's terms (what they took
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging115/// below cost plus the margin, `ledger.discount_micros`), and credits g1t
116/// staff gave, promotional and goodwill, when spent (`grants`). The Team
117/// plan's included usage is paid for by the plan's price, so it is sold,
118/// not given; so is what a refund pays for.
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running119#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
120pub(crate) struct Given {
121 pub comped: i64,
122 pub free: i64,
123 pub trial: i64,
124 pub pool: i64,
Merge branch 'worktree-agent-a633ac0f7f66d419d'125 pub discount: i64,
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging126 pub credit_promotional: i64,
127 pub credit_goodwill: i64,
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running128}
129
130impl Given {
131 pub fn total(&self) -> i64 {
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging132 self.comped + self.free + self.trial + self.pool + self.discount + self.credit()
133 }
134
135 /// Credits from g1t, both kinds.
136 pub fn credit(&self) -> i64 {
137 self.credit_promotional + self.credit_goodwill
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running138 }
139
140 fn add(&mut self, other: &Given) {
141 self.comped += other.comped;
142 self.free += other.free;
143 self.trial += other.trial;
144 self.pool += other.pool;
Merge branch 'worktree-agent-a633ac0f7f66d419d'145 self.discount += other.discount;
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging146 self.credit_promotional += other.credit_promotional;
147 self.credit_goodwill += other.credit_goodwill;
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running148 }
149
150 /// The same shares of `cost` as these are of `value`, at most all of it.
151 fn of(&self, cost: i64, value: i64) -> Given {
152 let total = self.total();
153 if value <= 0 || cost <= 0 || total <= 0 {
154 return Given::default();
155 }
156 let given = cost as i128 * total.min(value) as i128 / value as i128;
157 let part = |x: i64| (given * x.max(0) as i128 / total as i128) as i64;
Merge branch 'worktree-agent-a633ac0f7f66d419d'158 Given {
159 comped: part(self.comped),
160 free: part(self.free),
161 trial: part(self.trial),
162 pool: part(self.pool),
163 discount: part(self.discount),
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging164 credit_promotional: part(self.credit_promotional),
165 credit_goodwill: part(self.credit_goodwill),
166 }
167 }
168}
169
170/// Credits from g1t in the reconciliation (`grants`): usage paid for with
171/// promotional or goodwill credit is given, not money in; a refund comes
172/// off money in on the day it refunds, shared over that day's paid usage.
173/// What credit paid for that is not among `rows` (month-end meters, or
174/// what was owed from before) is a row of its own on its day.
175pub(crate) fn apply_credits(rows: &mut Vec<UsageRow>, draws: &[(String, crate::grants::Draw)], refunds: &[crate::grants::Refunded]) {
176 let mut paid: BTreeMap<(String, String, String), Given> = BTreeMap::new();
177 for (workspace, draw) in draws {
178 let given = paid
179 .entry((draw.at[..10].to_owned(), workspace.clone(), crate::grants::usage_key(draw.task.as_deref(), &draw.reference)))
180 .or_default();
181 match draw.kind {
182 CreditKind::Promotional => given.credit_promotional += draw.micros,
183 CreditKind::Goodwill => given.credit_goodwill += draw.micros,
184 // Money already paid: what it pays for is paid for.
185 CreditKind::Refund | CreditKind::Purchased => {}
186 }
187 }
188 for ((day, workspace, key), given) in paid {
189 if given.credit() == 0 {
190 continue;
Merge branch 'worktree-agent-a633ac0f7f66d419d'191 }
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging192 match rows.iter_mut().find(|r| r.day == day && r.workspace == workspace && r.key == key) {
193 Some(row) => {
194 row.cash -= given.credit();
195 row.given.add(&given);
196 }
197 None => rows.push(UsageRow { day, workspace, key, value: 0, cash: -given.credit(), cost: 0, given }),
198 }
199 }
200 for refund in refunds {
201 let weights: Vec<(String, f64)> = rows
202 .iter()
203 .enumerate()
204 .filter(|(_, r)| r.day == refund.day && r.workspace == refund.workspace && r.cash > 0)
205 .map(|(i, r)| (format!("{i:08}"), r.cash as f64))
206 .collect();
207 let shares = attribute(refund.micros, &weights);
208 if shares.is_empty() {
209 rows.push(UsageRow {
210 day: refund.day.clone(),
211 workspace: refund.workspace.clone(),
212 key: "other".into(),
213 cash: -refund.micros,
214 ..UsageRow::default()
215 });
216 }
217 for (index, micros) in shares {
218 if let Ok(i) = index.parse::<usize>() {
219 rows[i].cash -= micros;
220 }
221 }
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running222 }
223}
224
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily225/// What a workspace was charged for one key on one day.
226#[derive(Clone, Debug, Default, PartialEq)]
227pub(crate) struct UsageRow {
228 pub day: String,
229 pub workspace: String,
230 /// A ledger task (or `builds`), a month-end source, or `plan`.
231 pub key: String,
232 pub value: i64,
233 pub cash: i64,
234 pub cost: i64,
Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it235 /// Of `value`, what g1t gave away: all of it for g1t's own (comped)
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running236 /// workspaces and in a free period, else what the trial and the pool
237 /// paid and the overruns g1t covered.
238 pub given: Given,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily239}
240
241/// One workspace's share of a product's cost on one day.
242#[derive(Clone, Debug, PartialEq)]
243pub(crate) struct WorkspaceDay {
244 pub day: String,
245 pub workspace: String,
246 pub bucket: String,
247 pub cost: i64,
Margin alerts measure what is sold, and say dollars when a percentage would mislead248 /// What the workspace paid in cash.
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily249 pub revenue: i64,
Margin alerts measure what is sold, and say dollars when a percentage would mislead250 /// What its usage was priced at, whoever paid for it.
251 pub value: i64,
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running252 /// Of `cost`, the part g1t gave away: all of it for a comped workspace
253 /// or one with nothing priced that day (free use), else the cost times
254 /// the shares of its usage that day that g1t paid for.
255 pub given: Given,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily256}
257
258fn micros(dollars: f64) -> i64 {
259 (dollars * 1_000_000.0).round() as i64
260}
261
262/// Puts the day's bill, g1t's counts and what customers were charged side
263/// by side, a row per day and bucket, and shares each bucket's cost out
264/// to workspaces.
265pub(crate) fn fold(
266 rules: &[Rule],
267 revenue_map: &BTreeMap<String, String>,
268 lines: &[LineRow],
269 own: &[OwnRow],
270 usage: &[UsageRow],
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running271 internal: &BTreeSet<String>,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily272) -> (Vec<ProductDay>, Vec<WorkspaceDay>) {
273 let mut days: BTreeMap<(String, String), ProductDay> = BTreeMap::new();
274 let entry = |day: &str, bucket: &str| -> ProductDay {
275 ProductDay { day: day.to_owned(), bucket: bucket.to_owned(), ..ProductDay::default() }
276 };
277 // Which of g1t's own meters count each bucket's units.
278 let mut own_meters: BTreeMap<&str, BTreeSet<&str>> = BTreeMap::new();
279 for rule in rules {
280 if let Some(meter) = &rule.own_meter {
281 own_meters.entry(rule.bucket.as_str()).or_default().insert(meter.as_str());
282 }
283 }
284 let mut events: BTreeMap<(String, String), f64> = BTreeMap::new();
285 for line in lines {
286 let rule = costs::classify(rules, &line.product, &line.meter);
287 let bucket = rule.map_or(UNMAPPED, |r| r.bucket.as_str());
288 let key = (line.day.clone(), bucket.to_owned());
289 if line.source == SOURCE_ARTIFACTS {
290 // What Artifacts counted: operations only, and only where the
291 // bill does not count them itself.
292 if ARTIFACTS_OPERATIONS.contains(&line.meter.as_str()) {
293 *events.entry(key).or_default() += line.quantity;
294 }
295 continue;
296 }
297 let row = days.entry(key.clone()).or_insert_with(|| entry(&key.0, &key.1));
298 row.cf_cost_micros += micros(line.cost_usd);
299 if line.source == SOURCE_BILLABLE && rule.is_some_and(|r| r.own_meter.is_some()) {
300 row.cf_quantity += line.quantity;
301 }
302 }
303 for (key, quantity) in events {
304 let row = days.entry(key.clone()).or_insert_with(|| entry(&key.0, &key.1));
305 if row.cf_quantity == 0.0 {
306 row.cf_quantity = quantity;
307 }
308 }
309 // g1t's own counts of the same units, by bucket and by workspace.
310 let mut own_by: BTreeMap<(String, String), Vec<(String, f64)>> = BTreeMap::new();
311 // Cloudflare's own count by workspace, where it gives one
312 // (`cloudflare_<bucket>`): the best way to share its cost.
313 let mut cf_by: BTreeMap<(String, String), Vec<(String, f64)>> = BTreeMap::new();
314 for count in own {
315 if let Some(bucket) = count.meter.strip_prefix("cloudflare_") {
316 cf_by.entry((count.day.clone(), bucket.to_owned())).or_default().push((count.workspace.clone(), count.quantity));
317 continue;
318 }
319 for (bucket, meters) in &own_meters {
320 if meters.contains(count.meter.as_str()) {
321 let key = (count.day.clone(), (*bucket).to_owned());
322 days.entry(key.clone()).or_insert_with(|| entry(&key.0, &key.1)).own_quantity += count.quantity;
323 own_by.entry(key).or_default().push((count.workspace.clone(), count.quantity));
324 }
325 }
326 }
327 // What customers were charged.
328 let bucket_of = |key: &str| revenue_map.get(key).cloned().unwrap_or_else(|| "models".to_owned());
329 let mut value_by: BTreeMap<(String, String), Vec<(String, f64)>> = BTreeMap::new();
330 let mut cost_by: BTreeMap<(String, String), Vec<(String, f64)>> = BTreeMap::new();
331 let mut revenue: BTreeMap<(String, String, String), i64> = BTreeMap::new();
Margin alerts measure what is sold, and say dollars when a percentage would mislead332 let mut valued: BTreeMap<(String, String, String), i64> = BTreeMap::new();
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily333 let mut active: BTreeMap<String, Vec<(String, f64)>> = BTreeMap::new();
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running334 let mut gave: BTreeMap<(String, String), (Given, i64)> = BTreeMap::new();
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily335 for u in usage {
Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it336 let g = gave.entry((u.day.clone(), u.workspace.clone())).or_default();
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running337 g.0.add(&u.given);
Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it338 g.1 += u.value;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily339 let bucket = bucket_of(&u.key);
340 let key = (u.day.clone(), bucket.clone());
341 let row = days.entry(key.clone()).or_insert_with(|| entry(&key.0, &key.1));
342 row.own_cost_micros += u.cost;
343 row.value_micros += u.value;
344 row.cash_micros += u.cash;
345 value_by.entry(key.clone()).or_default().push((u.workspace.clone(), u.value as f64));
346 cost_by.entry(key).or_default().push((u.workspace.clone(), u.cost as f64));
Margin alerts measure what is sold, and say dollars when a percentage would mislead347 *revenue.entry((u.day.clone(), u.workspace.clone(), bucket.clone())).or_default() += u.cash;
348 *valued.entry((u.day.clone(), u.workspace.clone(), bucket)).or_default() += u.value;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily349 active.entry(u.day.clone()).or_default().push((u.workspace.clone(), u.value.max(u.cost) as f64));
350 }
351 // Each bucket's cost shared out: by Cloudflare's own count per
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running352 // workspace, else by g1t's own count of its units, else by what its
353 // usage cost (so free use carries its own cost), else by what it was
354 // charged; running g1t, and what no one mapped, by each workspace's
355 // share of all usage that day.
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily356 let mut shares: BTreeMap<(String, String, String), i64> = BTreeMap::new();
357 for ((day, bucket), row) in &days {
358 let key = (day.clone(), bucket.clone());
359 let weigh = |m: &BTreeMap<(String, String), Vec<(String, f64)>>| m.get(&key).filter(|w| w.iter().any(|(_, v)| *v > 0.0)).cloned();
360 let weights = if OVERHEAD.contains(&bucket.as_str()) || bucket == UNMAPPED {
361 active.get(day).cloned()
362 } else {
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running363 weigh(&cf_by).or_else(|| weigh(&own_by)).or_else(|| weigh(&cost_by)).or_else(|| weigh(&value_by)).or_else(|| active.get(day).cloned())
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily364 };
365 for (workspace, micros) in attribute(row.cost(), &weights.unwrap_or_default()) {
366 *shares.entry((day.clone(), workspace, bucket.clone())).or_default() += micros;
367 }
368 }
369 let keys: BTreeSet<(String, String, String)> = shares.keys().chain(revenue.keys()).cloned().collect();
Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it370 let workspaces: Vec<WorkspaceDay> = keys
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily371 .into_iter()
Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it372 .map(|(day, workspace, bucket)| {
373 let cost = shares.get(&(day.clone(), workspace.clone(), bucket.clone())).copied().unwrap_or(0);
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running374 // The day's shares given away apply to every bucket, so a
375 // comped workspace's part of running g1t is given too. A
376 // workspace with nothing priced that day used g1t for free.
377 let given = if internal.contains(&workspace) {
378 Given { comped: cost, ..Given::default() }
379 } else {
380 match gave.get(&(day.clone(), workspace.clone())) {
381 Some((given, value)) if *value > 0 => given.of(cost, *value),
382 _ => Given { free: cost.max(0), ..Given::default() },
383 }
Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it384 };
385 WorkspaceDay {
386 cost,
387 revenue: revenue.get(&(day.clone(), workspace.clone(), bucket.clone())).copied().unwrap_or(0),
388 value: valued.get(&(day.clone(), workspace.clone(), bucket.clone())).copied().unwrap_or(0),
389 given,
390 day,
391 workspace,
392 bucket,
393 }
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily394 })
395 .collect();
Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it396 for w in &workspaces {
397 if let Some(row) = days.get_mut(&(w.day.clone(), w.bucket.clone())) {
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running398 row.given.add(&w.given);
Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it399 }
400 }
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily401 (days.into_values().collect(), workspaces)
402}
403
404/// A month-end source's day, from the snapshots of what it had come to:
405/// each day's figure less the day before's in the same month (the first
406/// day of a month, or the first snapshot, is its own).
407pub(crate) fn pending_deltas(snapshots: &[(String, String, String, i64, i64)]) -> Vec<UsageRow> {
408 // (day, workspace, source, cost, charge), any order.
409 let mut sorted = snapshots.to_vec();
410 sorted.sort_by(|a, b| (&a.1, &a.2, &a.0).cmp(&(&b.1, &b.2, &b.0)));
411 let mut out = Vec::new();
412 let mut previous: Option<&(String, String, String, i64, i64)> = None;
413 for snap in &sorted {
414 let (day, workspace, source, cost, charge) = snap;
415 let (before_cost, before_charge) = match previous {
416 Some(p) if p.1 == *workspace && p.2 == *source && p.0[..7] == day[..7] => (p.3, p.4),
417 _ => (0, 0),
418 };
419 let (cost, charge) = ((cost - before_cost).max(0), (charge - before_charge).max(0));
420 if cost > 0 || charge > 0 {
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running421 out.push(UsageRow { day: day.clone(), workspace: workspace.clone(), key: source.clone(), value: charge, cash: charge, cost, given: Given::default() });
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily422 }
423 previous = Some(snap);
424 }
425 out
426}
427
428/// Margin as a share of what was charged, in percent; None when nothing was.
429pub(crate) fn margin_percent(revenue_micros: i64, cost_micros: i64) -> Option<f64> {
430 (revenue_micros > 0).then(|| (revenue_micros - cost_micros) as f64 * 100.0 / revenue_micros as f64)
431}
432
433/// How far `ours` is from `theirs`, in percent of theirs; None when theirs
434/// is nothing.
435pub(crate) fn delta_percent(ours: f64, theirs: f64) -> Option<f64> {
436 (theirs > 0.0).then(|| (ours - theirs) * 100.0 / theirs)
437}
438
439#[derive(Clone, Copy, Debug, PartialEq, Eq)]
440pub(crate) enum DriftKind {
441 /// g1t counted a different number of units than Cloudflare did.
442 Count,
443 /// What Cloudflare charged differs from what the price book says the
444 /// same usage cost.
445 Cost,
446 /// Cloudflare charged for something nothing charges customers for.
447 Leak,
Merge branch 'worktree-agent-a633ac0f7f66d419d'448 /// Model usage AI Gateway put no price on: its cost is not what the
449 /// provider bills, so neither the ledger nor the gateway total has it.
450 Unpriced,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily451}
452
453impl DriftKind {
454 pub fn as_str(self) -> &'static str {
455 match self {
456 DriftKind::Count => "count",
457 DriftKind::Cost => "cost",
458 DriftKind::Leak => "leak",
Merge branch 'worktree-agent-a633ac0f7f66d419d'459 DriftKind::Unpriced => "unpriced",
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily460 }
461 }
462}
463
464#[derive(Clone, Debug, PartialEq)]
465pub(crate) struct Drift {
466 pub bucket: String,
467 pub kind: DriftKind,
468 pub ours: f64,
469 pub cloudflare: f64,
470 pub delta_percent: Option<f64>,
471}
472
473/// Drift over a window for one bucket: counts more than `threshold`
474/// percent apart, a bill that far from the price book's cost of the same
475/// usage, and cost with nothing charged for it. Under `min_cost_micros`
476/// in all, cost says nothing.
477pub(crate) fn drifts(bucket: &str, days: &[ProductDay], threshold: f64, counted: bool, min_cost_micros: i64) -> Vec<Drift> {
478 let overhead = OVERHEAD.contains(&bucket);
479 let sum = |f: &dyn Fn(&ProductDay) -> f64| days.iter().map(f).sum::<f64>();
480 let cf_cost = sum(&|d| d.cf_cost_micros as f64);
481 let own_cost = sum(&|d| d.own_cost_micros as f64);
482 let value = sum(&|d| d.value_micros as f64);
Costs: counts are compared with Cloudflare's from the day g1t started counting, so days before a meter existed are not drift483 // Counts are compared from the first day g1t counted: before its meter
484 // was deployed there is only Cloudflare's side. A meter that never
485 // counted anything is compared over every day, so it still shows.
486 let first_counted = days.iter().filter(|d| d.own_quantity > 0.0).map(|d| d.day.as_str()).min();
487 let compared = |d: &&ProductDay| first_counted.is_none_or(|from| d.day.as_str() >= from);
488 let (cf_quantity, own_quantity) = days.iter().filter(compared).fold((0.0, 0.0), |(cf, own), d| (cf + d.cf_quantity, own + d.own_quantity));
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily489 let mut out = Vec::new();
490 if counted && cf_quantity > 0.0 {
491 let delta = delta_percent(own_quantity, cf_quantity);
492 if delta.is_some_and(|d| d.abs() > threshold) {
493 out.push(Drift { bucket: bucket.into(), kind: DriftKind::Count, ours: own_quantity, cloudflare: cf_quantity, delta_percent: delta });
494 }
495 }
496 let enough = cf_cost.max(own_cost) >= min_cost_micros as f64;
Merge branch 'worktree-agent-a633ac0f7f66d419d'497 // Models: what AI Gateway priced g1t's own provider traffic at (its
498 // lines, as "Cloudflare's" side) against the ledger's model cost. Only
499 // once the gateway has been read; then the ledger having none of it is
500 // drift too (traffic no run was charged for).
501 let models = NOT_CLOUDFLARE.contains(&bucket) && cf_cost > 0.0;
502 if enough && !overhead && cf_cost > 0.0 && (own_cost > 0.0 || models) {
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily503 let delta = delta_percent(own_cost, cf_cost);
504 if delta.is_some_and(|d| d.abs() > threshold) {
505 out.push(Drift { bucket: bucket.into(), kind: DriftKind::Cost, ours: own_cost, cloudflare: cf_cost, delta_percent: delta });
506 }
507 }
Billing: AI Gateway's analytics are read with the token that can see them, and a gateway that priced nothing is said508 // The ledger has model cost and the gateway priced none of it: a token
509 // that cannot see AI Gateway reads as no rows, never an error, so this
510 // is not agreement. Said, rather than left as no row at all.
511 if NOT_CLOUDFLARE.contains(&bucket) && cf_cost <= 0.0 && own_cost >= min_cost_micros as f64 && own_cost > 0.0 {
512 out.push(Drift { bucket: bucket.into(), kind: DriftKind::Cost, ours: own_cost, cloudflare: 0.0, delta_percent: None });
513 }
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily514 if !overhead && cf_cost >= min_cost_micros as f64 && value <= 0.0 {
515 out.push(Drift { bucket: bucket.into(), kind: DriftKind::Leak, ours: value, cloudflare: cf_cost, delta_percent: None });
516 }
517 out
518}
519
Merge branch 'worktree-agent-a633ac0f7f66d419d'520/// What can make AI Gateway's cost differ from what the providers bill,
521/// said for staff: cache tokens (priced by the gateway at its own rates for
522/// them, which may lag the provider's), requests Cloudflare billed itself,
523/// models it has no price for, and runs settled short.
524fn caveat_notes(c: &costs::GatewayCaveats) -> Vec<String> {
525 let mut notes = Vec::new();
526 if c.cache_read_tokens > 0.0 || c.cache_write_tokens > 0.0 {
527 notes.push(format!(
528 "{} prompt-cache read and {} cache write tokens went through it: check its cost against the provider's invoice, since cache reads are billed far below input and writes above it",
529 crate::features::thousands(c.cache_read_tokens.round() as u64),
530 crate::features::thousands(c.cache_write_tokens.round() as u64)
531 ));
532 }
533 if c.wholesale_usd > 0.0 {
534 notes.push(format!(
535 "{} of it Cloudflare billed itself (unified billing): that part is on Cloudflare's bill, not a provider's",
536 dollars(micros(c.wholesale_usd))
537 ));
538 }
539 if !c.unpriced.is_empty() {
540 notes.push(format!("it has no price for {} (tokens used, $0)", c.unpriced.join(", ")));
541 }
542 if c.short_runs > 0 {
543 notes.push(format!("{} runs were settled at no less than the sandbox reported because the gateway could not price all of them", c.short_runs));
544 }
545 notes
546}
547
548/// The models drift's detail: the gateway's total against the ledger's.
549pub(crate) fn models_detail(drift: &Drift, caveats: &costs::GatewayCaveats) -> String {
Billing: AI Gateway's analytics are read with the token that can see them, and a gateway that priced nothing is said550 if drift.cloudflare <= 0.0 {
551 return format!(
552 "Models: the ledger's model cost is {} over the last {DRIFT_DAYS} days and AI Gateway priced nothing, so the two were not compared. Either the gateway's analytics cannot be seen (Cloudflare answers a token without AI Gateway: Read with no rows, not an error; billing reads them with CLOUDFLARE_USAGE_TOKEN, then CLOUDFLARE_BILLING_TOKEN), or model calls went around the gateway.",
553 dollars(drift.ours as i64)
554 );
555 }
Merge branch 'worktree-agent-a633ac0f7f66d419d'556 let lower = drift.ours < drift.cloudflare;
557 let mut detail = format!(
558 "Models: AI Gateway priced g1t's own provider traffic at {} over the last {DRIFT_DAYS} days; the ledger's model cost for the same days is {} ({:+.1}%). {}",
559 dollars(drift.cloudflare as i64),
560 dollars(drift.ours as i64),
561 drift.delta_percent.unwrap_or(0.0),
562 if lower {
563 "Model calls g1t paid for were not charged: runs not yet settled, runs with no session, or calls with no run (the ledger catches up as runs settle; a gap that stays is a leak)."
564 } else {
565 "The ledger counts more than the gateway priced: runs that went to a provider without the gateway, or sandbox reports the gateway could not correct."
566 }
567 );
568 let notes = caveat_notes(caveats);
569 if !notes.is_empty() {
570 detail.push_str(" The gateway's cost may be off: ");
571 detail.push_str(&notes.join("; "));
572 detail.push('.');
573 }
574 detail
575}
576
577/// The unpriced drift's detail.
578pub(crate) fn unpriced_detail(caveats: &costs::GatewayCaveats) -> String {
579 format!(
580 "Models: AI Gateway's cost is not all of what the providers bill over the last {DRIFT_DAYS} days: {}. Runs on a model with no gateway price are charged no less than the sandbox reported; add the model's price to the gateway (or route away from it) so it is charged at cost.",
581 caveat_notes(&costs::GatewayCaveats { cache_read_tokens: 0.0, cache_write_tokens: 0.0, wholesale_usd: 0.0, ..caveats.clone() }).join("; ")
582 )
583}
584
585/// Model usage AI Gateway could not price over the window, as drift on
586/// the models bucket: models with tokens and no cost, or runs settled
587/// short. None when there is none.
588pub(crate) fn unpriced_drift(caveats: &costs::GatewayCaveats) -> Option<(Drift, String)> {
589 if caveats.unpriced.is_empty() && caveats.short_runs == 0 {
590 return None;
591 }
592 let drift = Drift {
593 bucket: NOT_CLOUDFLARE[0].into(),
594 kind: DriftKind::Unpriced,
595 ours: f64::from(caveats.short_runs),
596 cloudflare: caveats.unpriced.len() as f64,
597 delta_percent: None,
598 };
599 Some((drift, unpriced_detail(caveats)))
600}
601
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily602/// When the last `days` in a row (each with enough cost to say something)
603/// were all under the floor: the first of them and the worst margin.
604/// Each item is a day's (day, revenue, cost).
605pub(crate) fn breach(series: &[(String, i64, i64)], floor_percent: f64, days: usize, min_cost_micros: i64) -> Option<(String, f64)> {
606 if days == 0 || series.len() < days {
607 return None;
608 }
609 let tail = &series[series.len() - days..];
610 let mut worst = f64::INFINITY;
611 for (_, revenue, cost) in tail {
612 if *cost < min_cost_micros {
613 return None;
614 }
615 let margin = margin_percent(*revenue, *cost).unwrap_or(-100.0);
616 if margin >= floor_percent {
617 return None;
618 }
619 worst = worst.min(margin);
620 }
621 Some((tail[0].0.clone(), worst))
622}
623
624/// `total` shared out in proportion to `weights`, in whole millionths that
625/// add up to it exactly (largest remainder first). Nothing to share, or no
626/// weight, shares nothing.
627pub(crate) fn attribute(total: i64, weights: &[(String, f64)]) -> Vec<(String, i64)> {
628 let mut merged: BTreeMap<String, f64> = BTreeMap::new();
629 for (key, w) in weights {
630 *merged.entry(key.clone()).or_default() += w.max(0.0);
631 }
632 let sum: f64 = merged.values().sum();
633 if total <= 0 || sum <= 0.0 {
634 return Vec::new();
635 }
636 let mut shares: Vec<(String, i64, f64)> = merged
637 .into_iter()
638 .map(|(key, w)| {
639 let exact = total as f64 * w / sum;
640 (key, exact.floor() as i64, exact - exact.floor())
641 })
642 .collect();
643 let mut left = total - shares.iter().map(|s| s.1).sum::<i64>();
644 let mut order: Vec<usize> = (0..shares.len()).collect();
645 order.sort_by(|a, b| shares[*b].2.total_cmp(&shares[*a].2).then(shares[*a].0.cmp(&shares[*b].0)));
646 for index in order {
647 if left <= 0 {
648 break;
649 }
650 shares[index].1 += 1;
651 left -= 1;
652 }
653 shares.into_iter().filter(|s| s.1 > 0).map(|(key, micros, _)| (key, micros)).collect()
654}
655
656/// Workspaces that cost g1t more than `factor` times what they paid, with
657/// at least `floor_micros` of cost: each (workspace, cost, revenue), the
658/// biggest gap first.
Models' margin read -14%: usage nothing paid for is valued at price, not $0659/// What a day's usage was worth at price. g1t's own workspaces are valued
660/// at price. So is usage nothing paid for, neither charged nor drawn from
661/// the plan, a trial, a pool or a gift (a free period): it was given away at
662/// its price, not sold for nothing. Anything paid keeps what it was paid, so
663/// a discount still shows as one.
664pub(crate) fn usage_value(internal: bool, cost: i64, paid: i64, margin_percent: u32) -> i64 {
665 if internal || (paid == 0 && cost > 0) {
666 return crate::credits::with_margin(cost, margin_percent);
667 }
668 paid
669}
670
Margin alerts measure what is sold, and say dollars when a percentage would mislead671/// What the overall alert says: the money as money, and a percentage only
672/// while there is enough coming in for one to mean something (a few cents
673/// against dollars of cost reads as -8000%).
674pub(crate) fn overall_detail(took: i64, spent: i64, days: usize, floor: f64, worst: f64) -> String {
675 if took < 1_000_000 * days as i64 {
676 return format!(
677 "All of g1t, comped workspaces left out: took in {} against {} of Cloudflare's bill over {days} days.",
678 dollars(took),
679 dollars(spent)
680 );
681 }
682 format!("All of g1t, comped workspaces left out: money in against Cloudflare's bill under {floor:.0}% for {days} days running, as low as {worst:.1}%.")
683}
684
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily685pub(crate) fn anomalies(rows: &[(String, i64, i64)], factor: f64, floor_micros: i64) -> Vec<(String, i64, i64)> {
686 let mut out: Vec<(String, i64, i64)> = rows
687 .iter()
688 .filter(|(_, cost, revenue)| *cost >= floor_micros && *cost as f64 > *revenue as f64 * factor)
689 .cloned()
690 .collect();
691 out.sort_by(|a, b| (b.1 - b.2).cmp(&(a.1 - a.2)).then(a.0.cmp(&b.0)));
692 out
693}
694
695/// Cloudflare's marginal rate for one of its units: the median over the
696/// charged days of cost over quantity, in dollars. None while the included
697/// amounts still cover it. Each item is a day's (quantity, cost).
698pub(crate) fn billed_rate(days: &[(f64, f64)]) -> Option<f64> {
699 let mut rates: Vec<f64> = days.iter().filter(|(q, c)| *q > 0.0 && *c > 0.0).map(|(q, c)| c / q).collect();
700 if rates.is_empty() {
701 return None;
702 }
703 rates.sort_by(f64::total_cmp);
704 Some(rates[rates.len() / 2])
705}
706
707/// What one of g1t's units costs, from Cloudflare's rate per its own unit
708/// and how many of Cloudflare's units each of g1t's took: if Cloudflare
709/// counts three operations for every git operation g1t counts, a git
710/// operation costs three of Cloudflare's. None without enough of g1t's
711/// units to say.
712pub(crate) fn derived_unit_cost(rate_per_cf_unit: f64, cf_units: f64, own_units: f64) -> Option<f64> {
713 (own_units >= MIN_UNITS && cf_units > 0.0 && rate_per_cf_unit > 0.0).then(|| rate_per_cf_unit * cf_units / own_units)
714}
715
716/// How many units a price is per: `1,000 operations` → 1,000, `million
717/// requests` → 1,000,000, `second` → 1.
718pub(crate) fn unit_size(unit: &str) -> f64 {
719 let first = unit.split_whitespace().next().unwrap_or_default().replace(',', "");
720 match first.as_str() {
721 "million" => 1_000_000.0,
722 "thousand" => 1_000.0,
723 n => n.parse().unwrap_or(1.0),
724 }
725}
726
727fn day_before(day: &str, days: u64) -> String {
728 let ms = g1t_contracts::time::parse_rfc3339(&format!("{day}T00:00:00Z")).unwrap_or(0);
729 rfc3339(ms.saturating_sub(days * DAY_MS))[..10].to_owned()
730}
731
732/// Dollars to the cent from a dollar up, finer below: `$17.02`, `$0.063`.
733fn dollars(micros: i64) -> String {
734 if micros.abs() >= 1_000_000 {
735 let cents = (micros as f64 / 10_000.0).round() as i64;
736 format!("{}${}.{:02}", if cents < 0 { "-" } else { "" }, cents.abs() / 100, cents.abs() % 100)
737 } else {
738 crate::features::dollars(micros)
739 }
740}
741
742/// The days a plan payment is spread over.
743const PLAN_DAYS: u64 = 30;
744
745/// `micros` paid on `day` spread evenly over `days` days from it, in
746/// whole micros that add up to it (the first days take the remainder).
747pub(crate) fn spread(day: &str, micros: i64, days: u64) -> Vec<(String, i64)> {
748 if micros <= 0 || days == 0 {
749 return Vec::new();
750 }
751 let start = g1t_contracts::time::parse_rfc3339(&format!("{}T00:00:00Z", &day[..10.min(day.len())])).unwrap_or(0);
752 let each = micros / days as i64;
753 let rest = micros % days as i64;
754 (0..days)
755 .map(|n| (rfc3339(start + n * DAY_MS)[..10].to_owned(), each + i64::from((n as i64) < rest)))
756 .collect()
757}
758
759// ---------------------------------------------------------------------
760// The daily run, and what sudo reads.
761// ---------------------------------------------------------------------
762
763#[derive(Serialize)]
764struct Mail<'a> {
765 to: &'a str,
766 from: &'a str,
767 subject: &'a str,
768 text: String,
769 html: String,
770}
771
772fn escape(text: &str) -> String {
773 text.replace('&', "&amp;").replace('<', "&lt;").replace('>', "&gt;").replace('"', "&quot;")
774}
775
776/// Emails staff through Cloudflare Email Sending, the `EMAIL` binding.
Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays777pub(crate) async fn email_staff(env: &Env, to: &str, subject: &str, lines: &[String]) -> Result<()> {
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily778 let link = "https://sudo.g1t.sh/costs";
779 let text = format!("{}\n\nCosts & margin: {link}\n\nSent by g1t-billing's margin guard (COSTS_ALERT_EMAIL).\n", lines.join("\n\n"));
780 let mut html = String::from("<div style=\"font-family:system-ui,sans-serif;max-width:560px;margin:0 auto;padding:24px 16px;color:#16150f\">");
781 for line in lines {
782 html.push_str(&format!("<p style=\"font-size:15px;line-height:1.6\">{}</p>", escape(line)));
783 }
784 html.push_str(&format!(
785 "<p><a href=\"{link}\">Open Costs &amp; margin in sudo</a></p><p style=\"font-size:13px;color:#6e6a5e\">Sent by g1t-billing's margin guard (COSTS_ALERT_EMAIL).</p></div>"
786 ));
787 let mail = Mail { to, from: "g1t <noreply@g1t.sh>", subject, text, html };
788 let binding = g1t_kit::js::binding(env, "EMAIL")?;
789 g1t_kit::js::call(&binding, "send", &[g1t_kit::js::to_js(&mail)?]).await?;
790 Ok(())
791}
792
793#[derive(Deserialize)]
794struct AlertRow {
795 id: String,
796 kind: String,
797 subject: String,
798 detail: String,
799 since: String,
800 opened_at: String,
801 emailed_at: Option<String>,
802}
803
804impl From<AlertRow> for MarginAlert {
805 fn from(r: AlertRow) -> Self {
806 MarginAlert { id: r.id, kind: r.kind, subject: r.subject, detail: r.detail, since: r.since, opened_at: r.opened_at, emailed_at: r.emailed_at }
807 }
808}
809
810#[derive(Deserialize)]
811struct MarginRow {
812 day: String,
813 bucket: String,
814 cf_cost_micros: i64,
815 own_cost_micros: i64,
816 value_micros: i64,
817 cash_micros: i64,
818 cf_quantity: f64,
819 own_quantity: f64,
Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it820 #[serde(default)]
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running821 given_comped_micros: Option<i64>,
822 #[serde(default)]
823 given_free_micros: Option<i64>,
824 #[serde(default)]
825 given_trial_micros: Option<i64>,
826 #[serde(default)]
827 given_pool_micros: Option<i64>,
Merge branch 'worktree-agent-a633ac0f7f66d419d'828 #[serde(default)]
829 given_discount_micros: Option<i64>,
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging830 #[serde(default)]
831 given_credit_promotional_micros: Option<i64>,
832 #[serde(default)]
833 given_credit_goodwill_micros: Option<i64>,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily834}
835
836impl From<MarginRow> for ProductDay {
837 fn from(r: MarginRow) -> Self {
838 ProductDay {
839 day: r.day,
840 bucket: r.bucket,
841 cf_cost_micros: r.cf_cost_micros,
842 own_cost_micros: r.own_cost_micros,
843 value_micros: r.value_micros,
844 cash_micros: r.cash_micros,
845 cf_quantity: r.cf_quantity,
846 own_quantity: r.own_quantity,
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running847 given: Given {
848 comped: r.given_comped_micros.unwrap_or(0),
849 free: r.given_free_micros.unwrap_or(0),
850 trial: r.given_trial_micros.unwrap_or(0),
851 pool: r.given_pool_micros.unwrap_or(0),
Merge branch 'worktree-agent-a633ac0f7f66d419d'852 discount: r.given_discount_micros.unwrap_or(0),
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging853 credit_promotional: r.given_credit_promotional_micros.unwrap_or(0),
854 credit_goodwill: r.given_credit_goodwill_micros.unwrap_or(0),
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running855 },
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily856 }
857 }
858}
859
860impl Billing {
861 /// The day's work: read Cloudflare's bill and g1t's own counts,
862 /// reconcile, look for drift, measure unit costs, apply prices whose
863 /// day has come, and raise or clear alerts.
864 pub(crate) async fn costs_daily(&self, env: &Env, keeper: &crate::keeper::Keeper) -> Result<CostsRun> {
865 let mut run = CostsRun::default();
866 let (since, until) = match self.read_cloudflare(keeper, &mut run.problems).await? {
867 Some((since, until, lines)) => {
868 run.lines = lines;
869 (since, until)
870 }
871 // Without the bill, still reconcile what g1t knows itself, over
872 // the same days the bill would be read for.
873 None => {
874 #[derive(Deserialize)]
875 struct Last {
876 day: Option<String>,
877 }
878 let last = self.db.prepare("SELECT MAX(day) AS day FROM margin_days").first::<Last>(None).await?.and_then(|l| l.day);
879 costs::window(last.as_deref(), now_ms())
880 }
881 };
Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing882 // Not a problem for the run: the last read, or the estimate, stays.
883 if keeper.can_read_bill()
884 && let Err(error) = self.read_subscriptions(keeper).await
885 {
886 worker::console_error!("Cloudflare's subscriptions were not read: {error}");
887 }
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily888 if let Err(error) = self.count_own(&since, &until).await {
889 run.problems.push(format!("g1t's own counts could not be read: {error}"));
890 }
891 self.snapshot_pending(&until).await?;
Models' margin read -14%: usage nothing paid for is valued at price, not $0892 // Reconciled over the whole window sudo shows, not only the days the
893 // bill was read for: it reads only what is already kept, so a change
894 // in how a day is valued reaches every day shown at the next run.
895 let window = day_before(&until, costs::BACKFILL_DAYS - 1);
896 let reconcile_from = if window < since { window } else { since.clone() };
897 run.days = self.reconcile_range(&reconcile_from, &until).await?;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily898 let drift = self.find_drift(&until).await?;
899 run.proposals = self.measure_units(&until).await?;
900 self.apply_due_versions().await?;
901 run.alerts = self.raise_alerts(env, &until, &drift).await?;
902 if let Some(identity) = &self.identity
903 && let Err(error) = self.tell_owners_of_rises(identity).await
904 {
905 run.problems.push(format!("owners could not be told of a price rise: {error}"));
906 }
907 for problem in &run.problems {
908 worker::console_warn!("costs: {problem}");
909 }
910 Ok(run)
911 }
912
913 /// What each month-end source had come to by the end of `day`.
914 async fn snapshot_pending(&self, day: &str) -> Result<()> {
915 self.db
916 .prepare(
917 "INSERT INTO pending_days (day, workspace, source, cost_micros, charge_micros)
918 SELECT ?1, workspace, source, COALESCE(cost_micros, 0), COALESCE(charge_micros, 0) FROM pending_usage WHERE month = ?2
919 ON CONFLICT (day, workspace, source) DO UPDATE SET cost_micros = excluded.cost_micros, charge_micros = excluded.charge_micros",
920 )
921 .bind(&[day.into(), day[..7].into()])?
922 .run()
923 .await?;
924 Ok(())
925 }
926
927 /// What customers were charged on the days, by workspace and key.
928 async fn usage_rows(&self, since: &str, until: &str) -> Result<Vec<UsageRow>> {
929 #[derive(Deserialize)]
930 struct Row {
931 day: String,
932 workspace: String,
933 key: String,
934 internal: i64,
935 own_provider: i64,
936 cash: Option<i64>,
937 drawn: Option<i64>,
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running938 trial: Option<i64>,
939 oss: Option<i64>,
940 covered: Option<i64>,
Merge branch 'worktree-agent-a633ac0f7f66d419d'941 discount: Option<i64>,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily942 cost: Option<i64>,
943 }
944 let charged_here = crate::storage::CHARGED_HERE.iter().map(|s| format!("'{s}'")).collect::<Vec<_>>().join(", ");
945 let end = format!("{until}T23:59:59.999Z");
946 let rows = self
947 .db
948 .prepare(format!(
949 "SELECT substr(created_at, 1, 10) AS day, workspace,
950 CASE WHEN task = 'deployments' AND reference LIKE 'deploy/%' THEN 'builds' ELSE COALESCE(task, 'other') END AS key,
951 CASE WHEN workspace IN ({internal}) THEN 1 ELSE 0 END AS internal,
952 CASE WHEN billed_to = 'workspace' THEN 1 ELSE 0 END AS own_provider,
953 -SUM(amount_micros) AS cash,
954 SUM(COALESCE(credit_micros, 0) + COALESCE(trial_micros, 0) + COALESCE(oss_micros, 0) + COALESCE(given_micros, 0)) AS drawn,
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running955 SUM(COALESCE(trial_micros, 0)) AS trial,
956 SUM(COALESCE(oss_micros, 0)) AS oss,
957 SUM(COALESCE(given_micros, 0)) AS covered,
Merge branch 'worktree-agent-a633ac0f7f66d419d'958 SUM(COALESCE(discount_micros, 0)) AS discount,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily959 SUM(COALESCE(cost_micros, 0)) AS cost
960 FROM ledger
961 WHERE kind = 'usage' AND created_at >= ?1 AND created_at <= ?2 AND COALESCE(task, '') NOT IN ({charged_here})
962 GROUP BY 1, 2, 3, 4, 5",
963 internal = crate::sales::INTERNAL_SQL
964 ))
965 .bind(&[since.into(), end.as_str().into()])?
966 .all()
967 .await?
968 .results::<Row>()?;
Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it969 let mut internal = BTreeSet::new();
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily970 let mut out: Vec<UsageRow> = rows
971 .into_iter()
972 .map(|r| {
973 // A workspace's own model provider was paid there: no cost
974 // to g1t. g1t's own workspaces are valued at price.
975 let cost = if r.own_provider == 1 { 0 } else { r.cost.unwrap_or(0) };
976 let cash = r.cash.unwrap_or(0);
Merge branch 'worktree-agent-a633ac0f7f66d419d'977 // A discount took its part below cost plus the margin: it is
978 // valued at price and that part counted as given, so a
979 // discounted sale never reads as margin lost.
980 let discount = r.discount.unwrap_or(0).max(0);
981 let paid = cash + r.drawn.unwrap_or(0) + discount;
Models' margin read -14%: usage nothing paid for is valued at price, not $0982 let value = usage_value(r.internal == 1, cost, paid, self.margin_percent);
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running983 let given = if r.internal == 1 {
984 Given { comped: value, ..Given::default() }
985 } else if paid == 0 && cost > 0 {
986 Given { free: value, ..Given::default() }
987 } else {
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging988 Given { free: r.covered.unwrap_or(0), trial: r.trial.unwrap_or(0), pool: r.oss.unwrap_or(0), discount, ..Given::default() }
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running989 };
Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it990 if r.internal == 1 {
991 internal.insert(r.workspace.clone());
992 }
993 UsageRow { day: r.day, workspace: r.workspace, key: r.key, value, cash, cost, given }
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily994 })
995 .collect();
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging996 // Credits from g1t: what promotional and goodwill credit paid for
997 // is given, not money in; a refund gives money back on its day.
998 let (draws, refunds) = self.credit_effects(since, until).await?;
999 apply_credits(&mut out, &draws, &refunds);
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1000 // Month-end sources, from their daily snapshots.
1001 #[derive(Deserialize)]
1002 struct Snap {
1003 day: String,
1004 workspace: String,
1005 source: String,
1006 cost_micros: i64,
1007 charge_micros: i64,
1008 }
1009 let snaps = self
1010 .db
1011 .prepare("SELECT day, workspace, source, cost_micros, charge_micros FROM pending_days WHERE day >= ?1 AND day <= ?2")
1012 .bind(&[day_before(since, 1).into(), until.into()])?
1013 .all()
1014 .await?
1015 .results::<Snap>()?
1016 .into_iter()
1017 .filter(|s| crate::storage::CHARGED_HERE.contains(&s.source.as_str()) || s.source == "domains")
1018 .map(|s| (s.day, s.workspace, s.source, s.cost_micros, s.charge_micros))
1019 .collect::<Vec<_>>();
Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it1020 out.extend(pending_deltas(&snaps).into_iter().filter(|u| u.day.as_str() >= since).map(|mut u| {
1021 if internal.contains(&u.workspace) {
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running1022 u.given = Given { comped: u.value, ..Given::default() };
Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it1023 }
1024 u
1025 }));
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1026 // The plan's price, spread over the 30 days it pays for, so a month's
1027 // payment does not read as one very good day and 29 bad ones.
1028 #[derive(Deserialize)]
1029 struct Plan {
1030 day: String,
1031 workspace: String,
1032 micros: Option<i64>,
1033 }
1034 let plans = self
1035 .db
1036 .prepare(
1037 "SELECT substr(paid_at, 1, 10) AS day, workspace, SUM(amount_micros) AS micros FROM plan_payments
1038 WHERE paid_at >= ?1 AND paid_at <= ?2 GROUP BY 1, 2",
1039 )
1040 .bind(&[day_before(since, PLAN_DAYS - 1).into(), end.as_str().into()])?
1041 .all()
1042 .await?
1043 .results::<Plan>()?;
1044 for p in plans {
1045 for (day, micros) in spread(&p.day, p.micros.unwrap_or(0), PLAN_DAYS) {
1046 if day.as_str() >= since && day.as_str() <= until {
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running1047 out.push(UsageRow { day, workspace: p.workspace.clone(), key: "plan".into(), value: micros, cash: micros, cost: 0, given: Given::default() });
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1048 }
1049 }
1050 }
1051 Ok(out)
1052 }
1053
1054 /// Reconciles the days and writes `margin_days` and `workspace_costs`.
1055 async fn reconcile_range(&self, since: &str, until: &str) -> Result<u32> {
1056 let rules = self.rules().await?;
1057 #[derive(Deserialize)]
1058 struct Map {
1059 key: String,
1060 bucket: String,
1061 }
1062 let revenue_map: BTreeMap<String, String> = self
1063 .db
1064 .prepare("SELECT key, bucket FROM revenue_map")
1065 .all()
1066 .await?
1067 .results::<Map>()?
1068 .into_iter()
1069 .map(|m| (m.key, m.bucket))
1070 .collect();
1071 let lines = self
1072 .db
1073 .prepare("SELECT day, source, product, meter, quantity, cost_usd FROM cost_lines WHERE day >= ?1 AND day <= ?2")
1074 .bind(&[since.into(), until.into()])?
1075 .all()
1076 .await?
1077 .results::<LineRow>()?;
1078 let own = self
1079 .db
1080 .prepare("SELECT day, meter, workspace, quantity FROM own_counts WHERE day >= ?1 AND day <= ?2")
1081 .bind(&[since.into(), until.into()])?
1082 .all()
1083 .await?
1084 .results::<OwnRow>()?;
1085 let usage = self.usage_rows(since, until).await?;
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running1086 #[derive(Deserialize)]
1087 struct Internal {
1088 workspace: String,
1089 }
1090 let internal: BTreeSet<String> = self
1091 .db
1092 .prepare(format!("WITH i(workspace) AS ({}) SELECT DISTINCT workspace FROM i", crate::sales::INTERNAL_SQL))
1093 .all()
1094 .await?
1095 .results::<Internal>()?
1096 .into_iter()
1097 .map(|i| i.workspace)
1098 .collect();
1099 let (days, workspaces) = fold(&rules, &revenue_map, &lines, &own, &usage, &internal);
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1100 let now = rfc3339(now_ms());
1101 self.db
1102 .batch(vec![
1103 self.db.prepare("DELETE FROM margin_days WHERE day >= ?1 AND day <= ?2").bind(&[since.into(), until.into()])?,
1104 self.db.prepare("DELETE FROM workspace_costs WHERE day >= ?1 AND day <= ?2").bind(&[since.into(), until.into()])?,
1105 ])
1106 .await?;
1107 for chunk in days.chunks(50) {
1108 let mut statements = Vec::with_capacity(chunk.len());
1109 for d in chunk {
1110 statements.push(
1111 self.db
1112 .prepare(
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging1113 "INSERT OR REPLACE INTO margin_days (day, bucket, cf_cost_micros, own_cost_micros, value_micros, cash_micros, cf_quantity, own_quantity, given_micros, given_comped_micros, given_free_micros, given_trial_micros, given_pool_micros, given_discount_micros, given_credit_promotional_micros, given_credit_goodwill_micros, computed_at)
1114 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1115 )
1116 .bind(&[
1117 d.day.as_str().into(),
1118 d.bucket.as_str().into(),
1119 (d.cf_cost_micros as f64).into(),
1120 (d.own_cost_micros as f64).into(),
1121 (d.value_micros as f64).into(),
1122 (d.cash_micros as f64).into(),
1123 d.cf_quantity.into(),
1124 d.own_quantity.into(),
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running1125 (d.given.total() as f64).into(),
1126 (d.given.comped as f64).into(),
1127 (d.given.free as f64).into(),
1128 (d.given.trial as f64).into(),
1129 (d.given.pool as f64).into(),
Merge branch 'worktree-agent-a633ac0f7f66d419d'1130 (d.given.discount as f64).into(),
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging1131 (d.given.credit_promotional as f64).into(),
1132 (d.given.credit_goodwill as f64).into(),
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1133 now.as_str().into(),
1134 ])?,
1135 );
1136 }
1137 self.db.batch(statements).await?;
1138 }
1139 for chunk in workspaces.chunks(50) {
1140 let mut statements = Vec::with_capacity(chunk.len());
1141 for w in chunk {
1142 statements.push(
1143 self.db
Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it1144 .prepare("INSERT OR REPLACE INTO workspace_costs (day, workspace, bucket, cost_micros, revenue_micros, value_micros, given_micros) VALUES (?, ?, ?, ?, ?, ?, ?)")
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1145 .bind(&[
1146 w.day.as_str().into(),
1147 w.workspace.as_str().into(),
1148 w.bucket.as_str().into(),
1149 (w.cost as f64).into(),
1150 (w.revenue as f64).into(),
Margin alerts measure what is sold, and say dollars when a percentage would mislead1151 (w.value as f64).into(),
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running1152 (w.given.total() as f64).into(),
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1153 ])?,
1154 );
1155 }
1156 self.db.batch(statements).await?;
1157 }
1158 Ok(costs::days_between(since, until).len() as u32)
1159 }
1160
1161 async fn margin_days(&self, since: &str, until: &str) -> Result<Vec<ProductDay>> {
1162 Ok(self
1163 .db
1164 .prepare("SELECT * FROM margin_days WHERE day >= ?1 AND day <= ?2 ORDER BY day, bucket")
1165 .bind(&[since.into(), until.into()])?
1166 .all()
1167 .await?
1168 .results::<MarginRow>()?
1169 .into_iter()
1170 .map(ProductDay::from)
1171 .collect())
1172 }
1173
Merge branch 'worktree-agent-a633ac0f7f66d419d'1174 /// What AI Gateway's lines over the days, and the runs settled in them,
1175 /// say about whether its cost is what the providers bill.
1176 async fn gateway_caveats(&self, since: &str, until: &str) -> Result<costs::GatewayCaveats> {
1177 #[derive(Deserialize)]
1178 struct Line {
1179 meter: String,
1180 quantity: f64,
1181 cost_usd: f64,
1182 }
1183 let lines: Vec<(String, f64, f64)> = self
1184 .db
1185 .prepare("SELECT meter, quantity, cost_usd FROM cost_lines WHERE source = ?1 AND day >= ?2 AND day <= ?3")
1186 .bind(&[costs::SOURCE_GATEWAY.into(), since.into(), until.into()])?
1187 .all()
1188 .await?
1189 .results::<Line>()?
1190 .into_iter()
1191 .map(|l| (l.meter, l.quantity, l.cost_usd))
1192 .collect();
1193 let mut caveats = costs::gateway_caveats(&lines);
1194 #[derive(Deserialize)]
1195 struct Short {
1196 n: Option<f64>,
1197 }
1198 caveats.short_runs = self
1199 .db
1200 .prepare("SELECT COUNT(*) AS n FROM runs WHERE gateway_note IS NOT NULL AND settled_at >= ?1 AND settled_at <= ?2")
1201 .bind(&[since.into(), format!("{until}T23:59:59.999Z").into()])?
1202 .first::<Short>(None)
1203 .await?
1204 .and_then(|s| s.n)
1205 .unwrap_or(0.0) as u32;
1206 Ok(caveats)
1207 }
1208
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1209 /// Drift over the last week, written to `cost_drift` (replacing the
1210 /// last run's), with unmapped Cloudflare meters as leaks.
1211 async fn find_drift(&self, until: &str) -> Result<Vec<(Drift, String)>> {
1212 let since = day_before(until, DRIFT_DAYS - 1);
1213 let settings = self.cost_settings().await?;
1214 let rules = self.rules().await?;
1215 let days = self.margin_days(&since, until).await?;
1216 let mut by: BTreeMap<String, Vec<ProductDay>> = BTreeMap::new();
1217 for d in days {
1218 by.entry(d.bucket.clone()).or_default().push(d);
1219 }
Merge branch 'worktree-agent-a633ac0f7f66d419d'1220 let caveats = self.gateway_caveats(&since, until).await?;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1221 let mut found = Vec::new();
Merge branch 'worktree-agent-a633ac0f7f66d419d'1222 if let Some(drift) = unpriced_drift(&caveats) {
1223 found.push(drift);
1224 }
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1225 for (bucket, days) in &by {
1226 let bucket_rules: Vec<&Rule> = rules.iter().filter(|r| &r.bucket == bucket).collect();
1227 let threshold = bucket_rules.iter().map(|r| r.drift_percent).fold(f64::INFINITY, f64::min);
1228 let threshold = if threshold.is_finite() { threshold } else { 10.0 };
1229 let counted = bucket_rules.iter().any(|r| r.own_meter.is_some());
1230 for drift in drifts(bucket, days, threshold, counted, settings.min_daily_cost_micros) {
1231 let title = costs::bucket_title(bucket);
1232 let detail = match drift.kind {
Merge branch 'worktree-agent-a633ac0f7f66d419d'1233 DriftKind::Cost if NOT_CLOUDFLARE.contains(&bucket.as_str()) => models_detail(&drift, &caveats),
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1234 DriftKind::Count => format!(
One operation mapping, owned by repos; billing reads it instead of keeping its own1235 "{title}: g1t counted {}, Cloudflare {} over the last {DRIFT_DAYS} days ({:+.1}%). Customers are charged for what g1t counts; check what Cloudflare counts as a unit and change the repos service's operation_mapping (set_operation_mapping).",
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1236 crate::features::thousands(drift.ours.max(0.0).round() as u64),
1237 crate::features::thousands(drift.cloudflare.max(0.0).round() as u64),
1238 drift.delta_percent.unwrap_or(0.0)
1239 ),
1240 DriftKind::Cost => format!(
1241 "{title}: Cloudflare charged {} over the last {DRIFT_DAYS} days; the price book's cost of the same usage is {} ({:+.1}%). A price may be stale: see the proposals.",
1242 dollars(drift.cloudflare as i64),
1243 dollars(drift.ours as i64),
1244 drift.delta_percent.unwrap_or(0.0)
1245 ),
1246 DriftKind::Leak if bucket == UNMAPPED => {
1247 format!("Cloudflare charged {} for meters no mapping claims. Map them on Costs & margin.", dollars(drift.cloudflare as i64))
1248 }
Merge branch 'worktree-agent-a633ac0f7f66d419d'1249 DriftKind::Leak if NOT_CLOUDFLARE.contains(&bucket.as_str()) => format!(
1250 "{title}: AI Gateway priced g1t's own provider traffic at {} over the last {DRIFT_DAYS} days and the ledger has no model charge for it, not even a comped or free one: model calls with no billing run behind them (a run started without a ticket, or something else using g1t's gateway).",
1251 dollars(drift.cloudflare as i64)
1252 ),
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1253 DriftKind::Leak => format!(
1254 "{title}: Cloudflare charged {} over the last {DRIFT_DAYS} days and customers were charged nothing for it.",
1255 dollars(drift.cloudflare as i64)
1256 ),
Merge branch 'worktree-agent-a633ac0f7f66d419d'1257 // Raised from the gateway's lines, not per bucket.
1258 DriftKind::Unpriced => unpriced_detail(&caveats),
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1259 };
1260 found.push((drift, detail));
1261 }
1262 }
1263 let now = rfc3339(now_ms());
1264 let mut statements = vec![self.db.prepare("DELETE FROM cost_drift")];
1265 for (drift, detail) in &found {
1266 statements.push(
1267 self.db
1268 .prepare("INSERT OR REPLACE INTO cost_drift (bucket, kind, ours, cloudflare, delta_percent, detail, found_at) VALUES (?, ?, ?, ?, ?, ?, ?)")
1269 .bind(&[
1270 drift.bucket.as_str().into(),
1271 drift.kind.as_str().into(),
1272 drift.ours.into(),
1273 drift.cloudflare.into(),
1274 drift.delta_percent.map_or(JsValue::NULL, JsValue::from),
1275 detail.as_str().into(),
1276 now.as_str().into(),
1277 ])?,
1278 );
1279 }
1280 self.db.batch(statements).await?;
1281 Ok(found)
1282 }
1283
1284 /// Unit costs from the bill for mappings that scale to g1t's own count
1285 /// (git operations), proposed to the price book.
1286 async fn measure_units(&self, until: &str) -> Result<u32> {
1287 #[derive(Deserialize)]
1288 struct Scaled {
1289 product: String,
1290 meter: String,
1291 price_meter: String,
1292 own_meter: String,
1293 unit: Option<String>,
1294 }
1295 let scaled = self
1296 .db
1297 .prepare(
1298 "SELECT m.product, m.meter, m.price_meter, m.own_meter, p.unit FROM cost_map m LEFT JOIN prices p ON p.meter = m.price_meter
1299 WHERE m.scale_to_own = 1 AND m.price_meter IS NOT NULL AND m.own_meter IS NOT NULL",
1300 )
1301 .all()
1302 .await?
1303 .results::<Scaled>()?;
1304 let since = day_before(until, MEASURE_DAYS - 1);
1305 let rules = self.rules().await?;
1306 let mut proposed = 0;
1307 for s in scaled {
1308 #[derive(Deserialize)]
1309 struct Day {
1310 product: String,
1311 meter: String,
1312 quantity: f64,
1313 cost_usd: f64,
1314 }
1315 let lines = self
1316 .db
1317 .prepare("SELECT product, meter, quantity, cost_usd FROM cost_lines WHERE source = ?1 AND product = ?2 AND day >= ?3 AND day <= ?4")
1318 .bind(&[SOURCE_BILLABLE.into(), s.product.as_str().into(), since.as_str().into(), until.into()])?
1319 .all()
1320 .await?
1321 .results::<Day>()?;
1322 // Only the lines this very mapping claims.
1323 let mine: Vec<(f64, f64)> = lines
1324 .iter()
1325 .filter(|l| costs::classify(&rules, &l.product, &l.meter).is_some_and(|r| r.product == s.product && r.meter == s.meter))
1326 .map(|l| (l.quantity, l.cost_usd))
1327 .collect();
1328 let Some(rate) = billed_rate(&mine) else { continue };
1329 let cf_units: f64 = mine.iter().map(|(q, _)| q).sum();
1330 #[derive(Deserialize)]
1331 struct Own {
1332 total: Option<f64>,
1333 }
1334 let own_units = self
1335 .db
1336 .prepare("SELECT SUM(quantity) AS total FROM own_counts WHERE meter = ?1 AND day >= ?2 AND day <= ?3")
1337 .bind(&[s.own_meter.as_str().into(), since.as_str().into(), until.into()])?
1338 .first::<Own>(None)
1339 .await?
1340 .and_then(|o| o.total)
1341 .unwrap_or(0.0);
1342 let Some(per_unit) = derived_unit_cost(rate, cf_units, own_units) else { continue };
1343 let size = unit_size(s.unit.as_deref().unwrap_or("1"));
1344 let measured = per_unit * size * 1_000_000.0;
1345 let reason = format!(
1346 "Cloudflare billed ${:.4} per 1,000 of its units and counted {:.2} of them for each one g1t counted over the last {MEASURE_DAYS} days ({} against {})",
1347 rate * 1000.0,
1348 cf_units / own_units,
1349 crate::features::thousands(cf_units.round() as u64),
1350 crate::features::thousands(own_units.round() as u64)
1351 );
1352 if self.propose(&s.price_meter, measured, &reason, "reconciler").await?.is_some() {
1353 proposed += 1;
1354 }
1355 }
1356 Ok(proposed)
1357 }
1358
1359 /// Opens, updates and closes margin alerts, and emails staff about new
1360 /// ones (and open ones each week).
1361 async fn raise_alerts(&self, env: &Env, until: &str, drift: &[(Drift, String)]) -> Result<u32> {
1362 let settings = self.cost_settings().await?;
1363 let since = day_before(until, u64::from(settings.alert_days.max(1)) - 1);
1364 let days = self.margin_days(&since, until).await?;
1365 let mut conditions: Vec<(String, String, String, String)> = Vec::new();
1366 // Each product under the floor.
1367 let mut by: BTreeMap<String, Vec<(String, i64, i64)>> = BTreeMap::new();
1368 let mut all: BTreeMap<String, (i64, i64)> = BTreeMap::new();
1369 for d in &days {
1370 let overall = all.entry(d.day.clone()).or_default();
Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it1371 // What g1t gave away (comped workspaces, free periods, the
1372 // trial and the pools) is a budget it chose to spend, watched on
1373 // its own (budget.rs): not part of whether what is sold pays.
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1374 overall.0 += d.cash_micros;
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running1375 overall.1 += (d.cost() - d.given.total()).max(0);
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1376 if !OVERHEAD.contains(&d.bucket.as_str()) && d.bucket != UNMAPPED {
1377 by.entry(d.bucket.clone()).or_default().push((d.day.clone(), d.value_micros, d.cost()));
1378 }
1379 }
1380 let floor = settings.margin_floor_percent;
1381 let n = settings.alert_days as usize;
1382 for (bucket, series) in &by {
1383 if let Some((from, worst)) = breach(series, floor, n, settings.min_daily_cost_micros) {
1384 conditions.push((
1385 "margin".into(),
1386 bucket.clone(),
1387 format!("{}: margin under {floor:.0}% for {n} days running, as low as {worst:.1}%.", costs::bucket_title(bucket)),
1388 from,
1389 ));
Margin alerts measure what is sold, and say dollars when a percentage would mislead1390 }
1391 }
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1392 let series: Vec<(String, i64, i64)> = all.into_iter().map(|(day, (revenue, cost))| (day, revenue, cost)).collect();
1393 if let Some((from, worst)) = breach(&series, floor, n, settings.min_daily_cost_micros) {
Margin alerts measure what is sold, and say dollars when a percentage would mislead1394 let tail = &series[series.len().saturating_sub(n)..];
1395 let (took, spent) = tail.iter().fold((0i64, 0i64), |(r, c), (_, revenue, cost)| (r + revenue, c + cost));
1396 conditions.push(("overall".into(), "g1t".into(), overall_detail(took, spent, n, floor, worst), from));
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1397 }
1398 for (d, detail) in drift {
1399 let kind = if d.kind == DriftKind::Leak { "leak" } else { "drift" };
1400 conditions.push((kind.into(), format!("{}:{}", d.bucket, d.kind.as_str()), detail.clone(), until.to_owned()));
1401 }
1402 // Workspaces costing more than they pay.
1403 for (workspace, cost, revenue) in self.workspace_anomalies(until, &settings).await? {
1404 conditions.push((
1405 "workspace".into(),
1406 workspace.clone(),
Margin alerts measure what is sold, and say dollars when a percentage would mislead1407 format!(
1408 "{workspace} cost g1t {} on Cloudflare over {ANOMALY_DAYS} days, and its usage was priced at {}: its prices are below cost.",
1409 dollars(cost),
1410 dollars(revenue)
1411 ),
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1412 day_before(until, ANOMALY_DAYS - 1),
1413 ));
1414 }
1415
1416 let open = self
1417 .db
1418 .prepare("SELECT * FROM margin_alerts WHERE resolved_at IS NULL")
1419 .all()
1420 .await?
1421 .results::<AlertRow>()?;
1422 let now = now_ms();
1423 let stamp = rfc3339(now);
1424 let mut to_email: Vec<String> = Vec::new();
1425 let mut kept: BTreeSet<String> = BTreeSet::new();
1426 for (kind, subject, detail, from) in &conditions {
1427 match open.iter().find(|a| &a.kind == kind && &a.subject == subject) {
1428 Some(alert) => {
1429 kept.insert(alert.id.clone());
1430 self.db
1431 .prepare("UPDATE margin_alerts SET detail = ? WHERE id = ?")
1432 .bind(&[detail.as_str().into(), alert.id.as_str().into()])?
1433 .run()
1434 .await?;
1435 let stale = alert
1436 .emailed_at
1437 .as_deref()
1438 .and_then(g1t_contracts::time::parse_rfc3339)
1439 .is_none_or(|at| now.saturating_sub(at) >= REMIND_MS);
1440 if stale && kind != "workspace" {
1441 to_email.push(format!("Still open: {detail}"));
1442 kept.insert(format!("email:{}", alert.id));
1443 }
1444 }
1445 None => {
1446 let id = new_id("mal", now);
1447 self.db
1448 .prepare("INSERT INTO margin_alerts (id, kind, subject, detail, since, opened_at) VALUES (?, ?, ?, ?, ?, ?)")
1449 .bind(&[id.as_str().into(), kind.as_str().into(), subject.as_str().into(), detail.as_str().into(), from.as_str().into(), stamp.as_str().into()])?
1450 .run()
1451 .await?;
1452 kept.insert(id.clone());
Margin alerts measure what is sold, and say dollars when a percentage would mislead1453 // A workspace's is for Reach out, not the inbox.
1454 if kind != "workspace" {
1455 to_email.push(detail.clone());
1456 }
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1457 kept.insert(format!("email:{id}"));
1458 }
1459 }
1460 }
1461 for alert in &open {
1462 if !kept.contains(&alert.id) {
1463 self.db
1464 .prepare("UPDATE margin_alerts SET resolved_at = ? WHERE id = ?")
1465 .bind(&[stamp.as_str().into(), alert.id.as_str().into()])?
1466 .run()
1467 .await?;
1468 }
1469 }
1470 let to = env.var("COSTS_ALERT_EMAIL").map(|v| v.to_string()).unwrap_or_default();
1471 if !to_email.is_empty() && !to.trim().is_empty() {
1472 let subject = format!("[g1t costs] {} margin alert{}", to_email.len(), if to_email.len() == 1 { "" } else { "s" });
1473 match email_staff(env, to.trim(), &subject, &to_email).await {
1474 Ok(()) => {
1475 for marker in kept.iter().filter_map(|k| k.strip_prefix("email:")) {
1476 self.db
1477 .prepare("UPDATE margin_alerts SET emailed_at = ? WHERE id = ?")
1478 .bind(&[stamp.as_str().into(), marker.into()])?
1479 .run()
1480 .await?;
1481 }
1482 }
1483 Err(error) => worker::console_error!("could not email the margin alerts: {error}"),
1484 }
1485 }
1486 Ok(conditions.len() as u32)
1487 }
1488
1489 /// Workspaces costing g1t more than they pay over 30 days, not g1t's own.
Margin alerts measure what is sold, and say dollars when a percentage would mislead1490 /// Each day's cost shared out to comped workspaces.
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1491 async fn workspace_anomalies(&self, until: &str, settings: &CostSettings) -> Result<Vec<(String, i64, i64)>> {
1492 #[derive(Deserialize)]
1493 struct Row {
1494 workspace: String,
1495 cost: Option<i64>,
1496 revenue: Option<i64>,
1497 }
1498 let rows = self
1499 .db
1500 .prepare(format!(
Margin alerts measure what is sold, and say dollars when a percentage would mislead1501 // Against what its usage was priced at, not the cash it
1502 // paid: a trial or a gift paying for usage is not a price
1503 // below cost.
The workspace cost alert compares only days that carry their value, not the days before it was kept1504 // Days from before value_micros was kept have none: only days
1505 // since the first one that does are compared.
Margin alerts measure what is sold, and say dollars when a percentage would mislead1506 "SELECT workspace, SUM(cost_micros) AS cost, SUM(value_micros) AS revenue FROM workspace_costs
The workspace cost alert compares only days that carry their value, not the days before it was kept1507 WHERE day >= ?1 AND day <= ?2 AND workspace NOT IN ({})
1508 AND day >= (SELECT MIN(day) FROM workspace_costs WHERE value_micros > 0)
1509 GROUP BY workspace",
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1510 crate::sales::INTERNAL_SQL
1511 ))
1512 .bind(&[day_before(until, ANOMALY_DAYS - 1).into(), until.into()])?
1513 .all()
1514 .await?
1515 .results::<Row>()?;
1516 let rows: Vec<(String, i64, i64)> = rows.into_iter().map(|r| (r.workspace, r.cost.unwrap_or(0), r.revenue.unwrap_or(0))).collect();
1517 Ok(anomalies(&rows, settings.anomaly_factor, settings.anomaly_floor_micros))
1518 }
1519
1520 /// For Reach out: workspaces with an open cost-over-revenue alert,
1521 /// each with its detail and cost.
1522 pub(crate) async fn costing_more_than_they_pay(&self) -> Result<Vec<(String, String, i64)>> {
1523 let alerts = self
1524 .db
1525 .prepare("SELECT * FROM margin_alerts WHERE resolved_at IS NULL AND kind = 'workspace' ORDER BY opened_at DESC LIMIT 50")
1526 .all()
1527 .await?
1528 .results::<AlertRow>()?;
1529 let mut out = Vec::new();
1530 for alert in alerts {
1531 #[derive(Deserialize)]
1532 struct Cost {
1533 cost: Option<i64>,
1534 }
1535 let cost = self
1536 .db
1537 .prepare("SELECT SUM(cost_micros) AS cost FROM workspace_costs WHERE workspace = ? AND day >= ?")
1538 .bind(&[alert.subject.as_str().into(), alert.since.as_str().into()])?
1539 .first::<Cost>(None)
1540 .await?
1541 .and_then(|c| c.cost)
1542 .unwrap_or(0);
1543 out.push((alert.subject, alert.detail, cost));
1544 }
1545 Ok(out)
1546 }
1547
1548 /// `admin_cost_alerts`: what sudo's banner says.
1549 pub(crate) async fn admin_cost_alerts(&self, _: AdminCostAlertsArgs) -> Result<Vec<MarginAlert>> {
1550 Ok(self
1551 .db
1552 .prepare("SELECT * FROM margin_alerts WHERE resolved_at IS NULL ORDER BY opened_at DESC LIMIT 50")
1553 .all()
1554 .await?
1555 .results::<AlertRow>()?
1556 .into_iter()
1557 .map(MarginAlert::from)
1558 .collect())
1559 }
1560
1561 /// `admin_run_costs`: the daily run, now.
1562 pub(crate) async fn admin_run_costs(&self, env: &Env, a: AdminRunCostsArgs) -> Result<Outcome<CostsRun>> {
1563 let keeper = crate::keeper::Keeper::from_env(env);
1564 let run = self.costs_daily(env, &keeper).await?;
1565 if !a.by.is_empty() {
1566 self.audit(
1567 "costs",
1568 "costs_run",
1569 &format!("{} lines, {} days, {} proposals, {} alerts", run.lines, run.days, run.proposals, run.alerts),
1570 &a.by,
1571 )
1572 .await?;
1573 }
1574 Ok(Outcome::Ok(run))
1575 }
1576
1577 /// `admin_set_cost_mapping`.
1578 pub(crate) async fn admin_set_cost_mapping(&self, a: AdminSetCostMappingArgs) -> Result<Outcome<CostMapping>> {
1579 let product = costs::slug(&a.product);
1580 let meter = if a.meter.trim() == "*" { "*".to_owned() } else { costs::slug(&a.meter) };
1581 if product.is_empty() || meter.is_empty() {
1582 return Ok(Outcome::fail(FailureCode::Invalid, "Name Cloudflare's product and a meter (or * for all of it)."));
1583 }
1584 let now = rfc3339(now_ms());
1585 if a.remove {
1586 self.db
1587 .prepare("DELETE FROM cost_map WHERE product = ? AND meter = ?")
1588 .bind(&[product.as_str().into(), meter.as_str().into()])?
1589 .run()
1590 .await?;
1591 self.audit("costs", "cost_mapping_removed", &format!("{product}/{meter}"), &a.by).await?;
1592 return Ok(Outcome::Ok(CostMapping {
1593 product,
1594 meter,
1595 bucket: String::new(),
1596 price_meter: None,
1597 own_meter: None,
1598 scale_to_own: false,
1599 drift_percent: 0.0,
1600 note: String::new(),
1601 updated_at: now,
1602 updated_by: a.by,
1603 }));
1604 }
1605 let bucket = costs::slug(&a.bucket);
1606 if bucket.is_empty() {
1607 return Ok(Outcome::fail(FailureCode::Invalid, "Say which of g1t's products it is a cost of."));
1608 }
1609 let clean = |v: Option<String>| v.map(|v| v.trim().to_owned()).filter(|v| !v.is_empty());
1610 let (price_meter, own_meter) = (clean(a.price_meter), clean(a.own_meter));
1611 let drift = a.drift_percent.filter(|d| d.is_finite() && *d > 0.0).unwrap_or(10.0);
1612 self.db
1613 .prepare(
1614 "INSERT INTO cost_map (product, meter, bucket, price_meter, own_meter, scale_to_own, drift_percent, note, updated_at, updated_by)
1615 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)
1616 ON CONFLICT (product, meter) DO UPDATE SET bucket = ?3, price_meter = ?4, own_meter = ?5, scale_to_own = ?6,
1617 drift_percent = ?7, note = ?8, updated_at = ?9, updated_by = ?10",
1618 )
1619 .bind(&[
1620 product.as_str().into(),
1621 meter.as_str().into(),
1622 bucket.as_str().into(),
1623 crate::optional(price_meter.as_deref()),
1624 crate::optional(own_meter.as_deref()),
1625 i32::from(a.scale_to_own).into(),
1626 drift.into(),
1627 a.note.trim().into(),
1628 now.as_str().into(),
1629 a.by.as_str().into(),
1630 ])?
1631 .run()
1632 .await?;
1633 self.audit("costs", "cost_mapping", &format!("{product}/{meter} → {bucket}"), &a.by).await?;
1634 Ok(Outcome::Ok(CostMapping {
1635 product,
1636 meter,
1637 bucket,
1638 price_meter,
1639 own_meter,
1640 scale_to_own: a.scale_to_own,
1641 drift_percent: drift,
1642 note: a.note.trim().to_owned(),
1643 updated_at: now,
1644 updated_by: a.by,
1645 }))
1646 }
1647
1648 /// `admin_costs`: the Costs & margin page.
1649 pub(crate) async fn admin_costs(&self, a: AdminCostsArgs, configured: bool) -> Result<CostsReport> {
1650 let until = rfc3339(now_ms())[..10].to_owned();
1651 let span = u64::from(a.days.unwrap_or(30).clamp(7, 90));
1652 let since = day_before(&until, span - 1);
1653 let days = self.margin_days(&since, &until).await?;
1654 let rules = self.rules().await?;
1655
1656 let mut products: BTreeMap<String, ProductMargin> = BTreeMap::new();
1657 let mut overall = OverallMargin::default();
1658 for d in &days {
1659 let p = products.entry(d.bucket.clone()).or_insert_with(|| ProductMargin {
1660 bucket: d.bucket.clone(),
1661 title: costs::bucket_title(&d.bucket),
1662 cost_source: if NOT_CLOUDFLARE.contains(&d.bucket.as_str()) { "ledger" } else { "cloudflare" }.into(),
1663 overhead: OVERHEAD.contains(&d.bucket.as_str()),
1664 ..ProductMargin::default()
1665 });
1666 p.cf_cost_micros += d.cf_cost_micros;
1667 p.own_cost_micros += d.own_cost_micros;
1668 p.value_micros += d.value_micros;
1669 p.cost_micros += d.cost();
1670 overall.cost_micros += d.cost();
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running1671 overall.given_micros += d.given.total();
1672 if NOT_CLOUDFLARE.contains(&d.bucket.as_str()) {
1673 overall.models_cost_micros += d.cost();
1674 } else {
1675 overall.cloudflare_cost_micros += d.cost();
1676 }
1677 overall.given_comped_micros += d.given.comped;
1678 overall.given_free_micros += d.given.free;
1679 overall.given_trial_micros += d.given.trial;
1680 overall.given_pool_micros += d.given.pool;
Merge branch 'worktree-agent-a633ac0f7f66d419d'1681 overall.given_discount_micros += d.given.discount;
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging1682 overall.given_credit_promotional_micros += d.given.credit_promotional;
1683 overall.given_credit_goodwill_micros += d.given.credit_goodwill;
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running1684 let sold = (d.cost() - d.given.total()).max(0);
1685 if OVERHEAD.contains(&d.bucket.as_str()) {
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1686 overall.plans_micros += d.cash_micros;
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running1687 overall.running_cost_micros += sold;
1688 } else if d.bucket == UNMAPPED {
1689 overall.usage_micros += d.cash_micros;
1690 overall.unmapped_cost_micros += sold;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1691 } else {
1692 overall.usage_micros += d.cash_micros;
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running1693 overall.usage_cost_micros += sold;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1694 }
1695 }
1696 for p in products.values_mut() {
1697 p.margin_micros = p.value_micros - p.cost_micros;
1698 p.margin_percent = margin_percent(p.value_micros, p.cost_micros);
1699 }
1700 let revenue = overall.usage_micros + overall.plans_micros;
1701 overall.margin_micros = revenue - overall.cost_micros;
1702 overall.margin_percent = margin_percent(revenue, overall.cost_micros);
Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it1703 let sold = (overall.cost_micros - overall.given_micros).max(0);
1704 overall.sold_margin_micros = revenue - sold;
1705 overall.sold_margin_percent = margin_percent(revenue, sold);
Costs: the plan's included usage counts as paid for the usage it covered, out of what plans leave for running g1t; the run button shows it is running with CSS alone (sudo ships no JavaScript)1706 // The plan's included usage was paid for by the plan's price: it is
1707 // money in for the usage it covered, and out of what the plans
1708 // leave for running g1t.
1709 #[derive(Deserialize)]
1710 struct Included {
1711 micros: Option<i64>,
1712 }
1713 overall.included_micros = self
1714 .db
1715 .prepare(format!(
1716 "SELECT SUM(COALESCE(credit_micros, 0)) AS micros FROM ledger
1717 WHERE kind = 'usage' AND created_at >= ?1 AND created_at <= ?2 AND workspace NOT IN ({})",
1718 crate::sales::INTERNAL_SQL
1719 ))
1720 .bind(&[since.as_str().into(), format!("{until}T23:59:59.999Z").into()])?
1721 .first::<Included>(None)
1722 .await?
1723 .and_then(|r| r.micros)
1724 .unwrap_or(0);
1725 let usage_in = overall.usage_micros + overall.included_micros;
1726 overall.usage_margin_micros = usage_in - overall.usage_cost_micros;
1727 overall.usage_margin_percent = margin_percent(usage_in, overall.usage_cost_micros);
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging1728 // Credits from g1t over the range: given, spent, and refunds' money
1729 // given back.
1730 overall.credits_given_micros = self
1731 .db
1732 .prepare("SELECT SUM(amount_micros) AS micros FROM credit_grants WHERE created_at >= ?1 AND created_at <= ?2")
1733 .bind(&[since.as_str().into(), format!("{until}T23:59:59.999Z").into()])?
1734 .first::<Included>(None)
1735 .await?
1736 .and_then(|r| r.micros)
1737 .unwrap_or(0);
1738 let (draws, refunds) = self.credit_effects(&since, &until).await?;
1739 overall.credits_used_micros = draws.iter().map(|(_, d)| d.micros).sum();
1740 overall.credits_refunded_micros = refunds.iter().map(|r| r.micros).sum();
Merge Stripe Tax, the card fee on card payments, and one free workspace per person1741 // Tax and card fees came in with payments but are neither cash nor
1742 // revenue: balances and plan payments are credited without them
1743 // (tax.rs), so cash above never holds them. Shown apart.
1744 (overall.tax_collected_micros, overall.card_fees_micros) = self.extras_between(&since, &until).await?;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1745 let mut products: Vec<ProductMargin> = products.into_values().collect();
1746 products.sort_by_key(|p| std::cmp::Reverse(p.cost_micros.max(p.value_micros)));
1747
1748 #[derive(Deserialize)]
1749 struct DriftRow {
1750 bucket: String,
1751 kind: String,
1752 ours: f64,
1753 cloudflare: f64,
1754 delta_percent: Option<f64>,
1755 detail: String,
1756 found_at: String,
1757 }
1758 let drift = self
1759 .db
1760 .prepare("SELECT * FROM cost_drift ORDER BY kind, bucket")
1761 .all()
1762 .await?
1763 .results::<DriftRow>()?
1764 .into_iter()
1765 .map(|r| CostDrift {
1766 title: costs::bucket_title(&r.bucket),
1767 bucket: r.bucket,
1768 kind: r.kind,
1769 ours: r.ours,
1770 cloudflare: r.cloudflare,
1771 delta_percent: r.delta_percent,
1772 detail: r.detail,
1773 found_at: r.found_at,
1774 })
1775 .collect();
1776
1777 #[derive(Deserialize)]
1778 struct Top {
1779 workspace: String,
1780 cost: Option<i64>,
1781 revenue: Option<i64>,
Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it1782 given: Option<i64>,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1783 internal: i64,
1784 }
1785 let top_workspaces = self
1786 .db
1787 .prepare(format!(
Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it1788 "SELECT workspace, SUM(cost_micros) AS cost, SUM(revenue_micros) AS revenue, SUM(given_micros) AS given,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1789 CASE WHEN workspace IN ({}) THEN 1 ELSE 0 END AS internal
1790 FROM workspace_costs WHERE day >= ?1 AND day <= ?2 GROUP BY workspace ORDER BY cost DESC LIMIT 15",
1791 crate::sales::INTERNAL_SQL
1792 ))
1793 .bind(&[since.as_str().into(), until.as_str().into()])?
1794 .all()
1795 .await?
1796 .results::<Top>()?
1797 .into_iter()
Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it1798 .map(|t| WorkspaceCost { workspace: t.workspace, cost_micros: t.cost.unwrap_or(0), revenue_micros: t.revenue.unwrap_or(0), given_micros: t.given.unwrap_or(0), internal: t.internal == 1 })
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1799 .collect();
1800
1801 #[derive(Deserialize)]
1802 struct Summary {
1803 source: String,
1804 product: String,
1805 meter: String,
1806 raw_name: String,
1807 unit: String,
1808 quantity: f64,
1809 cost_usd: f64,
1810 }
1811 let lines = self
1812 .db
1813 .prepare(
1814 "SELECT source, product, meter, MAX(raw_name) AS raw_name, MAX(unit) AS unit, SUM(quantity) AS quantity, SUM(cost_usd) AS cost_usd
1815 FROM cost_lines WHERE day >= ?1 AND day <= ?2 GROUP BY source, product, meter ORDER BY cost_usd DESC, product, meter LIMIT 200",
1816 )
1817 .bind(&[since.as_str().into(), until.as_str().into()])?
1818 .all()
1819 .await?
1820 .results::<Summary>()?
1821 .into_iter()
1822 .map(|l| CostLineSummary {
1823 bucket: costs::classify(&rules, &l.product, &l.meter).map(|r| r.bucket.clone()),
1824 product: l.product,
1825 meter: l.meter,
1826 raw_name: l.raw_name,
1827 unit: l.unit,
1828 source: l.source,
1829 quantity: l.quantity,
1830 cost_micros: micros(l.cost_usd),
1831 })
1832 .collect();
1833
1834 #[derive(Deserialize)]
1835 struct MapRow {
1836 product: String,
1837 meter: String,
1838 bucket: String,
1839 price_meter: Option<String>,
1840 own_meter: Option<String>,
1841 scale_to_own: i64,
1842 drift_percent: f64,
1843 note: String,
1844 updated_at: String,
1845 updated_by: String,
1846 }
1847 let mappings = self
1848 .db
1849 .prepare("SELECT * FROM cost_map ORDER BY product, meter")
1850 .all()
1851 .await?
1852 .results::<MapRow>()?
1853 .into_iter()
1854 .map(|m| CostMapping {
1855 product: m.product,
1856 meter: m.meter,
1857 bucket: m.bucket,
1858 price_meter: m.price_meter,
1859 own_meter: m.own_meter,
1860 scale_to_own: m.scale_to_own == 1,
1861 drift_percent: m.drift_percent,
1862 note: m.note,
1863 updated_at: m.updated_at,
1864 updated_by: m.updated_by,
1865 })
1866 .collect();
1867
1868 #[derive(Deserialize)]
1869 struct Fetched {
1870 at: Option<String>,
1871 }
1872 let fetched_at = self.db.prepare("SELECT MAX(fetched_at) AS at FROM cost_lines").first::<Fetched>(None).await?.and_then(|f| f.at);
1873
1874 Ok(CostsReport {
1875 configured,
1876 fetched_at,
1877 days: days
1878 .iter()
1879 .map(|d| CostDay {
1880 day: d.day.clone(),
1881 bucket: d.bucket.clone(),
1882 cf_cost_micros: d.cf_cost_micros,
1883 own_cost_micros: d.own_cost_micros,
1884 value_micros: d.value_micros,
1885 cash_micros: d.cash_micros,
1886 })
1887 .collect(),
1888 since,
1889 until,
1890 products,
1891 overall,
1892 drift,
1893 alerts: self.admin_cost_alerts(AdminCostAlertsArgs {}).await?,
1894 proposals: self.proposals().await?,
1895 versions: self.versions().await?,
1896 top_workspaces,
1897 lines,
1898 mappings,
1899 settings: self.cost_settings().await?,
Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays1900 caps: self.spend_caps().await?,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1901 })
1902 }
1903}
1904
1905#[cfg(test)]
1906mod tests {
1907 use super::*;
1908
Margin alerts measure what is sold, and say dollars when a percentage would mislead1909 #[test]
Models' margin read -14%: usage nothing paid for is valued at price, not $01910 fn usage_nothing_paid_for_is_valued_at_price_and_paid_usage_at_what_was_paid() {
1911 // A free period: charged nothing, drawn from nothing.
1912 assert_eq!(usage_value(false, 1_000_000, 0, 20), 1_200_000);
1913 // Charged, or drawn from a trial: what was paid.
1914 assert_eq!(usage_value(false, 1_000_000, 1_200_000, 20), 1_200_000);
1915 assert_eq!(usage_value(false, 1_000_000, 900_000, 20), 900_000);
1916 // g1t's own: at price.
1917 assert_eq!(usage_value(true, 1_000_000, 0, 20), 1_200_000);
1918 // No cost, nothing paid: nothing.
1919 assert_eq!(usage_value(false, 0, 0, 20), 0);
1920 }
1921
1922 #[test]
Margin alerts measure what is sold, and say dollars when a percentage would mislead1923 fn the_overall_alert_says_dollars_while_little_comes_in() {
1924 let small = overall_detail(90_000, 7_500_000, 3, 10.0, -8239.7);
1925 assert!(small.contains("took in $0.09 against $7.50"), "{small}");
1926 assert!(!small.contains('%'), "{small}");
1927 let real = overall_detail(30_000_000, 40_000_000, 3, 10.0, -33.3);
1928 assert!(real.contains("as low as -33.3%"), "{real}");
1929 }
1930
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1931 fn rule(product: &str, meter: &str, bucket: &str, own: Option<&str>) -> Rule {
1932 Rule { product: product.into(), meter: meter.into(), bucket: bucket.into(), price_meter: None, own_meter: own.map(Into::into), drift_percent: 10.0 }
1933 }
1934
1935 fn rules() -> Vec<Rule> {
1936 vec![
1937 rule("containers", "*", "sandboxes", None),
1938 rule("workers", "*", "platform", None),
1939 rule("artifacts", "*", "git", Some("git_operations")),
1940 rule("artifacts", "events_", "git", Some("git_operations")),
1941 ]
1942 }
1943
1944 fn revenue_map() -> BTreeMap<String, String> {
1945 [("sandbox", "sandboxes"), ("git", "git"), ("plan", "platform")].iter().map(|(k, v)| (k.to_string(), v.to_string())).collect()
1946 }
1947
1948 fn line(day: &str, source: &str, product: &str, meter: &str, quantity: f64, cost: f64) -> LineRow {
1949 LineRow { day: day.into(), source: source.into(), product: product.into(), meter: meter.into(), quantity, cost_usd: cost }
1950 }
1951
1952 fn usage(day: &str, workspace: &str, key: &str, value: i64, cash: i64, cost: i64) -> UsageRow {
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running1953 UsageRow { day: day.into(), workspace: workspace.into(), key: key.into(), value, cash, cost, given: Given::default() }
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1954 }
1955
1956 #[test]
1957 fn a_day_puts_the_bill_g1ts_counts_and_charges_side_by_side() {
1958 let lines = vec![
1959 line("2026-10-15", SOURCE_BILLABLE, "containers", "container_memory", 1000.0, 2.00),
1960 line("2026-10-15", SOURCE_BILLABLE, "artifacts", "artifacts_operations", 30_000.0, 3.00),
1961 // Artifacts' own events: not used while the bill has a count.
1962 line("2026-10-15", SOURCE_ARTIFACTS, "artifacts", "events_pull", 29_000.0, 0.0),
1963 line("2026-10-15", SOURCE_BILLABLE, "workers", "workers_cpu_ms", 1.0, 0.50),
1964 line("2026-10-15", SOURCE_BILLABLE, "browser_rendering", "browser_hours", 2.0, 0.25),
1965 ];
1966 let own = vec![
1967 OwnRow { day: "2026-10-15".into(), meter: "git_operations".into(), workspace: "acme".into(), quantity: 7_500.0 },
1968 OwnRow { day: "2026-10-15".into(), meter: "git_operations".into(), workspace: "beta".into(), quantity: 2_500.0 },
1969 ];
1970 let usage = vec![
1971 usage("2026-10-15", "acme", "sandbox", 2_400_000, 1_000_000, 2_000_000),
1972 usage("2026-10-15", "beta", "sandbox", 1_200_000, 1_200_000, 1_000_000),
1973 usage("2026-10-15", "acme", "git", 600_000, 600_000, 500_000),
1974 usage("2026-10-15", "acme", "implement", 120_000, 120_000, 100_000),
1975 usage("2026-10-15", "beta", "plan", 20_000_000, 20_000_000, 0),
1976 ];
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running1977 let (days, workspaces) = fold(&rules(), &revenue_map(), &lines, &own, &usage, &BTreeSet::new());
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1978 let get = |bucket: &str| days.iter().find(|d| d.bucket == bucket).unwrap();
1979 let sandboxes = get("sandboxes");
1980 assert_eq!((sandboxes.cf_cost_micros, sandboxes.own_cost_micros, sandboxes.value_micros, sandboxes.cash_micros), (2_000_000, 3_000_000, 3_600_000, 2_200_000));
1981 let git = get("git");
1982 assert_eq!(git.cf_cost_micros, 3_000_000);
1983 assert_eq!((git.cf_quantity, git.own_quantity), (30_000.0, 10_000.0));
1984 assert_eq!(get("platform").value_micros, 20_000_000);
1985 // Not mapped: a leak until someone maps it.
1986 assert_eq!(get(UNMAPPED).cf_cost_micros, 250_000);
1987 // Models: no Cloudflare line, their cost is g1t's own.
1988 assert_eq!(get("models").cost(), 100_000);
1989 // Git's cost shared by g1t's own counts (Cloudflare gave none per
1990 // workspace here): three quarters to acme.
1991 let share = |ws: &str, bucket: &str| workspaces.iter().find(|w| w.workspace == ws && w.bucket == bucket).map(|w| (w.cost, w.revenue));
1992 assert_eq!(share("acme", "git"), Some((2_250_000, 600_000)));
1993 assert_eq!(share("beta", "git"), Some((750_000, 0)));
1994 // Every bucket's cost is shared out exactly.
1995 for d in &days {
1996 let shared: i64 = workspaces.iter().filter(|w| w.bucket == d.bucket).map(|w| w.cost).sum();
1997 assert_eq!(shared, d.cost(), "{}", d.bucket);
1998 }
1999 }
2000
2001 #[test]
2002 fn artifacts_events_count_when_the_bill_does_not() {
2003 let lines = vec![
2004 line("2026-10-05", SOURCE_ARTIFACTS, "artifacts", "events_pull", 120.0, 0.0),
2005 line("2026-10-05", SOURCE_ARTIFACTS, "artifacts", "events_push", 30.0, 0.0),
2006 line("2026-10-05", SOURCE_ARTIFACTS, "artifacts", "events_ratelimited", 9.0, 0.0),
2007 ];
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running2008 let (days, _) = fold(&rules(), &revenue_map(), &lines, &[], &[], &BTreeSet::new());
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily2009 assert_eq!(days[0].cf_quantity, 150.0);
2010 assert_eq!(days[0].cf_cost_micros, 0);
2011 }
2012
2013 #[test]
2014 fn month_end_meters_are_told_by_the_day_from_snapshots() {
2015 let snap = |day: &str, cost: i64, charge: i64| (day.to_string(), "acme".to_string(), "git".to_string(), cost, charge);
2016 let rows = pending_deltas(&[snap("2026-10-30", 100, 120), snap("2026-10-31", 250, 300), snap("2026-11-01", 40, 48), snap("2026-11-02", 40, 48)]);
2017 assert_eq!(
2018 rows.iter().map(|r| (r.day.as_str(), r.cost, r.value)).collect::<Vec<_>>(),
2019 vec![("2026-10-30", 100, 120), ("2026-10-31", 150, 180), ("2026-11-01", 40, 48)]
2020 );
2021 }
2022
2023 #[test]
2024 fn a_plan_payment_is_spread_over_the_month_it_pays_for() {
2025 let days = spread("2026-10-01T00:00:00.000Z", 20_000_000, 30);
2026 assert_eq!(days.len(), 30);
2027 assert_eq!(days[0], ("2026-10-01".to_string(), 666_667));
2028 assert_eq!(days[29], ("2026-10-30".to_string(), 666_666));
2029 assert_eq!(days.iter().map(|d| d.1).sum::<i64>(), 20_000_000);
2030 assert!(spread("2026-10-01", 0, 30).is_empty());
2031 assert_eq!(dollars(17_024_000), "$17.02");
2032 assert_eq!(dollars(-27_668_620), "-$27.67");
2033 assert_eq!(dollars(63_000), "$0.063");
2034 }
2035
2036 #[test]
2037 fn margins_and_deltas() {
2038 assert_eq!(margin_percent(1_200_000, 1_000_000).map(|m| (m * 100.0).round() / 100.0), Some(16.67));
2039 assert_eq!(margin_percent(0, 5), None);
2040 assert_eq!(delta_percent(110.0, 100.0), Some(10.0));
2041 assert_eq!(delta_percent(1.0, 0.0), None);
2042 }
2043
2044 fn day(bucket: &str, cf: i64, own: i64, value: i64, cfq: f64, ownq: f64) -> ProductDay {
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running2045 ProductDay { day: "2026-10-15".into(), bucket: bucket.into(), cf_cost_micros: cf, own_cost_micros: own, value_micros: value, cash_micros: value, cf_quantity: cfq, own_quantity: ownq, given: Given::default() }
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily2046 }
2047
2048 #[test]
2049 fn counts_more_than_the_threshold_apart_are_drift() {
2050 // Cloudflare counted 30,000 operations where g1t counted 10,000:
2051 // binding reads, perhaps. -66.7%.
2052 let drift = drifts("git", &[day("git", 3_000_000, 1_500_000, 1_800_000, 30_000.0, 10_000.0)], 10.0, true, 100_000);
2053 assert_eq!(drift.iter().map(|d| d.kind).collect::<Vec<_>>(), vec![DriftKind::Count, DriftKind::Cost]);
2054 assert!((drift[0].delta_percent.unwrap() + 66.666).abs() < 0.01);
2055 // 9% apart: within 10%.
2056 assert!(drifts("git", &[day("git", 1_000_000, 1_000_000, 1_200_000, 10_000.0, 10_900.0)], 10.0, true, 100_000).is_empty());
2057 // Uncounted products have no count drift.
2058 assert!(drifts("sandboxes", &[day("sandboxes", 1_000_000, 1_050_000, 1_200_000, 5.0, 0.0)], 10.0, false, 100_000).is_empty());
2059 }
2060
2061 #[test]
2062 fn cost_with_no_revenue_is_a_leak_but_not_for_running_g1t() {
2063 let leak = drifts("actions_cache", &[day("actions_cache", 400_000, 0, 0, 0.0, 0.0)], 10.0, false, 100_000);
2064 assert_eq!(leak.len(), 1);
2065 assert_eq!(leak[0].kind, DriftKind::Leak);
2066 assert!(drifts("platform", &[day("platform", 5_000_000, 0, 0, 0.0, 0.0)], 10.0, false, 100_000).is_empty());
2067 // Pennies say nothing.
2068 assert!(drifts("actions_cache", &[day("actions_cache", 50_000, 0, 0, 0.0, 0.0)], 10.0, false, 100_000).is_empty());
2069 assert!(drifts(UNMAPPED, &[day(UNMAPPED, 250_000, 0, 0, 0.0, 0.0)], 10.0, false, 100_000)[0].kind == DriftKind::Leak);
2070 }
2071
2072 #[test]
2073 fn a_margin_alert_needs_n_days_in_a_row_under_the_floor() {
2074 let s = |d: &str, revenue: i64, cost: i64| (d.to_string(), revenue, cost);
2075 // 5%, 0%, -20%: three days under 10%.
2076 let series = vec![s("10-13", 1_200_000, 1_000_000), s("10-14", 1_050_000, 1_000_000), s("10-15", 1_000_000, 1_000_000), s("10-16", 1_000_000, 1_200_000)];
2077 let (from, worst) = breach(&series, 10.0, 3, 100_000).unwrap();
2078 assert_eq!(from, "10-14");
2079 assert!((worst + 20.0).abs() < 1e-9);
2080 // A good day in the window clears it.
2081 let mended = vec![s("10-14", 1_050_000, 1_000_000), s("10-15", 1_300_000, 1_000_000), s("10-16", 1_000_000, 1_200_000)];
2082 assert!(breach(&mended, 10.0, 3, 100_000).is_none());
2083 // Cost with no revenue at all is the worst margin there is.
2084 assert_eq!(breach(&[s("10-16", 0, 500_000)], 10.0, 1, 100_000).unwrap().1, -100.0);
2085 // Too little cost to judge.
2086 assert!(breach(&[s("10-16", 0, 5_000)], 10.0, 1, 100_000).is_none());
2087 assert!(breach(&series, 10.0, 9, 100_000).is_none());
2088 }
2089
2090 #[test]
2091 fn shared_costs_add_up_to_the_bill() {
2092 let w = |k: &str, v: f64| (k.to_string(), v);
2093 assert_eq!(attribute(100, &[w("a", 1.0), w("b", 1.0), w("c", 1.0)]), vec![("a".into(), 34), ("b".into(), 33), ("c".into(), 33)]);
2094 assert_eq!(attribute(10, &[w("a", 3.0), w("b", 1.0), w("a", 0.0)]), vec![("a".into(), 8), ("b".into(), 2)]);
2095 assert!(attribute(10, &[w("a", 0.0)]).is_empty());
2096 assert!(attribute(0, &[w("a", 1.0)]).is_empty());
2097 }
2098
2099 #[test]
Costs: counts are compared with Cloudflare's from the day g1t started counting, so days before a meter existed are not drift2100 fn counts_are_compared_from_the_day_g1t_started_counting() {
2101 let on = |day: &str, cf: f64, own: f64| ProductDay { day: day.into(), bucket: "git".into(), cf_quantity: cf, own_quantity: own, ..ProductDay::default() };
2102 // Five days of Cloudflare's count before g1t's meter, then two that match.
2103 let days = vec![on("2026-10-01", 500.0, 0.0), on("2026-10-05", 300.0, 0.0), on("2026-10-06", 210.0, 231.0), on("2026-10-07", 450.0, 458.0)];
2104 assert!(drifts("git", &days, 10.0, true, 0).iter().all(|d| d.kind != DriftKind::Count));
2105 // A real gap on the days both counted still shows.
2106 let days = vec![on("2026-10-01", 500.0, 0.0), on("2026-10-06", 400.0, 231.0), on("2026-10-07", 600.0, 300.0)];
2107 let found = drifts("git", &days, 10.0, true, 0);
2108 let count = found.iter().find(|d| d.kind == DriftKind::Count).unwrap();
2109 assert_eq!((count.ours, count.cloudflare), (531.0, 1000.0));
2110 // A meter that never counted is compared over every day.
2111 let days = vec![on("2026-10-06", 400.0, 0.0)];
2112 assert!(drifts("git", &days, 10.0, true, 0).iter().any(|d| d.kind == DriftKind::Count));
2113 }
2114
2115 #[test]
Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it2116 fn what_g1t_gives_away_is_kept_apart_from_what_it_sells() {
2117 let map = BTreeMap::new();
2118 // A comped workspace (all of it given), one in its trial (half paid
2119 // by the trial) and one paying in cash, all on models.
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running2120 let comped = usage("2026-10-15", "flagon", "agent", 1_200_000, 0, 1_000_000);
Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it2121 let mut trial = usage("2026-10-15", "acme", "agent", 1_200_000, 600_000, 1_000_000);
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running2122 trial.given = Given { trial: 600_000, ..Given::default() };
Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it2123 let paying = usage("2026-10-15", "beta", "agent", 1_200_000, 1_200_000, 1_000_000);
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running2124 // Nothing priced that day: free use.
2125 let free = usage("2026-10-15", "gamma", "agent", 0, 0, 1_000_000);
2126 let internal = BTreeSet::from(["flagon".to_string()]);
2127 let (days, workspaces) = fold(&[], &map, &[], &[], &[comped, trial, paying, free], &internal);
Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it2128 let models = days.iter().find(|d| d.bucket == "models").unwrap();
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running2129 assert_eq!(models.cost(), 4_000_000);
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging2130 assert_eq!(models.given, Given { comped: 1_000_000, free: 1_000_000, trial: 500_000, ..Given::default() });
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running2131 let given = |w: &str| workspaces.iter().find(|x| x.workspace == w).unwrap().given.total();
2132 assert_eq!((given("flagon"), given("acme"), given("beta"), given("gamma")), (1_000_000, 500_000, 0, 1_000_000));
Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it2133 }
2134
2135 #[test]
Merge branch 'worktree-agent-a633ac0f7f66d419d'2136 fn a_discounted_sale_keeps_its_margin_and_counts_the_discount_as_given() {
2137 // $1 of model cost at 20%, sold to an account with 30% off: charged
2138 // $0.84, and $0.36 below cost plus the margin given (as usage_rows
2139 // reads the ledger: value at price, the discount part given).
2140 let mut sale = usage("2026-10-15", "acme", "agent", 1_200_000, 840_000, 1_000_000);
2141 sale.given = Given { discount: 360_000, ..Given::default() };
2142 let (days, _) = fold(&[], &BTreeMap::new(), &[], &[], &[sale], &BTreeSet::new());
2143 let models = days.iter().find(|d| d.bucket == "models").unwrap();
2144 assert_eq!(models.value_micros, 1_200_000);
2145 assert_eq!(models.given, Given { discount: 300_000, ..Given::default() });
2146 // What was sold (cost less given) still makes the margin.
2147 let sold = models.cost() - models.given.total();
2148 assert_eq!(margin_percent(models.cash_micros, sold).map(|m| m.round()), Some(17.0));
2149 }
2150
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging2151 fn draw(kind: CreditKind, reference: &str, task: Option<&str>, at: &str, micros: i64) -> (String, crate::grants::Draw) {
2152 let draw = crate::grants::Draw { grant: "crd_a".into(), kind, reference: reference.into(), task: task.map(Into::into), at: at.into(), micros };
2153 ("acme".to_owned(), draw)
2154 }
2155
2156 #[test]
2157 fn usage_paid_for_with_credit_is_given_not_money_in() {
2158 // $1.20 of usage on $1 of cost, all of it paid with promotional credit.
2159 let mut rows = vec![usage("2026-10-15", "acme", "implement", 1_200_000, 1_200_000, 1_000_000)];
2160 apply_credits(&mut rows, &[draw(CreditKind::Promotional, "run_1", Some("implement"), "2026-10-15T10:00:00Z", 1_200_000)], &[]);
2161 assert_eq!(rows[0].cash, 0);
2162 assert_eq!(rows[0].given, Given { credit_promotional: 1_200_000, ..Given::default() });
2163 let (days, workspaces) = fold(&[], &BTreeMap::new(), &[], &[], &rows, &BTreeSet::new());
2164 let models = days.iter().find(|d| d.bucket == "models").unwrap();
2165 // Valued at its price, none of it money in, all of its cost given:
2166 // the margin on what was sold is untouched by it.
2167 assert_eq!((models.value_micros, models.cash_micros), (1_200_000, 0));
2168 assert_eq!(models.given, Given { credit_promotional: 1_000_000, ..Given::default() });
2169 assert_eq!(models.cost() - models.given.total(), 0);
2170 assert_eq!(workspaces[0].given.total(), 1_000_000);
2171 // Half paid with goodwill credit: half the cost given, half sold.
2172 let mut rows = vec![usage("2026-10-15", "acme", "implement", 1_200_000, 1_200_000, 1_000_000)];
2173 apply_credits(&mut rows, &[draw(CreditKind::Goodwill, "run_1", Some("implement"), "2026-10-15T10:00:00Z", 600_000)], &[]);
2174 let (days, _) = fold(&[], &BTreeMap::new(), &[], &[], &rows, &BTreeSet::new());
2175 let models = days.iter().find(|d| d.bucket == "models").unwrap();
2176 assert_eq!(models.cash_micros, 600_000);
2177 assert_eq!(models.given, Given { credit_goodwill: 500_000, ..Given::default() });
2178 let sold = models.cost() - models.given.total();
2179 assert_eq!(margin_percent(models.cash_micros, sold).map(|m| m.round()), Some(17.0));
2180 }
2181
2182 #[test]
2183 fn what_a_refund_pays_for_is_paid_for_and_the_refund_comes_off_its_day() {
2184 // A refund's credit pays for usage: still money in, nothing given.
2185 let mut rows = vec![usage("2026-10-15", "acme", "implement", 1_200_000, 1_200_000, 1_000_000)];
2186 apply_credits(&mut rows, &[draw(CreditKind::Refund, "run_9", Some("implement"), "2026-10-15T10:00:00Z", 1_200_000)], &[]);
2187 assert_eq!((rows[0].cash, rows[0].given), (1_200_000, Given::default()));
2188 // The $3 refunded for Oct 2 comes off that day's money in, shared
2189 // over what was paid that day.
2190 let mut rows = vec![
2191 usage("2026-10-02", "acme", "implement", 4_000_000, 4_000_000, 3_000_000),
2192 usage("2026-10-02", "acme", "sandbox", 2_000_000, 2_000_000, 1_500_000),
2193 usage("2026-10-02", "beta", "implement", 9_000_000, 9_000_000, 7_000_000),
2194 ];
2195 let refund = crate::grants::Refunded { workspace: "acme".into(), day: "2026-10-02".into(), micros: 3_000_000 };
2196 apply_credits(&mut rows, &[], std::slice::from_ref(&refund));
2197 assert_eq!((rows[0].cash, rows[1].cash, rows[2].cash), (2_000_000, 1_000_000, 9_000_000));
2198 assert!(rows.iter().all(|r| r.given == Given::default()));
2199 // Nothing paid that day: a line of its own, money in less than nothing.
2200 let mut rows = vec![];
2201 apply_credits(&mut rows, &[], &[refund]);
2202 assert_eq!((rows[0].key.as_str(), rows[0].cash, rows[0].value), ("other", -3_000_000, 0));
2203 }
2204
2205 #[test]
2206 fn credit_spent_on_month_end_meters_is_a_line_of_its_own() {
2207 // Storage is reconciled from snapshots, not its ledger line: what
2208 // credit paid of it is its own row on the day it was charged.
2209 let mut rows = vec![usage("2026-10-01", "acme", "implement", 1_000, 1_000, 800)];
2210 apply_credits(&mut rows, &[draw(CreditKind::Goodwill, "storage/2026-09", Some("storage"), "2026-10-01T00:05:00Z", 2_000_000)], &[]);
2211 assert_eq!(rows.len(), 2);
2212 assert_eq!((rows[1].key.as_str(), rows[1].cash, rows[1].value), ("storage", -2_000_000, 0));
2213 assert_eq!(rows[1].given.credit_goodwill, 2_000_000);
2214 assert_eq!(rows[0].cash, 1_000);
2215 }
2216
Merge branch 'worktree-agent-a633ac0f7f66d419d'2217 #[test]
2218 fn the_gateways_total_against_the_ledgers_model_cost_is_drift() {
2219 // The gateway priced $5 of g1t's own traffic; the ledger has $3.
2220 let short = drifts("models", &[day("models", 5_000_000, 3_000_000, 3_600_000, 0.0, 0.0)], 10.0, false, 100_000);
2221 assert_eq!(short.iter().map(|d| d.kind).collect::<Vec<_>>(), vec![DriftKind::Cost]);
2222 assert!((short[0].delta_percent.unwrap() + 40.0).abs() < 1e-9);
2223 // Gateway traffic with nothing on the ledger at all: cost drift and a leak.
2224 let none = drifts("models", &[day("models", 2_000_000, 0, 0, 0.0, 0.0)], 10.0, false, 100_000);
2225 assert_eq!(none.iter().map(|d| d.kind).collect::<Vec<_>>(), vec![DriftKind::Cost, DriftKind::Leak]);
Billing: AI Gateway's analytics are read with the token that can see them, and a gateway that priced nothing is said2226 // Within the threshold: nothing.
Merge branch 'worktree-agent-a633ac0f7f66d419d'2227 assert!(drifts("models", &[day("models", 1_050_000, 1_000_000, 1_200_000, 0.0, 0.0)], 10.0, false, 100_000).is_empty());
Billing: AI Gateway's analytics are read with the token that can see them, and a gateway that priced nothing is said2228 // The gateway priced nothing against a ledger that has model cost:
2229 // not agreement (a token that cannot see AI Gateway reads as no
2230 // rows), so it is said. Under the minimum, or no model cost: nothing.
2231 let silent = drifts("models", &[day("models", 0, 1_000_000, 1_200_000, 0.0, 0.0)], 10.0, false, 100_000);
2232 assert_eq!(silent, vec![Drift { bucket: "models".into(), kind: DriftKind::Cost, ours: 1_000_000.0, cloudflare: 0.0, delta_percent: None }]);
2233 let said = models_detail(&silent[0], &costs::GatewayCaveats::default());
2234 assert!(said.contains("$1.00") && said.contains("priced nothing") && said.contains("AI Gateway: Read"), "{said}");
2235 assert!(drifts("models", &[day("models", 0, 50_000, 60_000, 0.0, 0.0)], 10.0, false, 100_000).is_empty());
2236 assert!(drifts("models", &[day("models", 0, 0, 0, 0.0, 0.0)], 10.0, false, 100_000).is_empty());
Merge branch 'worktree-agent-a633ac0f7f66d419d'2237 // The detail says which way and why it may be off.
2238 let caveats = costs::GatewayCaveats { cache_read_tokens: 3_000_000.0, unpriced: vec!["anthropic_claude_new_1".into()], ..Default::default() };
2239 let detail = models_detail(&short[0], &caveats);
2240 assert!(detail.contains("$5.00") && detail.contains("$3.00") && detail.contains("were not charged"), "{detail}");
2241 assert!(detail.contains("3,000,000 prompt-cache read") && detail.contains("no price for anthropic_claude_new_1"), "{detail}");
2242 }
2243
2244 #[test]
2245 fn model_usage_the_gateway_cannot_price_is_drift_even_when_the_totals_agree() {
2246 assert!(unpriced_drift(&costs::GatewayCaveats::default()).is_none());
2247 // Cache tokens alone are a note on the cost drift, not drift.
2248 assert!(unpriced_drift(&costs::GatewayCaveats { cache_write_tokens: 10.0, ..Default::default() }).is_none());
2249 let (drift, detail) = unpriced_drift(&costs::GatewayCaveats { unpriced: vec!["anthropic_claude_new_1".into()], short_runs: 2, ..Default::default() }).unwrap();
2250 assert_eq!((drift.bucket.as_str(), drift.kind.as_str()), ("models", "unpriced"));
2251 assert!(detail.contains("no price for anthropic_claude_new_1") && detail.contains("2 runs were settled"), "{detail}");
2252 }
2253
2254 #[test]
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily2255 fn a_workspace_that_costs_more_than_it_pays_is_flagged() {
2256 let rows = vec![("acme".to_string(), 5_000_000, 1_000_000), ("beta".to_string(), 900_000, 0), ("gamma".to_string(), 2_000_000, 3_000_000)];
2257 let found = anomalies(&rows, 1.0, 1_000_000);
2258 assert_eq!(found, vec![("acme".to_string(), 5_000_000, 1_000_000)]);
2259 // At twice its revenue as the threshold, $5 against $3 is fine.
2260 assert!(anomalies(&[("acme".to_string(), 5_000_000, 3_000_000)], 2.0, 1_000_000).is_empty());
2261 }
2262
2263 #[test]
2264 fn a_git_operation_costs_what_cloudflare_counts_for_it() {
2265 // $0.15 per 1,000 of Cloudflare's operations, on the charged days.
2266 let rate = billed_rate(&[(10_000.0, 0.0), (20_000.0, 3.0), (30_000.0, 4.5), (5_000.0, 0.75)]).unwrap();
2267 assert!((rate - 0.000_15).abs() < 1e-12);
2268 // Cloudflare counted 3 for every 1 g1t did: binding reads count.
2269 let per_op = derived_unit_cost(rate, 300_000.0, 100_000.0).unwrap();
2270 let per_thousand_micros = per_op * unit_size("1,000 operations") * 1e6;
2271 assert!((per_thousand_micros - 450_000.0).abs() < 1e-6, "{per_thousand_micros}");
2272 // Too few of g1t's units to say.
2273 assert!(derived_unit_cost(rate, 3_000.0, 500.0).is_none());
2274 assert!(billed_rate(&[(10_000.0, 0.0)]).is_none());
2275 assert_eq!(unit_size("million requests"), 1e6);
2276 assert_eq!(unit_size("second"), 1.0);
2277 }
2278}

This file's history is long; its oldest lines are credited to the oldest commit read.