g1t/services/billing/src/margin.rs

1,897 lines87,407 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
114/// open-source pool. The Team plan's included usage is paid for by the
115/// plan's price, so it is sold, not given.
116#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
117pub(crate) struct Given {
118 pub comped: i64,
119 pub free: i64,
120 pub trial: i64,
121 pub pool: i64,
122}
123
124impl Given {
125 pub fn total(&self) -> i64 {
126 self.comped + self.free + self.trial + self.pool
127 }
128
129 fn add(&mut self, other: &Given) {
130 self.comped += other.comped;
131 self.free += other.free;
132 self.trial += other.trial;
133 self.pool += other.pool;
134 }
135
136 /// The same shares of `cost` as these are of `value`, at most all of it.
137 fn of(&self, cost: i64, value: i64) -> Given {
138 let total = self.total();
139 if value <= 0 || cost <= 0 || total <= 0 {
140 return Given::default();
141 }
142 let given = cost as i128 * total.min(value) as i128 / value as i128;
143 let part = |x: i64| (given * x.max(0) as i128 / total as i128) as i64;
144 Given { comped: part(self.comped), free: part(self.free), trial: part(self.trial), pool: part(self.pool) }
145 }
146}
147
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily148/// What a workspace was charged for one key on one day.
149#[derive(Clone, Debug, Default, PartialEq)]
150pub(crate) struct UsageRow {
151 pub day: String,
152 pub workspace: String,
153 /// A ledger task (or `builds`), a month-end source, or `plan`.
154 pub key: String,
155 pub value: i64,
156 pub cash: i64,
157 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 it158 /// 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 running159 /// workspaces and in a free period, else what the trial and the pool
160 /// paid and the overruns g1t covered.
161 pub given: Given,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily162}
163
164/// One workspace's share of a product's cost on one day.
165#[derive(Clone, Debug, PartialEq)]
166pub(crate) struct WorkspaceDay {
167 pub day: String,
168 pub workspace: String,
169 pub bucket: String,
170 pub cost: i64,
Margin alerts measure what is sold, and say dollars when a percentage would mislead171 /// What the workspace paid in cash.
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily172 pub revenue: i64,
Margin alerts measure what is sold, and say dollars when a percentage would mislead173 /// What its usage was priced at, whoever paid for it.
174 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 running175 /// Of `cost`, the part g1t gave away: all of it for a comped workspace
176 /// or one with nothing priced that day (free use), else the cost times
177 /// the shares of its usage that day that g1t paid for.
178 pub given: Given,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily179}
180
181fn micros(dollars: f64) -> i64 {
182 (dollars * 1_000_000.0).round() as i64
183}
184
185/// Puts the day's bill, g1t's counts and what customers were charged side
186/// by side, a row per day and bucket, and shares each bucket's cost out
187/// to workspaces.
188pub(crate) fn fold(
189 rules: &[Rule],
190 revenue_map: &BTreeMap<String, String>,
191 lines: &[LineRow],
192 own: &[OwnRow],
193 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 running194 internal: &BTreeSet<String>,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily195) -> (Vec<ProductDay>, Vec<WorkspaceDay>) {
196 let mut days: BTreeMap<(String, String), ProductDay> = BTreeMap::new();
197 let entry = |day: &str, bucket: &str| -> ProductDay {
198 ProductDay { day: day.to_owned(), bucket: bucket.to_owned(), ..ProductDay::default() }
199 };
200 // Which of g1t's own meters count each bucket's units.
201 let mut own_meters: BTreeMap<&str, BTreeSet<&str>> = BTreeMap::new();
202 for rule in rules {
203 if let Some(meter) = &rule.own_meter {
204 own_meters.entry(rule.bucket.as_str()).or_default().insert(meter.as_str());
205 }
206 }
207 let mut events: BTreeMap<(String, String), f64> = BTreeMap::new();
208 for line in lines {
209 let rule = costs::classify(rules, &line.product, &line.meter);
210 let bucket = rule.map_or(UNMAPPED, |r| r.bucket.as_str());
211 let key = (line.day.clone(), bucket.to_owned());
212 if line.source == SOURCE_ARTIFACTS {
213 // What Artifacts counted: operations only, and only where the
214 // bill does not count them itself.
215 if ARTIFACTS_OPERATIONS.contains(&line.meter.as_str()) {
216 *events.entry(key).or_default() += line.quantity;
217 }
218 continue;
219 }
220 let row = days.entry(key.clone()).or_insert_with(|| entry(&key.0, &key.1));
221 row.cf_cost_micros += micros(line.cost_usd);
222 if line.source == SOURCE_BILLABLE && rule.is_some_and(|r| r.own_meter.is_some()) {
223 row.cf_quantity += line.quantity;
224 }
225 }
226 for (key, quantity) in events {
227 let row = days.entry(key.clone()).or_insert_with(|| entry(&key.0, &key.1));
228 if row.cf_quantity == 0.0 {
229 row.cf_quantity = quantity;
230 }
231 }
232 // g1t's own counts of the same units, by bucket and by workspace.
233 let mut own_by: BTreeMap<(String, String), Vec<(String, f64)>> = BTreeMap::new();
234 // Cloudflare's own count by workspace, where it gives one
235 // (`cloudflare_<bucket>`): the best way to share its cost.
236 let mut cf_by: BTreeMap<(String, String), Vec<(String, f64)>> = BTreeMap::new();
237 for count in own {
238 if let Some(bucket) = count.meter.strip_prefix("cloudflare_") {
239 cf_by.entry((count.day.clone(), bucket.to_owned())).or_default().push((count.workspace.clone(), count.quantity));
240 continue;
241 }
242 for (bucket, meters) in &own_meters {
243 if meters.contains(count.meter.as_str()) {
244 let key = (count.day.clone(), (*bucket).to_owned());
245 days.entry(key.clone()).or_insert_with(|| entry(&key.0, &key.1)).own_quantity += count.quantity;
246 own_by.entry(key).or_default().push((count.workspace.clone(), count.quantity));
247 }
248 }
249 }
250 // What customers were charged.
251 let bucket_of = |key: &str| revenue_map.get(key).cloned().unwrap_or_else(|| "models".to_owned());
252 let mut value_by: BTreeMap<(String, String), Vec<(String, f64)>> = BTreeMap::new();
253 let mut cost_by: BTreeMap<(String, String), Vec<(String, f64)>> = BTreeMap::new();
254 let mut revenue: BTreeMap<(String, String, String), i64> = BTreeMap::new();
Margin alerts measure what is sold, and say dollars when a percentage would mislead255 let mut valued: BTreeMap<(String, String, String), i64> = BTreeMap::new();
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily256 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 running257 let mut gave: BTreeMap<(String, String), (Given, i64)> = BTreeMap::new();
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily258 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 it259 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 running260 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 it261 g.1 += u.value;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily262 let bucket = bucket_of(&u.key);
263 let key = (u.day.clone(), bucket.clone());
264 let row = days.entry(key.clone()).or_insert_with(|| entry(&key.0, &key.1));
265 row.own_cost_micros += u.cost;
266 row.value_micros += u.value;
267 row.cash_micros += u.cash;
268 value_by.entry(key.clone()).or_default().push((u.workspace.clone(), u.value as f64));
269 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 mislead270 *revenue.entry((u.day.clone(), u.workspace.clone(), bucket.clone())).or_default() += u.cash;
271 *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 daily272 active.entry(u.day.clone()).or_default().push((u.workspace.clone(), u.value.max(u.cost) as f64));
273 }
274 // 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 running275 // workspace, else by g1t's own count of its units, else by what its
276 // usage cost (so free use carries its own cost), else by what it was
277 // charged; running g1t, and what no one mapped, by each workspace's
278 // share of all usage that day.
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily279 let mut shares: BTreeMap<(String, String, String), i64> = BTreeMap::new();
280 for ((day, bucket), row) in &days {
281 let key = (day.clone(), bucket.clone());
282 let weigh = |m: &BTreeMap<(String, String), Vec<(String, f64)>>| m.get(&key).filter(|w| w.iter().any(|(_, v)| *v > 0.0)).cloned();
283 let weights = if OVERHEAD.contains(&bucket.as_str()) || bucket == UNMAPPED {
284 active.get(day).cloned()
285 } 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 running286 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 daily287 };
288 for (workspace, micros) in attribute(row.cost(), &weights.unwrap_or_default()) {
289 *shares.entry((day.clone(), workspace, bucket.clone())).or_default() += micros;
290 }
291 }
292 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 it293 let workspaces: Vec<WorkspaceDay> = keys
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily294 .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 it295 .map(|(day, workspace, bucket)| {
296 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 running297 // The day's shares given away apply to every bucket, so a
298 // comped workspace's part of running g1t is given too. A
299 // workspace with nothing priced that day used g1t for free.
300 let given = if internal.contains(&workspace) {
301 Given { comped: cost, ..Given::default() }
302 } else {
303 match gave.get(&(day.clone(), workspace.clone())) {
304 Some((given, value)) if *value > 0 => given.of(cost, *value),
305 _ => Given { free: cost.max(0), ..Given::default() },
306 }
Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it307 };
308 WorkspaceDay {
309 cost,
310 revenue: revenue.get(&(day.clone(), workspace.clone(), bucket.clone())).copied().unwrap_or(0),
311 value: valued.get(&(day.clone(), workspace.clone(), bucket.clone())).copied().unwrap_or(0),
312 given,
313 day,
314 workspace,
315 bucket,
316 }
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily317 })
318 .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 it319 for w in &workspaces {
320 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 running321 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 it322 }
323 }
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily324 (days.into_values().collect(), workspaces)
325}
326
327/// A month-end source's day, from the snapshots of what it had come to:
328/// each day's figure less the day before's in the same month (the first
329/// day of a month, or the first snapshot, is its own).
330pub(crate) fn pending_deltas(snapshots: &[(String, String, String, i64, i64)]) -> Vec<UsageRow> {
331 // (day, workspace, source, cost, charge), any order.
332 let mut sorted = snapshots.to_vec();
333 sorted.sort_by(|a, b| (&a.1, &a.2, &a.0).cmp(&(&b.1, &b.2, &b.0)));
334 let mut out = Vec::new();
335 let mut previous: Option<&(String, String, String, i64, i64)> = None;
336 for snap in &sorted {
337 let (day, workspace, source, cost, charge) = snap;
338 let (before_cost, before_charge) = match previous {
339 Some(p) if p.1 == *workspace && p.2 == *source && p.0[..7] == day[..7] => (p.3, p.4),
340 _ => (0, 0),
341 };
342 let (cost, charge) = ((cost - before_cost).max(0), (charge - before_charge).max(0));
343 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 running344 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 daily345 }
346 previous = Some(snap);
347 }
348 out
349}
350
351/// Margin as a share of what was charged, in percent; None when nothing was.
352pub(crate) fn margin_percent(revenue_micros: i64, cost_micros: i64) -> Option<f64> {
353 (revenue_micros > 0).then(|| (revenue_micros - cost_micros) as f64 * 100.0 / revenue_micros as f64)
354}
355
356/// How far `ours` is from `theirs`, in percent of theirs; None when theirs
357/// is nothing.
358pub(crate) fn delta_percent(ours: f64, theirs: f64) -> Option<f64> {
359 (theirs > 0.0).then(|| (ours - theirs) * 100.0 / theirs)
360}
361
362#[derive(Clone, Copy, Debug, PartialEq, Eq)]
363pub(crate) enum DriftKind {
364 /// g1t counted a different number of units than Cloudflare did.
365 Count,
366 /// What Cloudflare charged differs from what the price book says the
367 /// same usage cost.
368 Cost,
369 /// Cloudflare charged for something nothing charges customers for.
370 Leak,
371}
372
373impl DriftKind {
374 pub fn as_str(self) -> &'static str {
375 match self {
376 DriftKind::Count => "count",
377 DriftKind::Cost => "cost",
378 DriftKind::Leak => "leak",
379 }
380 }
381}
382
383#[derive(Clone, Debug, PartialEq)]
384pub(crate) struct Drift {
385 pub bucket: String,
386 pub kind: DriftKind,
387 pub ours: f64,
388 pub cloudflare: f64,
389 pub delta_percent: Option<f64>,
390}
391
392/// Drift over a window for one bucket: counts more than `threshold`
393/// percent apart, a bill that far from the price book's cost of the same
394/// usage, and cost with nothing charged for it. Under `min_cost_micros`
395/// in all, cost says nothing.
396pub(crate) fn drifts(bucket: &str, days: &[ProductDay], threshold: f64, counted: bool, min_cost_micros: i64) -> Vec<Drift> {
397 let overhead = OVERHEAD.contains(&bucket);
398 let sum = |f: &dyn Fn(&ProductDay) -> f64| days.iter().map(f).sum::<f64>();
399 let cf_cost = sum(&|d| d.cf_cost_micros as f64);
400 let own_cost = sum(&|d| d.own_cost_micros as f64);
401 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 drift402 // Counts are compared from the first day g1t counted: before its meter
403 // was deployed there is only Cloudflare's side. A meter that never
404 // counted anything is compared over every day, so it still shows.
405 let first_counted = days.iter().filter(|d| d.own_quantity > 0.0).map(|d| d.day.as_str()).min();
406 let compared = |d: &&ProductDay| first_counted.is_none_or(|from| d.day.as_str() >= from);
407 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 daily408 let mut out = Vec::new();
409 if counted && cf_quantity > 0.0 {
410 let delta = delta_percent(own_quantity, cf_quantity);
411 if delta.is_some_and(|d| d.abs() > threshold) {
412 out.push(Drift { bucket: bucket.into(), kind: DriftKind::Count, ours: own_quantity, cloudflare: cf_quantity, delta_percent: delta });
413 }
414 }
415 let enough = cf_cost.max(own_cost) >= min_cost_micros as f64;
416 if enough && !overhead && cf_cost > 0.0 && own_cost > 0.0 && !NOT_CLOUDFLARE.contains(&bucket) {
417 let delta = delta_percent(own_cost, cf_cost);
418 if delta.is_some_and(|d| d.abs() > threshold) {
419 out.push(Drift { bucket: bucket.into(), kind: DriftKind::Cost, ours: own_cost, cloudflare: cf_cost, delta_percent: delta });
420 }
421 }
422 if !overhead && cf_cost >= min_cost_micros as f64 && value <= 0.0 {
423 out.push(Drift { bucket: bucket.into(), kind: DriftKind::Leak, ours: value, cloudflare: cf_cost, delta_percent: None });
424 }
425 out
426}
427
428/// When the last `days` in a row (each with enough cost to say something)
429/// were all under the floor: the first of them and the worst margin.
430/// Each item is a day's (day, revenue, cost).
431pub(crate) fn breach(series: &[(String, i64, i64)], floor_percent: f64, days: usize, min_cost_micros: i64) -> Option<(String, f64)> {
432 if days == 0 || series.len() < days {
433 return None;
434 }
435 let tail = &series[series.len() - days..];
436 let mut worst = f64::INFINITY;
437 for (_, revenue, cost) in tail {
438 if *cost < min_cost_micros {
439 return None;
440 }
441 let margin = margin_percent(*revenue, *cost).unwrap_or(-100.0);
442 if margin >= floor_percent {
443 return None;
444 }
445 worst = worst.min(margin);
446 }
447 Some((tail[0].0.clone(), worst))
448}
449
450/// `total` shared out in proportion to `weights`, in whole millionths that
451/// add up to it exactly (largest remainder first). Nothing to share, or no
452/// weight, shares nothing.
453pub(crate) fn attribute(total: i64, weights: &[(String, f64)]) -> Vec<(String, i64)> {
454 let mut merged: BTreeMap<String, f64> = BTreeMap::new();
455 for (key, w) in weights {
456 *merged.entry(key.clone()).or_default() += w.max(0.0);
457 }
458 let sum: f64 = merged.values().sum();
459 if total <= 0 || sum <= 0.0 {
460 return Vec::new();
461 }
462 let mut shares: Vec<(String, i64, f64)> = merged
463 .into_iter()
464 .map(|(key, w)| {
465 let exact = total as f64 * w / sum;
466 (key, exact.floor() as i64, exact - exact.floor())
467 })
468 .collect();
469 let mut left = total - shares.iter().map(|s| s.1).sum::<i64>();
470 let mut order: Vec<usize> = (0..shares.len()).collect();
471 order.sort_by(|a, b| shares[*b].2.total_cmp(&shares[*a].2).then(shares[*a].0.cmp(&shares[*b].0)));
472 for index in order {
473 if left <= 0 {
474 break;
475 }
476 shares[index].1 += 1;
477 left -= 1;
478 }
479 shares.into_iter().filter(|s| s.1 > 0).map(|(key, micros, _)| (key, micros)).collect()
480}
481
482/// Workspaces that cost g1t more than `factor` times what they paid, with
483/// at least `floor_micros` of cost: each (workspace, cost, revenue), the
484/// biggest gap first.
Models' margin read -14%: usage nothing paid for is valued at price, not $0485/// What a day's usage was worth at price. g1t's own workspaces are valued
486/// at price. So is usage nothing paid for, neither charged nor drawn from
487/// the plan, a trial, a pool or a gift (a free period): it was given away at
488/// its price, not sold for nothing. Anything paid keeps what it was paid, so
489/// a discount still shows as one.
490pub(crate) fn usage_value(internal: bool, cost: i64, paid: i64, margin_percent: u32) -> i64 {
491 if internal || (paid == 0 && cost > 0) {
492 return crate::credits::with_margin(cost, margin_percent);
493 }
494 paid
495}
496
Margin alerts measure what is sold, and say dollars when a percentage would mislead497/// What the overall alert says: the money as money, and a percentage only
498/// while there is enough coming in for one to mean something (a few cents
499/// against dollars of cost reads as -8000%).
500pub(crate) fn overall_detail(took: i64, spent: i64, days: usize, floor: f64, worst: f64) -> String {
501 if took < 1_000_000 * days as i64 {
502 return format!(
503 "All of g1t, comped workspaces left out: took in {} against {} of Cloudflare's bill over {days} days.",
504 dollars(took),
505 dollars(spent)
506 );
507 }
508 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}%.")
509}
510
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily511pub(crate) fn anomalies(rows: &[(String, i64, i64)], factor: f64, floor_micros: i64) -> Vec<(String, i64, i64)> {
512 let mut out: Vec<(String, i64, i64)> = rows
513 .iter()
514 .filter(|(_, cost, revenue)| *cost >= floor_micros && *cost as f64 > *revenue as f64 * factor)
515 .cloned()
516 .collect();
517 out.sort_by(|a, b| (b.1 - b.2).cmp(&(a.1 - a.2)).then(a.0.cmp(&b.0)));
518 out
519}
520
521/// Cloudflare's marginal rate for one of its units: the median over the
522/// charged days of cost over quantity, in dollars. None while the included
523/// amounts still cover it. Each item is a day's (quantity, cost).
524pub(crate) fn billed_rate(days: &[(f64, f64)]) -> Option<f64> {
525 let mut rates: Vec<f64> = days.iter().filter(|(q, c)| *q > 0.0 && *c > 0.0).map(|(q, c)| c / q).collect();
526 if rates.is_empty() {
527 return None;
528 }
529 rates.sort_by(f64::total_cmp);
530 Some(rates[rates.len() / 2])
531}
532
533/// What one of g1t's units costs, from Cloudflare's rate per its own unit
534/// and how many of Cloudflare's units each of g1t's took: if Cloudflare
535/// counts three operations for every git operation g1t counts, a git
536/// operation costs three of Cloudflare's. None without enough of g1t's
537/// units to say.
538pub(crate) fn derived_unit_cost(rate_per_cf_unit: f64, cf_units: f64, own_units: f64) -> Option<f64> {
539 (own_units >= MIN_UNITS && cf_units > 0.0 && rate_per_cf_unit > 0.0).then(|| rate_per_cf_unit * cf_units / own_units)
540}
541
542/// How many units a price is per: `1,000 operations` → 1,000, `million
543/// requests` → 1,000,000, `second` → 1.
544pub(crate) fn unit_size(unit: &str) -> f64 {
545 let first = unit.split_whitespace().next().unwrap_or_default().replace(',', "");
546 match first.as_str() {
547 "million" => 1_000_000.0,
548 "thousand" => 1_000.0,
549 n => n.parse().unwrap_or(1.0),
550 }
551}
552
553fn day_before(day: &str, days: u64) -> String {
554 let ms = g1t_contracts::time::parse_rfc3339(&format!("{day}T00:00:00Z")).unwrap_or(0);
555 rfc3339(ms.saturating_sub(days * DAY_MS))[..10].to_owned()
556}
557
558/// Dollars to the cent from a dollar up, finer below: `$17.02`, `$0.063`.
559fn dollars(micros: i64) -> String {
560 if micros.abs() >= 1_000_000 {
561 let cents = (micros as f64 / 10_000.0).round() as i64;
562 format!("{}${}.{:02}", if cents < 0 { "-" } else { "" }, cents.abs() / 100, cents.abs() % 100)
563 } else {
564 crate::features::dollars(micros)
565 }
566}
567
568/// The days a plan payment is spread over.
569const PLAN_DAYS: u64 = 30;
570
571/// `micros` paid on `day` spread evenly over `days` days from it, in
572/// whole micros that add up to it (the first days take the remainder).
573pub(crate) fn spread(day: &str, micros: i64, days: u64) -> Vec<(String, i64)> {
574 if micros <= 0 || days == 0 {
575 return Vec::new();
576 }
577 let start = g1t_contracts::time::parse_rfc3339(&format!("{}T00:00:00Z", &day[..10.min(day.len())])).unwrap_or(0);
578 let each = micros / days as i64;
579 let rest = micros % days as i64;
580 (0..days)
581 .map(|n| (rfc3339(start + n * DAY_MS)[..10].to_owned(), each + i64::from((n as i64) < rest)))
582 .collect()
583}
584
585// ---------------------------------------------------------------------
586// The daily run, and what sudo reads.
587// ---------------------------------------------------------------------
588
589#[derive(Serialize)]
590struct Mail<'a> {
591 to: &'a str,
592 from: &'a str,
593 subject: &'a str,
594 text: String,
595 html: String,
596}
597
598fn escape(text: &str) -> String {
599 text.replace('&', "&amp;").replace('<', "&lt;").replace('>', "&gt;").replace('"', "&quot;")
600}
601
602/// Emails staff through Cloudflare Email Sending, the `EMAIL` binding.
Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays603pub(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 daily604 let link = "https://sudo.g1t.sh/costs";
605 let text = format!("{}\n\nCosts & margin: {link}\n\nSent by g1t-billing's margin guard (COSTS_ALERT_EMAIL).\n", lines.join("\n\n"));
606 let mut html = String::from("<div style=\"font-family:system-ui,sans-serif;max-width:560px;margin:0 auto;padding:24px 16px;color:#16150f\">");
607 for line in lines {
608 html.push_str(&format!("<p style=\"font-size:15px;line-height:1.6\">{}</p>", escape(line)));
609 }
610 html.push_str(&format!(
611 "<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>"
612 ));
613 let mail = Mail { to, from: "g1t <noreply@g1t.sh>", subject, text, html };
614 let binding = g1t_kit::js::binding(env, "EMAIL")?;
615 g1t_kit::js::call(&binding, "send", &[g1t_kit::js::to_js(&mail)?]).await?;
616 Ok(())
617}
618
619#[derive(Deserialize)]
620struct AlertRow {
621 id: String,
622 kind: String,
623 subject: String,
624 detail: String,
625 since: String,
626 opened_at: String,
627 emailed_at: Option<String>,
628}
629
630impl From<AlertRow> for MarginAlert {
631 fn from(r: AlertRow) -> Self {
632 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 }
633 }
634}
635
636#[derive(Deserialize)]
637struct MarginRow {
638 day: String,
639 bucket: String,
640 cf_cost_micros: i64,
641 own_cost_micros: i64,
642 value_micros: i64,
643 cash_micros: i64,
644 cf_quantity: f64,
645 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 it646 #[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 running647 given_comped_micros: Option<i64>,
648 #[serde(default)]
649 given_free_micros: Option<i64>,
650 #[serde(default)]
651 given_trial_micros: Option<i64>,
652 #[serde(default)]
653 given_pool_micros: Option<i64>,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily654}
655
656impl From<MarginRow> for ProductDay {
657 fn from(r: MarginRow) -> Self {
658 ProductDay {
659 day: r.day,
660 bucket: r.bucket,
661 cf_cost_micros: r.cf_cost_micros,
662 own_cost_micros: r.own_cost_micros,
663 value_micros: r.value_micros,
664 cash_micros: r.cash_micros,
665 cf_quantity: r.cf_quantity,
666 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 running667 given: Given {
668 comped: r.given_comped_micros.unwrap_or(0),
669 free: r.given_free_micros.unwrap_or(0),
670 trial: r.given_trial_micros.unwrap_or(0),
671 pool: r.given_pool_micros.unwrap_or(0),
672 },
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily673 }
674 }
675}
676
677impl Billing {
678 /// The day's work: read Cloudflare's bill and g1t's own counts,
679 /// reconcile, look for drift, measure unit costs, apply prices whose
680 /// day has come, and raise or clear alerts.
681 pub(crate) async fn costs_daily(&self, env: &Env, keeper: &crate::keeper::Keeper) -> Result<CostsRun> {
682 let mut run = CostsRun::default();
683 let (since, until) = match self.read_cloudflare(keeper, &mut run.problems).await? {
684 Some((since, until, lines)) => {
685 run.lines = lines;
686 (since, until)
687 }
688 // Without the bill, still reconcile what g1t knows itself, over
689 // the same days the bill would be read for.
690 None => {
691 #[derive(Deserialize)]
692 struct Last {
693 day: Option<String>,
694 }
695 let last = self.db.prepare("SELECT MAX(day) AS day FROM margin_days").first::<Last>(None).await?.and_then(|l| l.day);
696 costs::window(last.as_deref(), now_ms())
697 }
698 };
Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing699 // Not a problem for the run: the last read, or the estimate, stays.
700 if keeper.can_read_bill()
701 && let Err(error) = self.read_subscriptions(keeper).await
702 {
703 worker::console_error!("Cloudflare's subscriptions were not read: {error}");
704 }
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily705 if let Err(error) = self.count_own(&since, &until).await {
706 run.problems.push(format!("g1t's own counts could not be read: {error}"));
707 }
708 self.snapshot_pending(&until).await?;
Models' margin read -14%: usage nothing paid for is valued at price, not $0709 // Reconciled over the whole window sudo shows, not only the days the
710 // bill was read for: it reads only what is already kept, so a change
711 // in how a day is valued reaches every day shown at the next run.
712 let window = day_before(&until, costs::BACKFILL_DAYS - 1);
713 let reconcile_from = if window < since { window } else { since.clone() };
714 run.days = self.reconcile_range(&reconcile_from, &until).await?;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily715 let drift = self.find_drift(&until).await?;
716 run.proposals = self.measure_units(&until).await?;
717 self.apply_due_versions().await?;
718 run.alerts = self.raise_alerts(env, &until, &drift).await?;
719 if let Some(identity) = &self.identity
720 && let Err(error) = self.tell_owners_of_rises(identity).await
721 {
722 run.problems.push(format!("owners could not be told of a price rise: {error}"));
723 }
724 for problem in &run.problems {
725 worker::console_warn!("costs: {problem}");
726 }
727 Ok(run)
728 }
729
730 /// What each month-end source had come to by the end of `day`.
731 async fn snapshot_pending(&self, day: &str) -> Result<()> {
732 self.db
733 .prepare(
734 "INSERT INTO pending_days (day, workspace, source, cost_micros, charge_micros)
735 SELECT ?1, workspace, source, COALESCE(cost_micros, 0), COALESCE(charge_micros, 0) FROM pending_usage WHERE month = ?2
736 ON CONFLICT (day, workspace, source) DO UPDATE SET cost_micros = excluded.cost_micros, charge_micros = excluded.charge_micros",
737 )
738 .bind(&[day.into(), day[..7].into()])?
739 .run()
740 .await?;
741 Ok(())
742 }
743
744 /// What customers were charged on the days, by workspace and key.
745 async fn usage_rows(&self, since: &str, until: &str) -> Result<Vec<UsageRow>> {
746 #[derive(Deserialize)]
747 struct Row {
748 day: String,
749 workspace: String,
750 key: String,
751 internal: i64,
752 own_provider: i64,
753 cash: Option<i64>,
754 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 running755 trial: Option<i64>,
756 oss: Option<i64>,
757 covered: Option<i64>,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily758 cost: Option<i64>,
759 }
760 let charged_here = crate::storage::CHARGED_HERE.iter().map(|s| format!("'{s}'")).collect::<Vec<_>>().join(", ");
761 let end = format!("{until}T23:59:59.999Z");
762 let rows = self
763 .db
764 .prepare(format!(
765 "SELECT substr(created_at, 1, 10) AS day, workspace,
766 CASE WHEN task = 'deployments' AND reference LIKE 'deploy/%' THEN 'builds' ELSE COALESCE(task, 'other') END AS key,
767 CASE WHEN workspace IN ({internal}) THEN 1 ELSE 0 END AS internal,
768 CASE WHEN billed_to = 'workspace' THEN 1 ELSE 0 END AS own_provider,
769 -SUM(amount_micros) AS cash,
770 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 running771 SUM(COALESCE(trial_micros, 0)) AS trial,
772 SUM(COALESCE(oss_micros, 0)) AS oss,
773 SUM(COALESCE(given_micros, 0)) AS covered,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily774 SUM(COALESCE(cost_micros, 0)) AS cost
775 FROM ledger
776 WHERE kind = 'usage' AND created_at >= ?1 AND created_at <= ?2 AND COALESCE(task, '') NOT IN ({charged_here})
777 GROUP BY 1, 2, 3, 4, 5",
778 internal = crate::sales::INTERNAL_SQL
779 ))
780 .bind(&[since.into(), end.as_str().into()])?
781 .all()
782 .await?
783 .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 it784 let mut internal = BTreeSet::new();
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily785 let mut out: Vec<UsageRow> = rows
786 .into_iter()
787 .map(|r| {
788 // A workspace's own model provider was paid there: no cost
789 // to g1t. g1t's own workspaces are valued at price.
790 let cost = if r.own_provider == 1 { 0 } else { r.cost.unwrap_or(0) };
791 let cash = r.cash.unwrap_or(0);
Models' margin read -14%: usage nothing paid for is valued at price, not $0792 let paid = cash + r.drawn.unwrap_or(0);
793 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 running794 let given = if r.internal == 1 {
795 Given { comped: value, ..Given::default() }
796 } else if paid == 0 && cost > 0 {
797 Given { free: value, ..Given::default() }
798 } else {
799 Given { free: r.covered.unwrap_or(0), trial: r.trial.unwrap_or(0), pool: r.oss.unwrap_or(0), comped: 0 }
800 };
Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it801 if r.internal == 1 {
802 internal.insert(r.workspace.clone());
803 }
804 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 daily805 })
806 .collect();
807 // Month-end sources, from their daily snapshots.
808 #[derive(Deserialize)]
809 struct Snap {
810 day: String,
811 workspace: String,
812 source: String,
813 cost_micros: i64,
814 charge_micros: i64,
815 }
816 let snaps = self
817 .db
818 .prepare("SELECT day, workspace, source, cost_micros, charge_micros FROM pending_days WHERE day >= ?1 AND day <= ?2")
819 .bind(&[day_before(since, 1).into(), until.into()])?
820 .all()
821 .await?
822 .results::<Snap>()?
823 .into_iter()
824 .filter(|s| crate::storage::CHARGED_HERE.contains(&s.source.as_str()) || s.source == "domains")
825 .map(|s| (s.day, s.workspace, s.source, s.cost_micros, s.charge_micros))
826 .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 it827 out.extend(pending_deltas(&snaps).into_iter().filter(|u| u.day.as_str() >= since).map(|mut u| {
828 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 running829 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 it830 }
831 u
832 }));
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily833 // The plan's price, spread over the 30 days it pays for, so a month's
834 // payment does not read as one very good day and 29 bad ones.
835 #[derive(Deserialize)]
836 struct Plan {
837 day: String,
838 workspace: String,
839 micros: Option<i64>,
840 }
841 let plans = self
842 .db
843 .prepare(
844 "SELECT substr(paid_at, 1, 10) AS day, workspace, SUM(amount_micros) AS micros FROM plan_payments
845 WHERE paid_at >= ?1 AND paid_at <= ?2 GROUP BY 1, 2",
846 )
847 .bind(&[day_before(since, PLAN_DAYS - 1).into(), end.as_str().into()])?
848 .all()
849 .await?
850 .results::<Plan>()?;
851 for p in plans {
852 for (day, micros) in spread(&p.day, p.micros.unwrap_or(0), PLAN_DAYS) {
853 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 running854 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 daily855 }
856 }
857 }
858 Ok(out)
859 }
860
861 /// Reconciles the days and writes `margin_days` and `workspace_costs`.
862 async fn reconcile_range(&self, since: &str, until: &str) -> Result<u32> {
863 let rules = self.rules().await?;
864 #[derive(Deserialize)]
865 struct Map {
866 key: String,
867 bucket: String,
868 }
869 let revenue_map: BTreeMap<String, String> = self
870 .db
871 .prepare("SELECT key, bucket FROM revenue_map")
872 .all()
873 .await?
874 .results::<Map>()?
875 .into_iter()
876 .map(|m| (m.key, m.bucket))
877 .collect();
878 let lines = self
879 .db
880 .prepare("SELECT day, source, product, meter, quantity, cost_usd FROM cost_lines WHERE day >= ?1 AND day <= ?2")
881 .bind(&[since.into(), until.into()])?
882 .all()
883 .await?
884 .results::<LineRow>()?;
885 let own = self
886 .db
887 .prepare("SELECT day, meter, workspace, quantity FROM own_counts WHERE day >= ?1 AND day <= ?2")
888 .bind(&[since.into(), until.into()])?
889 .all()
890 .await?
891 .results::<OwnRow>()?;
892 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 running893 #[derive(Deserialize)]
894 struct Internal {
895 workspace: String,
896 }
897 let internal: BTreeSet<String> = self
898 .db
899 .prepare(format!("WITH i(workspace) AS ({}) SELECT DISTINCT workspace FROM i", crate::sales::INTERNAL_SQL))
900 .all()
901 .await?
902 .results::<Internal>()?
903 .into_iter()
904 .map(|i| i.workspace)
905 .collect();
906 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 daily907 let now = rfc3339(now_ms());
908 self.db
909 .batch(vec![
910 self.db.prepare("DELETE FROM margin_days WHERE day >= ?1 AND day <= ?2").bind(&[since.into(), until.into()])?,
911 self.db.prepare("DELETE FROM workspace_costs WHERE day >= ?1 AND day <= ?2").bind(&[since.into(), until.into()])?,
912 ])
913 .await?;
914 for chunk in days.chunks(50) {
915 let mut statements = Vec::with_capacity(chunk.len());
916 for d in chunk {
917 statements.push(
918 self.db
919 .prepare(
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 running920 "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, computed_at)
921 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily922 )
923 .bind(&[
924 d.day.as_str().into(),
925 d.bucket.as_str().into(),
926 (d.cf_cost_micros as f64).into(),
927 (d.own_cost_micros as f64).into(),
928 (d.value_micros as f64).into(),
929 (d.cash_micros as f64).into(),
930 d.cf_quantity.into(),
931 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 running932 (d.given.total() as f64).into(),
933 (d.given.comped as f64).into(),
934 (d.given.free as f64).into(),
935 (d.given.trial as f64).into(),
936 (d.given.pool as f64).into(),
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily937 now.as_str().into(),
938 ])?,
939 );
940 }
941 self.db.batch(statements).await?;
942 }
943 for chunk in workspaces.chunks(50) {
944 let mut statements = Vec::with_capacity(chunk.len());
945 for w in chunk {
946 statements.push(
947 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 it948 .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 daily949 .bind(&[
950 w.day.as_str().into(),
951 w.workspace.as_str().into(),
952 w.bucket.as_str().into(),
953 (w.cost as f64).into(),
954 (w.revenue as f64).into(),
Margin alerts measure what is sold, and say dollars when a percentage would mislead955 (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 running956 (w.given.total() as f64).into(),
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily957 ])?,
958 );
959 }
960 self.db.batch(statements).await?;
961 }
962 Ok(costs::days_between(since, until).len() as u32)
963 }
964
965 async fn margin_days(&self, since: &str, until: &str) -> Result<Vec<ProductDay>> {
966 Ok(self
967 .db
968 .prepare("SELECT * FROM margin_days WHERE day >= ?1 AND day <= ?2 ORDER BY day, bucket")
969 .bind(&[since.into(), until.into()])?
970 .all()
971 .await?
972 .results::<MarginRow>()?
973 .into_iter()
974 .map(ProductDay::from)
975 .collect())
976 }
977
978 /// Drift over the last week, written to `cost_drift` (replacing the
979 /// last run's), with unmapped Cloudflare meters as leaks.
980 async fn find_drift(&self, until: &str) -> Result<Vec<(Drift, String)>> {
981 let since = day_before(until, DRIFT_DAYS - 1);
982 let settings = self.cost_settings().await?;
983 let rules = self.rules().await?;
984 let days = self.margin_days(&since, until).await?;
985 let mut by: BTreeMap<String, Vec<ProductDay>> = BTreeMap::new();
986 for d in days {
987 by.entry(d.bucket.clone()).or_default().push(d);
988 }
989 let mut found = Vec::new();
990 for (bucket, days) in &by {
991 let bucket_rules: Vec<&Rule> = rules.iter().filter(|r| &r.bucket == bucket).collect();
992 let threshold = bucket_rules.iter().map(|r| r.drift_percent).fold(f64::INFINITY, f64::min);
993 let threshold = if threshold.is_finite() { threshold } else { 10.0 };
994 let counted = bucket_rules.iter().any(|r| r.own_meter.is_some());
995 for drift in drifts(bucket, days, threshold, counted, settings.min_daily_cost_micros) {
996 let title = costs::bucket_title(bucket);
997 let detail = match drift.kind {
998 DriftKind::Count => format!(
One operation mapping, owned by repos; billing reads it instead of keeping its own999 "{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 daily1000 crate::features::thousands(drift.ours.max(0.0).round() as u64),
1001 crate::features::thousands(drift.cloudflare.max(0.0).round() as u64),
1002 drift.delta_percent.unwrap_or(0.0)
1003 ),
1004 DriftKind::Cost => format!(
1005 "{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.",
1006 dollars(drift.cloudflare as i64),
1007 dollars(drift.ours as i64),
1008 drift.delta_percent.unwrap_or(0.0)
1009 ),
1010 DriftKind::Leak if bucket == UNMAPPED => {
1011 format!("Cloudflare charged {} for meters no mapping claims. Map them on Costs & margin.", dollars(drift.cloudflare as i64))
1012 }
1013 DriftKind::Leak => format!(
1014 "{title}: Cloudflare charged {} over the last {DRIFT_DAYS} days and customers were charged nothing for it.",
1015 dollars(drift.cloudflare as i64)
1016 ),
1017 };
1018 found.push((drift, detail));
1019 }
1020 }
1021 let now = rfc3339(now_ms());
1022 let mut statements = vec![self.db.prepare("DELETE FROM cost_drift")];
1023 for (drift, detail) in &found {
1024 statements.push(
1025 self.db
1026 .prepare("INSERT OR REPLACE INTO cost_drift (bucket, kind, ours, cloudflare, delta_percent, detail, found_at) VALUES (?, ?, ?, ?, ?, ?, ?)")
1027 .bind(&[
1028 drift.bucket.as_str().into(),
1029 drift.kind.as_str().into(),
1030 drift.ours.into(),
1031 drift.cloudflare.into(),
1032 drift.delta_percent.map_or(JsValue::NULL, JsValue::from),
1033 detail.as_str().into(),
1034 now.as_str().into(),
1035 ])?,
1036 );
1037 }
1038 self.db.batch(statements).await?;
1039 Ok(found)
1040 }
1041
1042 /// Unit costs from the bill for mappings that scale to g1t's own count
1043 /// (git operations), proposed to the price book.
1044 async fn measure_units(&self, until: &str) -> Result<u32> {
1045 #[derive(Deserialize)]
1046 struct Scaled {
1047 product: String,
1048 meter: String,
1049 price_meter: String,
1050 own_meter: String,
1051 unit: Option<String>,
1052 }
1053 let scaled = self
1054 .db
1055 .prepare(
1056 "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
1057 WHERE m.scale_to_own = 1 AND m.price_meter IS NOT NULL AND m.own_meter IS NOT NULL",
1058 )
1059 .all()
1060 .await?
1061 .results::<Scaled>()?;
1062 let since = day_before(until, MEASURE_DAYS - 1);
1063 let rules = self.rules().await?;
1064 let mut proposed = 0;
1065 for s in scaled {
1066 #[derive(Deserialize)]
1067 struct Day {
1068 product: String,
1069 meter: String,
1070 quantity: f64,
1071 cost_usd: f64,
1072 }
1073 let lines = self
1074 .db
1075 .prepare("SELECT product, meter, quantity, cost_usd FROM cost_lines WHERE source = ?1 AND product = ?2 AND day >= ?3 AND day <= ?4")
1076 .bind(&[SOURCE_BILLABLE.into(), s.product.as_str().into(), since.as_str().into(), until.into()])?
1077 .all()
1078 .await?
1079 .results::<Day>()?;
1080 // Only the lines this very mapping claims.
1081 let mine: Vec<(f64, f64)> = lines
1082 .iter()
1083 .filter(|l| costs::classify(&rules, &l.product, &l.meter).is_some_and(|r| r.product == s.product && r.meter == s.meter))
1084 .map(|l| (l.quantity, l.cost_usd))
1085 .collect();
1086 let Some(rate) = billed_rate(&mine) else { continue };
1087 let cf_units: f64 = mine.iter().map(|(q, _)| q).sum();
1088 #[derive(Deserialize)]
1089 struct Own {
1090 total: Option<f64>,
1091 }
1092 let own_units = self
1093 .db
1094 .prepare("SELECT SUM(quantity) AS total FROM own_counts WHERE meter = ?1 AND day >= ?2 AND day <= ?3")
1095 .bind(&[s.own_meter.as_str().into(), since.as_str().into(), until.into()])?
1096 .first::<Own>(None)
1097 .await?
1098 .and_then(|o| o.total)
1099 .unwrap_or(0.0);
1100 let Some(per_unit) = derived_unit_cost(rate, cf_units, own_units) else { continue };
1101 let size = unit_size(s.unit.as_deref().unwrap_or("1"));
1102 let measured = per_unit * size * 1_000_000.0;
1103 let reason = format!(
1104 "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 {})",
1105 rate * 1000.0,
1106 cf_units / own_units,
1107 crate::features::thousands(cf_units.round() as u64),
1108 crate::features::thousands(own_units.round() as u64)
1109 );
1110 if self.propose(&s.price_meter, measured, &reason, "reconciler").await?.is_some() {
1111 proposed += 1;
1112 }
1113 }
1114 Ok(proposed)
1115 }
1116
1117 /// Opens, updates and closes margin alerts, and emails staff about new
1118 /// ones (and open ones each week).
1119 async fn raise_alerts(&self, env: &Env, until: &str, drift: &[(Drift, String)]) -> Result<u32> {
1120 let settings = self.cost_settings().await?;
1121 let since = day_before(until, u64::from(settings.alert_days.max(1)) - 1);
1122 let days = self.margin_days(&since, until).await?;
1123 let mut conditions: Vec<(String, String, String, String)> = Vec::new();
1124 // Each product under the floor.
1125 let mut by: BTreeMap<String, Vec<(String, i64, i64)>> = BTreeMap::new();
1126 let mut all: BTreeMap<String, (i64, i64)> = BTreeMap::new();
1127 for d in &days {
1128 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 it1129 // What g1t gave away (comped workspaces, free periods, the
1130 // trial and the pools) is a budget it chose to spend, watched on
1131 // 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 daily1132 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 running1133 overall.1 += (d.cost() - d.given.total()).max(0);
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1134 if !OVERHEAD.contains(&d.bucket.as_str()) && d.bucket != UNMAPPED {
1135 by.entry(d.bucket.clone()).or_default().push((d.day.clone(), d.value_micros, d.cost()));
1136 }
1137 }
1138 let floor = settings.margin_floor_percent;
1139 let n = settings.alert_days as usize;
1140 for (bucket, series) in &by {
1141 if let Some((from, worst)) = breach(series, floor, n, settings.min_daily_cost_micros) {
1142 conditions.push((
1143 "margin".into(),
1144 bucket.clone(),
1145 format!("{}: margin under {floor:.0}% for {n} days running, as low as {worst:.1}%.", costs::bucket_title(bucket)),
1146 from,
1147 ));
Margin alerts measure what is sold, and say dollars when a percentage would mislead1148 }
1149 }
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1150 let series: Vec<(String, i64, i64)> = all.into_iter().map(|(day, (revenue, cost))| (day, revenue, cost)).collect();
1151 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 mislead1152 let tail = &series[series.len().saturating_sub(n)..];
1153 let (took, spent) = tail.iter().fold((0i64, 0i64), |(r, c), (_, revenue, cost)| (r + revenue, c + cost));
1154 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 daily1155 }
1156 for (d, detail) in drift {
1157 let kind = if d.kind == DriftKind::Leak { "leak" } else { "drift" };
1158 conditions.push((kind.into(), format!("{}:{}", d.bucket, d.kind.as_str()), detail.clone(), until.to_owned()));
1159 }
1160 // Workspaces costing more than they pay.
1161 for (workspace, cost, revenue) in self.workspace_anomalies(until, &settings).await? {
1162 conditions.push((
1163 "workspace".into(),
1164 workspace.clone(),
Margin alerts measure what is sold, and say dollars when a percentage would mislead1165 format!(
1166 "{workspace} cost g1t {} on Cloudflare over {ANOMALY_DAYS} days, and its usage was priced at {}: its prices are below cost.",
1167 dollars(cost),
1168 dollars(revenue)
1169 ),
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1170 day_before(until, ANOMALY_DAYS - 1),
1171 ));
1172 }
1173
1174 let open = self
1175 .db
1176 .prepare("SELECT * FROM margin_alerts WHERE resolved_at IS NULL")
1177 .all()
1178 .await?
1179 .results::<AlertRow>()?;
1180 let now = now_ms();
1181 let stamp = rfc3339(now);
1182 let mut to_email: Vec<String> = Vec::new();
1183 let mut kept: BTreeSet<String> = BTreeSet::new();
1184 for (kind, subject, detail, from) in &conditions {
1185 match open.iter().find(|a| &a.kind == kind && &a.subject == subject) {
1186 Some(alert) => {
1187 kept.insert(alert.id.clone());
1188 self.db
1189 .prepare("UPDATE margin_alerts SET detail = ? WHERE id = ?")
1190 .bind(&[detail.as_str().into(), alert.id.as_str().into()])?
1191 .run()
1192 .await?;
1193 let stale = alert
1194 .emailed_at
1195 .as_deref()
1196 .and_then(g1t_contracts::time::parse_rfc3339)
1197 .is_none_or(|at| now.saturating_sub(at) >= REMIND_MS);
1198 if stale && kind != "workspace" {
1199 to_email.push(format!("Still open: {detail}"));
1200 kept.insert(format!("email:{}", alert.id));
1201 }
1202 }
1203 None => {
1204 let id = new_id("mal", now);
1205 self.db
1206 .prepare("INSERT INTO margin_alerts (id, kind, subject, detail, since, opened_at) VALUES (?, ?, ?, ?, ?, ?)")
1207 .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()])?
1208 .run()
1209 .await?;
1210 kept.insert(id.clone());
Margin alerts measure what is sold, and say dollars when a percentage would mislead1211 // A workspace's is for Reach out, not the inbox.
1212 if kind != "workspace" {
1213 to_email.push(detail.clone());
1214 }
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1215 kept.insert(format!("email:{id}"));
1216 }
1217 }
1218 }
1219 for alert in &open {
1220 if !kept.contains(&alert.id) {
1221 self.db
1222 .prepare("UPDATE margin_alerts SET resolved_at = ? WHERE id = ?")
1223 .bind(&[stamp.as_str().into(), alert.id.as_str().into()])?
1224 .run()
1225 .await?;
1226 }
1227 }
1228 let to = env.var("COSTS_ALERT_EMAIL").map(|v| v.to_string()).unwrap_or_default();
1229 if !to_email.is_empty() && !to.trim().is_empty() {
1230 let subject = format!("[g1t costs] {} margin alert{}", to_email.len(), if to_email.len() == 1 { "" } else { "s" });
1231 match email_staff(env, to.trim(), &subject, &to_email).await {
1232 Ok(()) => {
1233 for marker in kept.iter().filter_map(|k| k.strip_prefix("email:")) {
1234 self.db
1235 .prepare("UPDATE margin_alerts SET emailed_at = ? WHERE id = ?")
1236 .bind(&[stamp.as_str().into(), marker.into()])?
1237 .run()
1238 .await?;
1239 }
1240 }
1241 Err(error) => worker::console_error!("could not email the margin alerts: {error}"),
1242 }
1243 }
1244 Ok(conditions.len() as u32)
1245 }
1246
1247 /// 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 mislead1248 /// Each day's cost shared out to comped workspaces.
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1249 async fn workspace_anomalies(&self, until: &str, settings: &CostSettings) -> Result<Vec<(String, i64, i64)>> {
1250 #[derive(Deserialize)]
1251 struct Row {
1252 workspace: String,
1253 cost: Option<i64>,
1254 revenue: Option<i64>,
1255 }
1256 let rows = self
1257 .db
1258 .prepare(format!(
Margin alerts measure what is sold, and say dollars when a percentage would mislead1259 // Against what its usage was priced at, not the cash it
1260 // paid: a trial or a gift paying for usage is not a price
1261 // below cost.
The workspace cost alert compares only days that carry their value, not the days before it was kept1262 // Days from before value_micros was kept have none: only days
1263 // since the first one that does are compared.
Margin alerts measure what is sold, and say dollars when a percentage would mislead1264 "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 kept1265 WHERE day >= ?1 AND day <= ?2 AND workspace NOT IN ({})
1266 AND day >= (SELECT MIN(day) FROM workspace_costs WHERE value_micros > 0)
1267 GROUP BY workspace",
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1268 crate::sales::INTERNAL_SQL
1269 ))
1270 .bind(&[day_before(until, ANOMALY_DAYS - 1).into(), until.into()])?
1271 .all()
1272 .await?
1273 .results::<Row>()?;
1274 let rows: Vec<(String, i64, i64)> = rows.into_iter().map(|r| (r.workspace, r.cost.unwrap_or(0), r.revenue.unwrap_or(0))).collect();
1275 Ok(anomalies(&rows, settings.anomaly_factor, settings.anomaly_floor_micros))
1276 }
1277
1278 /// For Reach out: workspaces with an open cost-over-revenue alert,
1279 /// each with its detail and cost.
1280 pub(crate) async fn costing_more_than_they_pay(&self) -> Result<Vec<(String, String, i64)>> {
1281 let alerts = self
1282 .db
1283 .prepare("SELECT * FROM margin_alerts WHERE resolved_at IS NULL AND kind = 'workspace' ORDER BY opened_at DESC LIMIT 50")
1284 .all()
1285 .await?
1286 .results::<AlertRow>()?;
1287 let mut out = Vec::new();
1288 for alert in alerts {
1289 #[derive(Deserialize)]
1290 struct Cost {
1291 cost: Option<i64>,
1292 }
1293 let cost = self
1294 .db
1295 .prepare("SELECT SUM(cost_micros) AS cost FROM workspace_costs WHERE workspace = ? AND day >= ?")
1296 .bind(&[alert.subject.as_str().into(), alert.since.as_str().into()])?
1297 .first::<Cost>(None)
1298 .await?
1299 .and_then(|c| c.cost)
1300 .unwrap_or(0);
1301 out.push((alert.subject, alert.detail, cost));
1302 }
1303 Ok(out)
1304 }
1305
1306 /// `admin_cost_alerts`: what sudo's banner says.
1307 pub(crate) async fn admin_cost_alerts(&self, _: AdminCostAlertsArgs) -> Result<Vec<MarginAlert>> {
1308 Ok(self
1309 .db
1310 .prepare("SELECT * FROM margin_alerts WHERE resolved_at IS NULL ORDER BY opened_at DESC LIMIT 50")
1311 .all()
1312 .await?
1313 .results::<AlertRow>()?
1314 .into_iter()
1315 .map(MarginAlert::from)
1316 .collect())
1317 }
1318
1319 /// `admin_run_costs`: the daily run, now.
1320 pub(crate) async fn admin_run_costs(&self, env: &Env, a: AdminRunCostsArgs) -> Result<Outcome<CostsRun>> {
1321 let keeper = crate::keeper::Keeper::from_env(env);
1322 let run = self.costs_daily(env, &keeper).await?;
1323 if !a.by.is_empty() {
1324 self.audit(
1325 "costs",
1326 "costs_run",
1327 &format!("{} lines, {} days, {} proposals, {} alerts", run.lines, run.days, run.proposals, run.alerts),
1328 &a.by,
1329 )
1330 .await?;
1331 }
1332 Ok(Outcome::Ok(run))
1333 }
1334
1335 /// `admin_set_cost_mapping`.
1336 pub(crate) async fn admin_set_cost_mapping(&self, a: AdminSetCostMappingArgs) -> Result<Outcome<CostMapping>> {
1337 let product = costs::slug(&a.product);
1338 let meter = if a.meter.trim() == "*" { "*".to_owned() } else { costs::slug(&a.meter) };
1339 if product.is_empty() || meter.is_empty() {
1340 return Ok(Outcome::fail(FailureCode::Invalid, "Name Cloudflare's product and a meter (or * for all of it)."));
1341 }
1342 let now = rfc3339(now_ms());
1343 if a.remove {
1344 self.db
1345 .prepare("DELETE FROM cost_map WHERE product = ? AND meter = ?")
1346 .bind(&[product.as_str().into(), meter.as_str().into()])?
1347 .run()
1348 .await?;
1349 self.audit("costs", "cost_mapping_removed", &format!("{product}/{meter}"), &a.by).await?;
1350 return Ok(Outcome::Ok(CostMapping {
1351 product,
1352 meter,
1353 bucket: String::new(),
1354 price_meter: None,
1355 own_meter: None,
1356 scale_to_own: false,
1357 drift_percent: 0.0,
1358 note: String::new(),
1359 updated_at: now,
1360 updated_by: a.by,
1361 }));
1362 }
1363 let bucket = costs::slug(&a.bucket);
1364 if bucket.is_empty() {
1365 return Ok(Outcome::fail(FailureCode::Invalid, "Say which of g1t's products it is a cost of."));
1366 }
1367 let clean = |v: Option<String>| v.map(|v| v.trim().to_owned()).filter(|v| !v.is_empty());
1368 let (price_meter, own_meter) = (clean(a.price_meter), clean(a.own_meter));
1369 let drift = a.drift_percent.filter(|d| d.is_finite() && *d > 0.0).unwrap_or(10.0);
1370 self.db
1371 .prepare(
1372 "INSERT INTO cost_map (product, meter, bucket, price_meter, own_meter, scale_to_own, drift_percent, note, updated_at, updated_by)
1373 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)
1374 ON CONFLICT (product, meter) DO UPDATE SET bucket = ?3, price_meter = ?4, own_meter = ?5, scale_to_own = ?6,
1375 drift_percent = ?7, note = ?8, updated_at = ?9, updated_by = ?10",
1376 )
1377 .bind(&[
1378 product.as_str().into(),
1379 meter.as_str().into(),
1380 bucket.as_str().into(),
1381 crate::optional(price_meter.as_deref()),
1382 crate::optional(own_meter.as_deref()),
1383 i32::from(a.scale_to_own).into(),
1384 drift.into(),
1385 a.note.trim().into(),
1386 now.as_str().into(),
1387 a.by.as_str().into(),
1388 ])?
1389 .run()
1390 .await?;
1391 self.audit("costs", "cost_mapping", &format!("{product}/{meter} → {bucket}"), &a.by).await?;
1392 Ok(Outcome::Ok(CostMapping {
1393 product,
1394 meter,
1395 bucket,
1396 price_meter,
1397 own_meter,
1398 scale_to_own: a.scale_to_own,
1399 drift_percent: drift,
1400 note: a.note.trim().to_owned(),
1401 updated_at: now,
1402 updated_by: a.by,
1403 }))
1404 }
1405
1406 /// `admin_costs`: the Costs & margin page.
1407 pub(crate) async fn admin_costs(&self, a: AdminCostsArgs, configured: bool) -> Result<CostsReport> {
1408 let until = rfc3339(now_ms())[..10].to_owned();
1409 let span = u64::from(a.days.unwrap_or(30).clamp(7, 90));
1410 let since = day_before(&until, span - 1);
1411 let days = self.margin_days(&since, &until).await?;
1412 let rules = self.rules().await?;
1413
1414 let mut products: BTreeMap<String, ProductMargin> = BTreeMap::new();
1415 let mut overall = OverallMargin::default();
1416 for d in &days {
1417 let p = products.entry(d.bucket.clone()).or_insert_with(|| ProductMargin {
1418 bucket: d.bucket.clone(),
1419 title: costs::bucket_title(&d.bucket),
1420 cost_source: if NOT_CLOUDFLARE.contains(&d.bucket.as_str()) { "ledger" } else { "cloudflare" }.into(),
1421 overhead: OVERHEAD.contains(&d.bucket.as_str()),
1422 ..ProductMargin::default()
1423 });
1424 p.cf_cost_micros += d.cf_cost_micros;
1425 p.own_cost_micros += d.own_cost_micros;
1426 p.value_micros += d.value_micros;
1427 p.cost_micros += d.cost();
1428 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 running1429 overall.given_micros += d.given.total();
1430 if NOT_CLOUDFLARE.contains(&d.bucket.as_str()) {
1431 overall.models_cost_micros += d.cost();
1432 } else {
1433 overall.cloudflare_cost_micros += d.cost();
1434 }
1435 overall.given_comped_micros += d.given.comped;
1436 overall.given_free_micros += d.given.free;
1437 overall.given_trial_micros += d.given.trial;
1438 overall.given_pool_micros += d.given.pool;
1439 let sold = (d.cost() - d.given.total()).max(0);
1440 if OVERHEAD.contains(&d.bucket.as_str()) {
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1441 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 running1442 overall.running_cost_micros += sold;
1443 } else if d.bucket == UNMAPPED {
1444 overall.usage_micros += d.cash_micros;
1445 overall.unmapped_cost_micros += sold;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1446 } else {
1447 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 running1448 overall.usage_cost_micros += sold;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1449 }
1450 }
1451 for p in products.values_mut() {
1452 p.margin_micros = p.value_micros - p.cost_micros;
1453 p.margin_percent = margin_percent(p.value_micros, p.cost_micros);
1454 }
1455 let revenue = overall.usage_micros + overall.plans_micros;
1456 overall.margin_micros = revenue - overall.cost_micros;
1457 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 it1458 let sold = (overall.cost_micros - overall.given_micros).max(0);
1459 overall.sold_margin_micros = revenue - sold;
1460 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)1461 // The plan's included usage was paid for by the plan's price: it is
1462 // money in for the usage it covered, and out of what the plans
1463 // leave for running g1t.
1464 #[derive(Deserialize)]
1465 struct Included {
1466 micros: Option<i64>,
1467 }
1468 overall.included_micros = self
1469 .db
1470 .prepare(format!(
1471 "SELECT SUM(COALESCE(credit_micros, 0)) AS micros FROM ledger
1472 WHERE kind = 'usage' AND created_at >= ?1 AND created_at <= ?2 AND workspace NOT IN ({})",
1473 crate::sales::INTERNAL_SQL
1474 ))
1475 .bind(&[since.as_str().into(), format!("{until}T23:59:59.999Z").into()])?
1476 .first::<Included>(None)
1477 .await?
1478 .and_then(|r| r.micros)
1479 .unwrap_or(0);
1480 let usage_in = overall.usage_micros + overall.included_micros;
1481 overall.usage_margin_micros = usage_in - overall.usage_cost_micros;
1482 overall.usage_margin_percent = margin_percent(usage_in, overall.usage_cost_micros);
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1483 let mut products: Vec<ProductMargin> = products.into_values().collect();
1484 products.sort_by_key(|p| std::cmp::Reverse(p.cost_micros.max(p.value_micros)));
1485
1486 #[derive(Deserialize)]
1487 struct DriftRow {
1488 bucket: String,
1489 kind: String,
1490 ours: f64,
1491 cloudflare: f64,
1492 delta_percent: Option<f64>,
1493 detail: String,
1494 found_at: String,
1495 }
1496 let drift = self
1497 .db
1498 .prepare("SELECT * FROM cost_drift ORDER BY kind, bucket")
1499 .all()
1500 .await?
1501 .results::<DriftRow>()?
1502 .into_iter()
1503 .map(|r| CostDrift {
1504 title: costs::bucket_title(&r.bucket),
1505 bucket: r.bucket,
1506 kind: r.kind,
1507 ours: r.ours,
1508 cloudflare: r.cloudflare,
1509 delta_percent: r.delta_percent,
1510 detail: r.detail,
1511 found_at: r.found_at,
1512 })
1513 .collect();
1514
1515 #[derive(Deserialize)]
1516 struct Top {
1517 workspace: String,
1518 cost: Option<i64>,
1519 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 it1520 given: Option<i64>,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1521 internal: i64,
1522 }
1523 let top_workspaces = self
1524 .db
1525 .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 it1526 "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 daily1527 CASE WHEN workspace IN ({}) THEN 1 ELSE 0 END AS internal
1528 FROM workspace_costs WHERE day >= ?1 AND day <= ?2 GROUP BY workspace ORDER BY cost DESC LIMIT 15",
1529 crate::sales::INTERNAL_SQL
1530 ))
1531 .bind(&[since.as_str().into(), until.as_str().into()])?
1532 .all()
1533 .await?
1534 .results::<Top>()?
1535 .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 it1536 .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 daily1537 .collect();
1538
1539 #[derive(Deserialize)]
1540 struct Summary {
1541 source: String,
1542 product: String,
1543 meter: String,
1544 raw_name: String,
1545 unit: String,
1546 quantity: f64,
1547 cost_usd: f64,
1548 }
1549 let lines = self
1550 .db
1551 .prepare(
1552 "SELECT source, product, meter, MAX(raw_name) AS raw_name, MAX(unit) AS unit, SUM(quantity) AS quantity, SUM(cost_usd) AS cost_usd
1553 FROM cost_lines WHERE day >= ?1 AND day <= ?2 GROUP BY source, product, meter ORDER BY cost_usd DESC, product, meter LIMIT 200",
1554 )
1555 .bind(&[since.as_str().into(), until.as_str().into()])?
1556 .all()
1557 .await?
1558 .results::<Summary>()?
1559 .into_iter()
1560 .map(|l| CostLineSummary {
1561 bucket: costs::classify(&rules, &l.product, &l.meter).map(|r| r.bucket.clone()),
1562 product: l.product,
1563 meter: l.meter,
1564 raw_name: l.raw_name,
1565 unit: l.unit,
1566 source: l.source,
1567 quantity: l.quantity,
1568 cost_micros: micros(l.cost_usd),
1569 })
1570 .collect();
1571
1572 #[derive(Deserialize)]
1573 struct MapRow {
1574 product: String,
1575 meter: String,
1576 bucket: String,
1577 price_meter: Option<String>,
1578 own_meter: Option<String>,
1579 scale_to_own: i64,
1580 drift_percent: f64,
1581 note: String,
1582 updated_at: String,
1583 updated_by: String,
1584 }
1585 let mappings = self
1586 .db
1587 .prepare("SELECT * FROM cost_map ORDER BY product, meter")
1588 .all()
1589 .await?
1590 .results::<MapRow>()?
1591 .into_iter()
1592 .map(|m| CostMapping {
1593 product: m.product,
1594 meter: m.meter,
1595 bucket: m.bucket,
1596 price_meter: m.price_meter,
1597 own_meter: m.own_meter,
1598 scale_to_own: m.scale_to_own == 1,
1599 drift_percent: m.drift_percent,
1600 note: m.note,
1601 updated_at: m.updated_at,
1602 updated_by: m.updated_by,
1603 })
1604 .collect();
1605
1606 #[derive(Deserialize)]
1607 struct Fetched {
1608 at: Option<String>,
1609 }
1610 let fetched_at = self.db.prepare("SELECT MAX(fetched_at) AS at FROM cost_lines").first::<Fetched>(None).await?.and_then(|f| f.at);
1611
1612 Ok(CostsReport {
1613 configured,
1614 fetched_at,
1615 days: days
1616 .iter()
1617 .map(|d| CostDay {
1618 day: d.day.clone(),
1619 bucket: d.bucket.clone(),
1620 cf_cost_micros: d.cf_cost_micros,
1621 own_cost_micros: d.own_cost_micros,
1622 value_micros: d.value_micros,
1623 cash_micros: d.cash_micros,
1624 })
1625 .collect(),
1626 since,
1627 until,
1628 products,
1629 overall,
1630 drift,
1631 alerts: self.admin_cost_alerts(AdminCostAlertsArgs {}).await?,
1632 proposals: self.proposals().await?,
1633 versions: self.versions().await?,
1634 top_workspaces,
1635 lines,
1636 mappings,
1637 settings: self.cost_settings().await?,
Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays1638 caps: self.spend_caps().await?,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1639 })
1640 }
1641}
1642
1643#[cfg(test)]
1644mod tests {
1645 use super::*;
1646
Margin alerts measure what is sold, and say dollars when a percentage would mislead1647 #[test]
Models' margin read -14%: usage nothing paid for is valued at price, not $01648 fn usage_nothing_paid_for_is_valued_at_price_and_paid_usage_at_what_was_paid() {
1649 // A free period: charged nothing, drawn from nothing.
1650 assert_eq!(usage_value(false, 1_000_000, 0, 20), 1_200_000);
1651 // Charged, or drawn from a trial: what was paid.
1652 assert_eq!(usage_value(false, 1_000_000, 1_200_000, 20), 1_200_000);
1653 assert_eq!(usage_value(false, 1_000_000, 900_000, 20), 900_000);
1654 // g1t's own: at price.
1655 assert_eq!(usage_value(true, 1_000_000, 0, 20), 1_200_000);
1656 // No cost, nothing paid: nothing.
1657 assert_eq!(usage_value(false, 0, 0, 20), 0);
1658 }
1659
1660 #[test]
Margin alerts measure what is sold, and say dollars when a percentage would mislead1661 fn the_overall_alert_says_dollars_while_little_comes_in() {
1662 let small = overall_detail(90_000, 7_500_000, 3, 10.0, -8239.7);
1663 assert!(small.contains("took in $0.09 against $7.50"), "{small}");
1664 assert!(!small.contains('%'), "{small}");
1665 let real = overall_detail(30_000_000, 40_000_000, 3, 10.0, -33.3);
1666 assert!(real.contains("as low as -33.3%"), "{real}");
1667 }
1668
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1669 fn rule(product: &str, meter: &str, bucket: &str, own: Option<&str>) -> Rule {
1670 Rule { product: product.into(), meter: meter.into(), bucket: bucket.into(), price_meter: None, own_meter: own.map(Into::into), drift_percent: 10.0 }
1671 }
1672
1673 fn rules() -> Vec<Rule> {
1674 vec![
1675 rule("containers", "*", "sandboxes", None),
1676 rule("workers", "*", "platform", None),
1677 rule("artifacts", "*", "git", Some("git_operations")),
1678 rule("artifacts", "events_", "git", Some("git_operations")),
1679 ]
1680 }
1681
1682 fn revenue_map() -> BTreeMap<String, String> {
1683 [("sandbox", "sandboxes"), ("git", "git"), ("plan", "platform")].iter().map(|(k, v)| (k.to_string(), v.to_string())).collect()
1684 }
1685
1686 fn line(day: &str, source: &str, product: &str, meter: &str, quantity: f64, cost: f64) -> LineRow {
1687 LineRow { day: day.into(), source: source.into(), product: product.into(), meter: meter.into(), quantity, cost_usd: cost }
1688 }
1689
1690 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 running1691 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 daily1692 }
1693
1694 #[test]
1695 fn a_day_puts_the_bill_g1ts_counts_and_charges_side_by_side() {
1696 let lines = vec![
1697 line("2026-10-15", SOURCE_BILLABLE, "containers", "container_memory", 1000.0, 2.00),
1698 line("2026-10-15", SOURCE_BILLABLE, "artifacts", "artifacts_operations", 30_000.0, 3.00),
1699 // Artifacts' own events: not used while the bill has a count.
1700 line("2026-10-15", SOURCE_ARTIFACTS, "artifacts", "events_pull", 29_000.0, 0.0),
1701 line("2026-10-15", SOURCE_BILLABLE, "workers", "workers_cpu_ms", 1.0, 0.50),
1702 line("2026-10-15", SOURCE_BILLABLE, "browser_rendering", "browser_hours", 2.0, 0.25),
1703 ];
1704 let own = vec![
1705 OwnRow { day: "2026-10-15".into(), meter: "git_operations".into(), workspace: "acme".into(), quantity: 7_500.0 },
1706 OwnRow { day: "2026-10-15".into(), meter: "git_operations".into(), workspace: "beta".into(), quantity: 2_500.0 },
1707 ];
1708 let usage = vec![
1709 usage("2026-10-15", "acme", "sandbox", 2_400_000, 1_000_000, 2_000_000),
1710 usage("2026-10-15", "beta", "sandbox", 1_200_000, 1_200_000, 1_000_000),
1711 usage("2026-10-15", "acme", "git", 600_000, 600_000, 500_000),
1712 usage("2026-10-15", "acme", "implement", 120_000, 120_000, 100_000),
1713 usage("2026-10-15", "beta", "plan", 20_000_000, 20_000_000, 0),
1714 ];
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 running1715 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 daily1716 let get = |bucket: &str| days.iter().find(|d| d.bucket == bucket).unwrap();
1717 let sandboxes = get("sandboxes");
1718 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));
1719 let git = get("git");
1720 assert_eq!(git.cf_cost_micros, 3_000_000);
1721 assert_eq!((git.cf_quantity, git.own_quantity), (30_000.0, 10_000.0));
1722 assert_eq!(get("platform").value_micros, 20_000_000);
1723 // Not mapped: a leak until someone maps it.
1724 assert_eq!(get(UNMAPPED).cf_cost_micros, 250_000);
1725 // Models: no Cloudflare line, their cost is g1t's own.
1726 assert_eq!(get("models").cost(), 100_000);
1727 // Git's cost shared by g1t's own counts (Cloudflare gave none per
1728 // workspace here): three quarters to acme.
1729 let share = |ws: &str, bucket: &str| workspaces.iter().find(|w| w.workspace == ws && w.bucket == bucket).map(|w| (w.cost, w.revenue));
1730 assert_eq!(share("acme", "git"), Some((2_250_000, 600_000)));
1731 assert_eq!(share("beta", "git"), Some((750_000, 0)));
1732 // Every bucket's cost is shared out exactly.
1733 for d in &days {
1734 let shared: i64 = workspaces.iter().filter(|w| w.bucket == d.bucket).map(|w| w.cost).sum();
1735 assert_eq!(shared, d.cost(), "{}", d.bucket);
1736 }
1737 }
1738
1739 #[test]
1740 fn artifacts_events_count_when_the_bill_does_not() {
1741 let lines = vec![
1742 line("2026-10-05", SOURCE_ARTIFACTS, "artifacts", "events_pull", 120.0, 0.0),
1743 line("2026-10-05", SOURCE_ARTIFACTS, "artifacts", "events_push", 30.0, 0.0),
1744 line("2026-10-05", SOURCE_ARTIFACTS, "artifacts", "events_ratelimited", 9.0, 0.0),
1745 ];
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 running1746 let (days, _) = fold(&rules(), &revenue_map(), &lines, &[], &[], &BTreeSet::new());
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1747 assert_eq!(days[0].cf_quantity, 150.0);
1748 assert_eq!(days[0].cf_cost_micros, 0);
1749 }
1750
1751 #[test]
1752 fn month_end_meters_are_told_by_the_day_from_snapshots() {
1753 let snap = |day: &str, cost: i64, charge: i64| (day.to_string(), "acme".to_string(), "git".to_string(), cost, charge);
1754 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)]);
1755 assert_eq!(
1756 rows.iter().map(|r| (r.day.as_str(), r.cost, r.value)).collect::<Vec<_>>(),
1757 vec![("2026-10-30", 100, 120), ("2026-10-31", 150, 180), ("2026-11-01", 40, 48)]
1758 );
1759 }
1760
1761 #[test]
1762 fn a_plan_payment_is_spread_over_the_month_it_pays_for() {
1763 let days = spread("2026-10-01T00:00:00.000Z", 20_000_000, 30);
1764 assert_eq!(days.len(), 30);
1765 assert_eq!(days[0], ("2026-10-01".to_string(), 666_667));
1766 assert_eq!(days[29], ("2026-10-30".to_string(), 666_666));
1767 assert_eq!(days.iter().map(|d| d.1).sum::<i64>(), 20_000_000);
1768 assert!(spread("2026-10-01", 0, 30).is_empty());
1769 assert_eq!(dollars(17_024_000), "$17.02");
1770 assert_eq!(dollars(-27_668_620), "-$27.67");
1771 assert_eq!(dollars(63_000), "$0.063");
1772 }
1773
1774 #[test]
1775 fn margins_and_deltas() {
1776 assert_eq!(margin_percent(1_200_000, 1_000_000).map(|m| (m * 100.0).round() / 100.0), Some(16.67));
1777 assert_eq!(margin_percent(0, 5), None);
1778 assert_eq!(delta_percent(110.0, 100.0), Some(10.0));
1779 assert_eq!(delta_percent(1.0, 0.0), None);
1780 }
1781
1782 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 running1783 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 daily1784 }
1785
1786 #[test]
1787 fn counts_more_than_the_threshold_apart_are_drift() {
1788 // Cloudflare counted 30,000 operations where g1t counted 10,000:
1789 // binding reads, perhaps. -66.7%.
1790 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);
1791 assert_eq!(drift.iter().map(|d| d.kind).collect::<Vec<_>>(), vec![DriftKind::Count, DriftKind::Cost]);
1792 assert!((drift[0].delta_percent.unwrap() + 66.666).abs() < 0.01);
1793 // 9% apart: within 10%.
1794 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());
1795 // Uncounted products have no count drift.
1796 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());
1797 }
1798
1799 #[test]
1800 fn cost_with_no_revenue_is_a_leak_but_not_for_running_g1t() {
1801 let leak = drifts("actions_cache", &[day("actions_cache", 400_000, 0, 0, 0.0, 0.0)], 10.0, false, 100_000);
1802 assert_eq!(leak.len(), 1);
1803 assert_eq!(leak[0].kind, DriftKind::Leak);
1804 assert!(drifts("platform", &[day("platform", 5_000_000, 0, 0, 0.0, 0.0)], 10.0, false, 100_000).is_empty());
1805 // Pennies say nothing.
1806 assert!(drifts("actions_cache", &[day("actions_cache", 50_000, 0, 0, 0.0, 0.0)], 10.0, false, 100_000).is_empty());
1807 assert!(drifts(UNMAPPED, &[day(UNMAPPED, 250_000, 0, 0, 0.0, 0.0)], 10.0, false, 100_000)[0].kind == DriftKind::Leak);
1808 }
1809
1810 #[test]
1811 fn a_margin_alert_needs_n_days_in_a_row_under_the_floor() {
1812 let s = |d: &str, revenue: i64, cost: i64| (d.to_string(), revenue, cost);
1813 // 5%, 0%, -20%: three days under 10%.
1814 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)];
1815 let (from, worst) = breach(&series, 10.0, 3, 100_000).unwrap();
1816 assert_eq!(from, "10-14");
1817 assert!((worst + 20.0).abs() < 1e-9);
1818 // A good day in the window clears it.
1819 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)];
1820 assert!(breach(&mended, 10.0, 3, 100_000).is_none());
1821 // Cost with no revenue at all is the worst margin there is.
1822 assert_eq!(breach(&[s("10-16", 0, 500_000)], 10.0, 1, 100_000).unwrap().1, -100.0);
1823 // Too little cost to judge.
1824 assert!(breach(&[s("10-16", 0, 5_000)], 10.0, 1, 100_000).is_none());
1825 assert!(breach(&series, 10.0, 9, 100_000).is_none());
1826 }
1827
1828 #[test]
1829 fn shared_costs_add_up_to_the_bill() {
1830 let w = |k: &str, v: f64| (k.to_string(), v);
1831 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)]);
1832 assert_eq!(attribute(10, &[w("a", 3.0), w("b", 1.0), w("a", 0.0)]), vec![("a".into(), 8), ("b".into(), 2)]);
1833 assert!(attribute(10, &[w("a", 0.0)]).is_empty());
1834 assert!(attribute(0, &[w("a", 1.0)]).is_empty());
1835 }
1836
1837 #[test]
Costs: counts are compared with Cloudflare's from the day g1t started counting, so days before a meter existed are not drift1838 fn counts_are_compared_from_the_day_g1t_started_counting() {
1839 let on = |day: &str, cf: f64, own: f64| ProductDay { day: day.into(), bucket: "git".into(), cf_quantity: cf, own_quantity: own, ..ProductDay::default() };
1840 // Five days of Cloudflare's count before g1t's meter, then two that match.
1841 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)];
1842 assert!(drifts("git", &days, 10.0, true, 0).iter().all(|d| d.kind != DriftKind::Count));
1843 // A real gap on the days both counted still shows.
1844 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)];
1845 let found = drifts("git", &days, 10.0, true, 0);
1846 let count = found.iter().find(|d| d.kind == DriftKind::Count).unwrap();
1847 assert_eq!((count.ours, count.cloudflare), (531.0, 1000.0));
1848 // A meter that never counted is compared over every day.
1849 let days = vec![on("2026-10-06", 400.0, 0.0)];
1850 assert!(drifts("git", &days, 10.0, true, 0).iter().any(|d| d.kind == DriftKind::Count));
1851 }
1852
1853 #[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 it1854 fn what_g1t_gives_away_is_kept_apart_from_what_it_sells() {
1855 let map = BTreeMap::new();
1856 // A comped workspace (all of it given), one in its trial (half paid
1857 // 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 running1858 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 it1859 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 running1860 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 it1861 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 running1862 // Nothing priced that day: free use.
1863 let free = usage("2026-10-15", "gamma", "agent", 0, 0, 1_000_000);
1864 let internal = BTreeSet::from(["flagon".to_string()]);
1865 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 it1866 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 running1867 assert_eq!(models.cost(), 4_000_000);
1868 assert_eq!(models.given, Given { comped: 1_000_000, free: 1_000_000, trial: 500_000, pool: 0 });
1869 let given = |w: &str| workspaces.iter().find(|x| x.workspace == w).unwrap().given.total();
1870 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 it1871 }
1872
1873 #[test]
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1874 fn a_workspace_that_costs_more_than_it_pays_is_flagged() {
1875 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)];
1876 let found = anomalies(&rows, 1.0, 1_000_000);
1877 assert_eq!(found, vec![("acme".to_string(), 5_000_000, 1_000_000)]);
1878 // At twice its revenue as the threshold, $5 against $3 is fine.
1879 assert!(anomalies(&[("acme".to_string(), 5_000_000, 3_000_000)], 2.0, 1_000_000).is_empty());
1880 }
1881
1882 #[test]
1883 fn a_git_operation_costs_what_cloudflare_counts_for_it() {
1884 // $0.15 per 1,000 of Cloudflare's operations, on the charged days.
1885 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();
1886 assert!((rate - 0.000_15).abs() < 1e-12);
1887 // Cloudflare counted 3 for every 1 g1t did: binding reads count.
1888 let per_op = derived_unit_cost(rate, 300_000.0, 100_000.0).unwrap();
1889 let per_thousand_micros = per_op * unit_size("1,000 operations") * 1e6;
1890 assert!((per_thousand_micros - 450_000.0).abs() < 1e-6, "{per_thousand_micros}");
1891 // Too few of g1t's units to say.
1892 assert!(derived_unit_cost(rate, 3_000.0, 500.0).is_none());
1893 assert!(billed_rate(&[(10_000.0, 0.0)]).is_none());
1894 assert_eq!(unit_size("million requests"), 1e6);
1895 assert_eq!(unit_size("second"), 1.0);
1896 }
1897}