Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
718 linesCodeBlameRaw
1//! What g1t pays for itself, and two caps on it.
2//!
3//! Every charge that settles (an agent run, sandbox time, a build) is
4//! split by what paid for it, at cost: a customer's real money, or g1t's.
5//! g1t's part goes to `g1t_spend` by day, bucket and billing account:
6//!
7//! - `comped`: work on a comped account (g1t's own, Flagon's), all of it.
8//! - `trial`, `oss`: the trial credit and the open-source pool.
9//! - `given`: a free workspace's overrun past its last bit of trial.
10//! - `unpaid`: charged, but with no real money behind it: Stripe's test
11//! key, or `FREE_WHILE_BUILDING`.
12//!
13//! The plan's included usage and on-demand charges with live payments are
14//! revenue, not g1t's. A workspace's own model provider costs g1t nothing.
15//!
16//! Two caps read it:
17//!
18//! 1. **A comped account's monthly budget**: `COMPED_MONTHLY_CEILING_MICROS`
19//! ($150), or the account's own limit in its terms (sudo, Accounts →
20//! Terms → Limit). Staff are emailed at 50, 75, 90 and 100%, once each a
21//! month; at 100% new work on it is refused until staff raise it or the
22//! month turns. Work already running finishes.
23//! 2. **The daily breaker**: when g1t's part across every workspace today
24//! (UTC) reaches `PLATFORM_DAILY_SPEND_CAP_MICROS` ($75), new compute
25//! that g1t would pay for (sandboxes for workflows, checks, the merge
26//! queue and agents alike) is paused for the rest of the day: everyone's
27//! except workspaces paying with real money on the plan or an enterprise
28//! contract, and comped accounts, which have their own monthly budget. Staff are emailed at once and sudo
29//! shows a red bar; staff can lift it for the day.
30//!
31//! Zero for either variable turns that cap off.
32
33use g1t_contracts::billing::{
34 AdminLiftBreakerArgs, BillingAccount, CompedBudget, ComputeKind, PlanKind, SpendBucket, SpendCaps,
35};
36use g1t_contracts::time::rfc3339;
37use g1t_contracts::{FailureCode, Outcome};
38use g1t_kit::now_ms;
39use serde::Deserialize;
40use worker::{Env, Result};
41
42use crate::Billing;
43use crate::credits::Drawn;
44use crate::limits::alert_level;
45
46/// The caps, from the billing service's variables.
47#[derive(Clone, Debug)]
48pub(crate) struct Caps {
49 /// `COMPED_MONTHLY_CEILING_MICROS`: a comped account's monthly budget
50 /// at cost, unless its terms set one. Zero: none.
51 pub comped_monthly: i64,
52 /// `PLATFORM_DAILY_SPEND_CAP_MICROS`: g1t's own spend a day before the
53 /// breaker trips. Zero: no breaker.
54 pub daily: i64,
55 /// `CLOUDFLARE_FIXED_MONTHLY_MICROS`: Cloudflare's subscriptions, an
56 /// estimate for sudo.
57 pub fixed_monthly: i64,
58 /// `COSTS_ALERT_EMAIL`. Empty: nothing is emailed.
59 pub alert_to: String,
60}
61
62impl Caps {
63 pub(crate) fn from_env(env: &Env) -> Self {
64 let number = |name: &str, default: i64| {
65 env.var(name).ok().and_then(|v| v.to_string().trim().parse::<i64>().ok()).unwrap_or(default).max(0)
66 };
67 Caps {
68 comped_monthly: number("COMPED_MONTHLY_CEILING_MICROS", 150_000_000),
69 daily: number("PLATFORM_DAILY_SPEND_CAP_MICROS", 75_000_000),
70 fixed_monthly: number("CLOUDFLARE_FIXED_MONTHLY_MICROS", 30_000_000),
71 alert_to: env.var("COSTS_ALERT_EMAIL").map(|v| v.to_string().trim().to_owned()).unwrap_or_default(),
72 }
73 }
74}
75
76/// g1t's part of one charge, at cost, by what paid for it.
77#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
78pub(crate) struct Share {
79 pub comped: i64,
80 pub trial: i64,
81 pub oss: i64,
82 pub given: i64,
83 pub unpaid: i64,
84}
85
86impl Share {
87 pub fn total(&self) -> i64 {
88 self.comped + self.trial + self.oss + self.given + self.unpaid
89 }
90
91 pub fn parts(&self) -> [(&'static str, i64); 5] {
92 [("comped", self.comped), ("trial", self.trial), ("oss", self.oss), ("given", self.given), ("unpaid", self.unpaid)]
93 }
94}
95
96/// What of a charge costing g1t `cost` g1t paid itself. `charged` is what
97/// the workspace was charged after `drawn` paid its part (both at price);
98/// `real_money` is whether payments are live. The plan's included usage
99/// and what the workspace is charged are revenue only with real money.
100pub(crate) fn share(cost: i64, charged: i64, drawn: &Drawn, comped: bool, real_money: bool) -> Share {
101 if cost <= 0 {
102 return Share::default();
103 }
104 if comped {
105 return Share { comped: cost, ..Share::default() };
106 }
107 let gross = charged.max(0) + drawn.total();
108 if gross <= 0 {
109 // Charged nothing at all (free while g1t is being built out).
110 return Share { unpaid: cost, ..Share::default() };
111 }
112 let part = |paid: i64| (i128::from(cost) * i128::from(paid.max(0)) / i128::from(gross)) as i64;
113 let (trial, oss, given) = (part(drawn.trial), part(drawn.oss), part(drawn.given));
114 let unpaid = if real_money { 0 } else { (cost - trial - oss - given).max(0) };
115 Share { comped: 0, trial, oss, given, unpaid }
116}
117
118/// A comped account's monthly budget: its own (terms' limit) or the
119/// default; and whether it is the default. Zero: none.
120pub(crate) fn comped_ceiling(own: Option<i64>, default: i64) -> (i64, bool) {
121 match own {
122 Some(own) => (own.max(0), false),
123 None => (default.max(0), true),
124 }
125}
126
127/// Whether a budget is used up.
128pub(crate) fn used_up(used: i64, ceiling: i64) -> bool {
129 ceiling > 0 && used >= ceiling
130}
131
132/// Whether the breaker stops new runs: on, reached, and not lifted today.
133pub(crate) fn breaker_open(today: i64, cap: i64, lifted: bool) -> bool {
134 cap > 0 && today >= cap && !lifted
135}
136
137/// Whether the breaker is about this start: any compute g1t would pay
138/// for. Every kind costs g1t sandbox time, so none is left out; a
139/// workspace whose spend is covered is left out by [`covered_by_revenue`]
140/// instead.
141pub(crate) fn breaker_applies(_kind: ComputeKind) -> bool {
142 true
143}
144
145/// Whether a workspace's spend is covered by revenue, so the breaker
146/// leaves it alone: live payments, not comped, and on the plan it pays for
147/// (not given it by staff) or an enterprise contract.
148pub(crate) fn covered_by_revenue(plan: PlanKind, comped: bool, plan_given: bool, live: bool) -> bool {
149 live && !comped && match plan {
150 PlanKind::Enterprise => true,
151 PlanKind::Paid => !plan_given,
152 _ => false,
153 }
154}
155
156/// The alert to send now: the level reached, if higher than any sent this
157/// month.
158pub(crate) fn alert_to_send(level: u32, sent: u32) -> Option<u32> {
159 (level > 0 && level > sent).then_some(level)
160}
161
162/// `$150.00`: whole cents.
163pub(crate) fn cents(micros: i64) -> String {
164 let cents = (micros as f64 / 10_000.0).round() as i64;
165 format!("{}${}.{:02}", if cents < 0 { "-" } else { "" }, cents.abs() / 100, cents.abs() % 100)
166}
167
168/// What a start on a comped account past its budget is told. Staff-only:
169/// only comped (g1t's own) accounts see it.
170pub(crate) fn comped_refusal(name: &str, used: i64, ceiling: i64) -> String {
171 format!(
172 "{name}'s monthly budget for g1t's own agents is used up ({} of {} this month at cost), so new runs wait. Staff can raise it in sudo: Accounts, {name}, Terms, Limit.",
173 cents(used),
174 cents(ceiling)
175 )
176}
177
178/// What a hosted-model start is told while the breaker is open.
179pub(crate) fn breaker_refusal(today: i64, cap: i64) -> String {
180 format!(
181 "g1t's daily spend breaker is open: g1t has paid {} of its {} a day for work today, so new runs g1t pays for wait until 00:00 UTC. Work on the paid plan still runs.",
182 cents(today),
183 cents(cap)
184 )
185}
186
187#[derive(Deserialize)]
188struct Sum {
189 micros: Option<i64>,
190}
191
192#[derive(Deserialize)]
193struct BreakerRow {
194 tripped_at: Option<String>,
195 told_at: Option<String>,
196 lifted_by: Option<String>,
197 lifted_at: Option<String>,
198 lift_note: Option<String>,
199}
200
201fn today() -> String {
202 rfc3339(now_ms())[..10].to_owned()
203}
204
205impl Billing {
206 fn live(&self) -> bool {
207 self.stripe.as_ref().is_some_and(crate::stripe::Stripe::live)
208 }
209
210 /// Counts g1t's part of a charge that just settled, and trips the
211 /// breaker if today reached its cap. Never fails the charge: a problem
212 /// here is logged.
213 pub(crate) async fn count_spend(&self, workspace: &str, cost: i64, charged: i64, drawn: &Drawn) {
214 if let Err(error) = self.try_count_spend(workspace, cost, charged, drawn).await {
215 worker::console_error!("could not count g1t's spend for {workspace}: {error}");
216 }
217 }
218
219 async fn try_count_spend(&self, workspace: &str, cost: i64, charged: i64, drawn: &Drawn) -> Result<()> {
220 if cost <= 0 {
221 return Ok(());
222 }
223 let account = self.account_of(workspace).await?;
224 let paid = share(cost, charged, drawn, account.terms.full_discount(), self.live());
225 if paid.total() == 0 {
226 return Ok(());
227 }
228 let day = today();
229 let mut writes = vec![];
230 for (bucket, micros) in paid.parts() {
231 if micros > 0 {
232 writes.push(
233 self.db
234 .prepare(
235 "INSERT INTO g1t_spend (day, bucket, account, micros) VALUES (?1, ?2, ?3, ?4)
236 ON CONFLICT (day, bucket, account) DO UPDATE SET micros = micros + ?4",
237 )
238 .bind(&[day.as_str().into(), bucket.into(), account.id.as_str().into(), (micros as f64).into()])?,
239 );
240 }
241 }
242 self.db.batch(writes).await?;
243 if self.caps.daily <= 0 {
244 return Ok(());
245 }
246 let total = self.spent_on(&day).await?;
247 if total < self.caps.daily {
248 return Ok(());
249 }
250 // Tripped: recorded once a day, and staff told at once.
251 let now = rfc3339(now_ms());
252 let tripped = self
253 .db
254 .prepare(
255 "INSERT INTO spend_breaker (day, tripped_at, tripped_micros) VALUES (?1, ?2, ?3)
256 ON CONFLICT (day) DO UPDATE SET tripped_at = ?2, tripped_micros = ?3 WHERE spend_breaker.tripped_at IS NULL
257 RETURNING day",
258 )
259 .bind(&[day.as_str().into(), now.as_str().into(), (total as f64).into()])?
260 .first::<serde_json::Value>(None)
261 .await?;
262 if tripped.is_some() {
263 self.tell_breaker(&day, total).await?;
264 }
265 Ok(())
266 }
267
268 /// g1t's own spend on `day`, across every workspace.
269 async fn spent_on(&self, day: &str) -> Result<i64> {
270 Ok(self
271 .db
272 .prepare("SELECT SUM(micros) AS micros FROM g1t_spend WHERE day = ?")
273 .bind(&[day.into()])?
274 .first::<Sum>(None)
275 .await?
276 .and_then(|s| s.micros)
277 .unwrap_or(0))
278 }
279
280 async fn breaker_row(&self, day: &str) -> Result<Option<BreakerRow>> {
281 self.db
282 .prepare("SELECT tripped_at, told_at, lifted_by, lifted_at, lift_note FROM spend_breaker WHERE day = ?")
283 .bind(&[day.into()])?
284 .first::<BreakerRow>(None)
285 .await
286 }
287
288 /// Emails staff that the breaker tripped, and notes it was told.
289 async fn tell_breaker(&self, day: &str, total: i64) -> Result<()> {
290 if self.caps.alert_to.is_empty() {
291 return Ok(());
292 }
293 let lifted = self.breaker_row(day).await?.and_then(|row| row.lifted_by);
294 let mut lines = vec![
295 format!(
296 "g1t paid {} for work today ({day}, UTC), its daily cap of {} (PLATFORM_DAILY_SPEND_CAP_MICROS). New compute that g1t pays for (workflows, checks, the merge queue and other runs) is paused until 00:00 UTC; workspaces paying with real money, and comped accounts, are not affected. Runs already going finish.",
297 cents(total),
298 cents(self.caps.daily)
299 ),
300 "To let them start again today: sudo, Costs & margin, Lift for today. To change the cap: PLATFORM_DAILY_SPEND_CAP_MICROS in services/billing/wrangler.jsonc.".to_owned(),
301 ];
302 if let Some(by) = lifted {
303 lines.insert(1, format!("{by} had already lifted it for today, so nothing is paused."));
304 }
305 match crate::margin::email_staff(&self.env, &self.caps.alert_to, &format!("g1t: the daily spend breaker tripped at {}", cents(total)), &lines).await {
306 Ok(()) => {
307 self.db
308 .prepare("UPDATE spend_breaker SET told_at = ? WHERE day = ?")
309 .bind(&[rfc3339(now_ms()).into(), day.into()])?
310 .run()
311 .await?;
312 }
313 Err(error) => worker::console_error!("could not email the breaker: {error}"),
314 }
315 Ok(())
316 }
317
318 /// Why a start is refused by the breaker, if it is.
319 pub(crate) async fn breaker_refuses(
320 &self,
321 plan: PlanKind,
322 account: &BillingAccount,
323 kind: ComputeKind,
324 ) -> Result<Option<String>> {
325 if self.caps.daily <= 0 || !breaker_applies(kind) {
326 return Ok(None);
327 }
328 let comped = account.terms.full_discount();
329 // A comped account is held by its own monthly budget instead.
330 if comped || covered_by_revenue(plan, comped, account.allowances.plan, self.live()) {
331 return Ok(None);
332 }
333 let day = today();
334 let spent = self.spent_on(&day).await?;
335 if spent < self.caps.daily {
336 return Ok(None);
337 }
338 let lifted = self.breaker_row(&day).await?.is_some_and(|row| row.lifted_at.is_some());
339 Ok(breaker_open(spent, self.caps.daily, lifted).then(|| breaker_refusal(spent, self.caps.daily)))
340 }
341
342 /// A comped account's budget this month.
343 pub(crate) async fn comped_budget(&self, account: &BillingAccount) -> Result<CompedBudget> {
344 let month = &rfc3339(now_ms())[..7];
345 let used = self
346 .db
347 .prepare("SELECT SUM(micros) AS micros FROM g1t_spend WHERE account = ? AND bucket = 'comped' AND day >= ?")
348 .bind(&[account.id.as_str().into(), format!("{month}-01").into()])?
349 .first::<Sum>(None)
350 .await?
351 .and_then(|s| s.micros)
352 .unwrap_or(0);
353 let (ceiling, default_ceiling) = comped_ceiling(account.terms.ceiling_micros, self.caps.comped_monthly);
354 Ok(CompedBudget {
355 account: account.id.clone(),
356 name: account.name.clone(),
357 used_micros: used,
358 ceiling_micros: ceiling,
359 default_ceiling,
360 level: alert_level(used, ceiling),
361 })
362 }
363
364 /// Why new work on a comped account is refused, if its budget is used
365 /// up. None for every other account.
366 pub(crate) async fn comped_stop(&self, account: &BillingAccount) -> Result<Option<String>> {
367 if !account.terms.full_discount() {
368 return Ok(None);
369 }
370 let budget = self.comped_budget(account).await?;
371 Ok(used_up(budget.used_micros, budget.ceiling_micros).then(|| comped_refusal(&account.name, budget.used_micros, budget.ceiling_micros)))
372 }
373
374 /// Every 15 minutes: comped budgets' alerts, once each level a month,
375 /// and a tripped breaker staff were not yet told about.
376 pub(crate) async fn watch_spend(&self) -> Result<()> {
377 if self.caps.alert_to.is_empty() {
378 return Ok(());
379 }
380 let month = rfc3339(now_ms())[..7].to_owned();
381 #[derive(Deserialize)]
382 struct Id {
383 id: String,
384 }
385 let comped = self
386 .db
387 .prepare(format!("SELECT id FROM billing_accounts WHERE {}", crate::sales::FULL_DISCOUNT_SQL))
388 .all()
389 .await?
390 .results::<Id>()?;
391 #[derive(Deserialize)]
392 struct Sent {
393 level: Option<i64>,
394 }
395 for Id { id } in comped {
396 let Some(account) = self.find_account(&id).await? else { continue };
397 let budget = self.comped_budget(&account).await?;
398 let sent = self
399 .db
400 .prepare("SELECT MAX(level) AS level FROM budget_alerts WHERE account = ? AND month = ?")
401 .bind(&[id.as_str().into(), month.as_str().into()])?
402 .first::<Sent>(None)
403 .await?
404 .and_then(|s| s.level)
405 .unwrap_or(0);
406 let Some(level) = alert_to_send(budget.level, u32::try_from(sent).unwrap_or(0)) else { continue };
407 let name = &account.name;
408 let mut lines = vec![format!(
409 "{name}'s work has cost g1t {} this month, {level}% of its {} monthly budget ({}).",
410 cents(budget.used_micros),
411 cents(budget.ceiling_micros),
412 if budget.default_ceiling { "COMPED_MONTHLY_CEILING_MICROS" } else { "its own limit, in its terms" }
413 )];
414 lines.push(if level >= 100 {
415 format!("New agent runs, checks and builds on {name} are refused until the budget is raised or the month turns. Runs already going finish. To raise it: sudo, Accounts, {name}, Terms, Limit.")
416 } else {
417 format!("At 100%, new work on {name} is refused until staff raise the budget. To raise it now: sudo, Accounts, {name}, Terms, Limit.")
418 });
419 let subject = format!("g1t: {name} has used {level}% of its monthly budget");
420 match crate::margin::email_staff(&self.env, &self.caps.alert_to, &subject, &lines).await {
421 Ok(()) => {
422 self.db
423 .prepare("INSERT OR IGNORE INTO budget_alerts (account, month, level, sent_at) VALUES (?, ?, ?, ?)")
424 .bind(&[id.as_str().into(), month.as_str().into(), level.into(), rfc3339(now_ms()).into()])?
425 .run()
426 .await?;
427 }
428 Err(error) => worker::console_error!("could not email {name}'s budget alert: {error}"),
429 }
430 }
431 // A trip whose email did not go out when it happened.
432 let day = today();
433 if self.breaker_row(&day).await?.is_some_and(|row| row.tripped_at.is_some() && row.told_at.is_none()) {
434 let total = self.spent_on(&day).await?;
435 self.tell_breaker(&day, total).await?;
436 }
437 Ok(())
438 }
439
440 /// `admin_spend_caps`: g1t's own spend against its caps.
441 pub(crate) async fn spend_caps(&self) -> Result<SpendCaps> {
442 let day = today();
443 let month = day[..7].to_owned();
444 let month_start = format!("{month}-01");
445 let today_micros = self.spent_on(&day).await?;
446 let row = self.breaker_row(&day).await?;
447 let lifted = row.as_ref().is_some_and(|r| r.lifted_at.is_some());
448 #[derive(Deserialize)]
449 struct Bucket {
450 bucket: String,
451 micros: Option<i64>,
452 }
453 let rows = self
454 .db
455 .prepare("SELECT bucket, SUM(micros) AS micros FROM g1t_spend WHERE day >= ? GROUP BY bucket")
456 .bind(&[month_start.as_str().into()])?
457 .all()
458 .await?
459 .results::<Bucket>()?;
460 let month_buckets = ["comped", "trial", "oss", "given", "unpaid"]
461 .iter()
462 .map(|bucket| SpendBucket {
463 bucket: (*bucket).to_owned(),
464 title: bucket_title(bucket).to_owned(),
465 micros: rows.iter().find(|r| r.bucket == *bucket).and_then(|r| r.micros).unwrap_or(0),
466 })
467 .collect();
468 #[derive(Deserialize)]
469 struct Id {
470 id: String,
471 }
472 let ids = self
473 .db
474 .prepare(format!("SELECT id FROM billing_accounts WHERE {} ORDER BY id", crate::sales::FULL_DISCOUNT_SQL))
475 .all()
476 .await?
477 .results::<Id>()?;
478 let mut comped = vec![];
479 for Id { id } in ids {
480 if let Some(account) = self.find_account(&id).await? {
481 comped.push(self.comped_budget(&account).await?);
482 }
483 }
484 // Free workspaces' share of the reconciled costs that are not on
485 // the ledger (models, sandboxes and builds are, above).
486 let free_tier_micros = self
487 .db
488 .prepare(format!(
489 "SELECT SUM(cost_micros) AS micros FROM workspace_costs
490 WHERE day >= ?1 AND bucket NOT IN ('models', 'sandboxes', 'deployments')
491 AND workspace NOT IN ({internal})
492 AND workspace NOT IN (SELECT workspace FROM workspace_costs WHERE day >= ?1 GROUP BY workspace HAVING SUM(revenue_micros) > 0)",
493 internal = crate::sales::INTERNAL_SQL
494 ))
495 .bind(&[month_start.as_str().into()])?
496 .first::<Sum>(None)
497 .await?
498 .and_then(|s| s.micros)
499 .unwrap_or(0);
500 let revenue_micros = self
501 .db
502 .prepare("SELECT SUM(cash_micros) AS micros FROM margin_days WHERE day >= ?")
503 .bind(&[month_start.as_str().into()])?
504 .first::<Sum>(None)
505 .await?
506 .and_then(|s| s.micros)
507 .unwrap_or(0);
508 // What a testing reset wiped from the ledger is still here.
509 #[derive(Deserialize)]
510 struct Reset {
511 account: String,
512 micros: Option<i64>,
513 }
514 let reset = self
515 .db
516 .prepare(RESET_SPEND_SQL)
517 .bind(&[month_start.as_str().into()])?
518 .all()
519 .await?
520 .results::<Reset>()?;
521 let reset_micros = reset.iter().filter_map(|r| r.micros).sum();
522 let reset_workspaces = reset.into_iter().map(|r| r.account.strip_prefix("ws_").unwrap_or(&r.account).to_owned()).collect();
523 let fixed = self.fixed_monthly(self.caps.fixed_monthly).await?;
524 // This month's days so far, each its billing cycle's share: the
525 // same accrual as the statement's range (`cycle::accrued`).
526 let fixed_month_micros = crate::cycle::accrued(fixed.monthly_micros, &month_start, &day, self.cycle_anchor().await?);
527 Ok(SpendCaps {
528 reset_micros,
529 reset_workspaces,
530 day,
531 month,
532 today_micros,
533 daily_cap_micros: self.caps.daily,
534 tripped: breaker_open(today_micros, self.caps.daily, lifted),
535 tripped_at: row.as_ref().and_then(|r| r.tripped_at.clone()),
536 lifted_by: row.as_ref().and_then(|r| r.lifted_by.clone()),
537 lifted_at: row.as_ref().and_then(|r| r.lifted_at.clone()),
538 lift_note: row.as_ref().and_then(|r| r.lift_note.clone()),
539 month_buckets,
540 comped,
541 free_tier_micros,
542 fixed_monthly_micros: fixed.monthly_micros,
543 fixed_source: fixed.source.into(),
544 fixed_read_at: fixed.read_at,
545 fixed_items: fixed.items,
546 fixed_month_micros,
547 revenue_micros,
548 })
549 }
550
551 /// `admin_lift_breaker`: hosted-model runs start again for the rest of
552 /// today (UTC).
553 pub(crate) async fn admin_lift_breaker(&self, a: AdminLiftBreakerArgs) -> Result<Outcome<SpendCaps>> {
554 let (by, note) = (a.by.trim(), a.note.trim());
555 if by.is_empty() || note.len() < 5 {
556 return Ok(Outcome::fail(FailureCode::Invalid, "Say who is lifting it, and why, in the note."));
557 }
558 let day = today();
559 let now = rfc3339(now_ms());
560 let note: String = note.chars().take(500).collect();
561 self.db
562 .prepare(
563 "INSERT INTO spend_breaker (day, lifted_by, lifted_at, lift_note) VALUES (?1, ?2, ?3, ?4)
564 ON CONFLICT (day) DO UPDATE SET lifted_by = ?2, lifted_at = ?3, lift_note = ?4",
565 )
566 .bind(&[day.as_str().into(), by.into(), now.as_str().into(), note.as_str().into()])?
567 .run()
568 .await?;
569 let spent = self.spent_on(&day).await?;
570 self.audit("costs", "breaker_lifted", &format!("{day}: lifted at {} of {}: {note}", cents(spent), cents(self.caps.daily)), by)
571 .await?;
572 Ok(Outcome::Ok(self.spend_caps().await?))
573 }
574}
575
576/// g1t's own spend since `?1` on accounts a testing reset wiped later than
577/// the day it was spent (`admin_actions`, action `reset`), by account.
578pub(crate) const RESET_SPEND_SQL: &str = "SELECT s.account, SUM(s.micros) AS micros FROM g1t_spend s
579 WHERE s.day >= ?1 AND EXISTS (SELECT 1 FROM admin_actions a WHERE a.action = 'reset' AND a.account = s.account AND substr(a.created_at, 1, 10) >= s.day)
580 GROUP BY s.account HAVING SUM(s.micros) > 0 ORDER BY s.account";
581
582/// How sudo names a bucket of g1t's own spend.
583pub(crate) fn bucket_title(bucket: &str) -> &'static str {
584 match bucket {
585 "comped" => "100% discount (g1t's own)",
586 "trial" => "Trial pool",
587 "oss" => "Open-source pool",
588 "given" => "Free overruns g1t covered",
589 "unpaid" => "Charged without real money",
590 _ => "Other",
591 }
592}
593
594#[cfg(test)]
595mod tests {
596 use super::*;
597
598 fn drawn(credit: i64, trial: i64, oss: i64, given: i64) -> Drawn {
599 Drawn { credit, trial, oss, given }
600 }
601
602 #[test]
603 fn comped_work_is_all_g1ts_at_cost() {
604 let s = share(1_000_000, 0, &Drawn::default(), true, true);
605 assert_eq!(s, Share { comped: 1_000_000, ..Share::default() });
606 // Nothing that cost nothing is counted.
607 assert_eq!(share(0, 0, &Drawn::default(), true, true).total(), 0);
608 assert_eq!(share(-5, 0, &Drawn::default(), false, false).total(), 0);
609 }
610
611 #[test]
612 fn pools_pay_their_share_of_the_cost_not_the_price() {
613 // $1 of cost charged at $1.20, all from the trial: $1 is g1t's.
614 assert_eq!(share(1_000_000, 0, &drawn(0, 1_200_000, 0, 0), false, true), Share { trial: 1_000_000, ..Share::default() });
615 // Half the open-source pool, half charged on a live card: half is g1t's.
616 assert_eq!(share(1_000_000, 600_000, &drawn(0, 0, 600_000, 0), false, true), Share { oss: 500_000, ..Share::default() });
617 // A free workspace's overrun past its trial.
618 let s = share(1_000_000, 0, &drawn(0, 300_000, 0, 900_000), false, true);
619 assert_eq!((s.trial, s.given), (250_000, 750_000));
620 }
621
622 #[test]
623 fn revenue_is_only_revenue_with_real_money() {
624 // Plan credit and an on-demand charge, live: none of it is g1t's.
625 assert_eq!(share(1_000_000, 600_000, &drawn(600_000, 0, 0, 0), false, true).total(), 0);
626 // The same in test mode: all of it.
627 assert_eq!(share(1_000_000, 600_000, &drawn(600_000, 0, 0, 0), false, false), Share { unpaid: 1_000_000, ..Share::default() });
628 // Free while building: charged nothing, all g1t's.
629 assert_eq!(share(1_000_000, 0, &Drawn::default(), false, true), Share { unpaid: 1_000_000, ..Share::default() });
630 }
631
632 #[test]
633 fn a_comped_account_gets_the_default_budget_unless_its_terms_set_one() {
634 assert_eq!(comped_ceiling(None, 150_000_000), (150_000_000, true));
635 assert_eq!(comped_ceiling(Some(400_000_000), 150_000_000), (400_000_000, false));
636 // Zero: no budget.
637 assert_eq!(comped_ceiling(None, 0), (0, true));
638 assert!(!used_up(1_000_000_000, 0));
639 }
640
641 #[test]
642 fn a_comped_budget_refuses_new_work_at_one_hundred_percent() {
643 let ceiling = 150_000_000;
644 assert!(!used_up(149_999_999, ceiling));
645 assert!(used_up(150_000_000, ceiling));
646 assert!(used_up(151_000_000, ceiling));
647 let message = comped_refusal("flagon-io", 150_000_000, ceiling);
648 assert!(message.contains("used up") && message.contains("$150.00 of $150.00") && message.contains("sudo"), "{message}");
649 }
650
651 #[test]
652 fn budget_alerts_go_once_per_level_each_month() {
653 let ceiling = 150_000_000;
654 let mut sent = 0;
655 let mut emailed = vec![];
656 // Spend climbs through the month, checked every 15 minutes.
657 for used in [10_000_000, 74_000_000, 75_000_000, 80_000_000, 112_500_000, 120_000_000, 135_000_000, 140_000_000, 150_000_000, 170_000_000] {
658 if let Some(level) = alert_to_send(alert_level(used, ceiling), sent) {
659 emailed.push(level);
660 sent = level;
661 }
662 }
663 assert_eq!(emailed, vec![50, 75, 90, 100]);
664 // A jump straight past several levels sends only the highest.
665 assert_eq!(alert_to_send(alert_level(140_000_000, ceiling), 0), Some(90));
666 // A new month starts from nothing sent.
667 assert_eq!(alert_to_send(alert_level(80_000_000, ceiling), 0), Some(50));
668 }
669
670 #[test]
671 fn the_breaker_trips_at_the_cap_and_staff_can_lift_it_for_the_day() {
672 let cap = 75_000_000;
673 assert!(!breaker_open(74_999_999, cap, false));
674 assert!(breaker_open(75_000_000, cap, false));
675 // Lifted: open no more today.
676 assert!(!breaker_open(90_000_000, cap, true));
677 // Off.
678 assert!(!breaker_open(1_000_000_000, 0, false));
679 // Tomorrow's total starts at zero: the breaker resets by itself.
680 assert!(!breaker_open(0, cap, false));
681 let message = breaker_refusal(80_000_000, cap);
682 assert!(message.contains("$80.00 of its $75.00") && message.contains("00:00 UTC"), "{message}");
683 }
684
685 #[test]
686 fn the_breaker_is_about_all_compute_g1t_pays_for() {
687 assert!(breaker_applies(ComputeKind::Check));
688 assert!(breaker_applies(ComputeKind::Workflow));
689 assert!(breaker_applies(ComputeKind::Deploy));
690 }
691
692 #[test]
693 fn workspaces_paying_with_real_money_are_never_paused_by_the_breaker() {
694 assert!(covered_by_revenue(PlanKind::Paid, false, false, true));
695 assert!(covered_by_revenue(PlanKind::Enterprise, false, false, true));
696 // Test-mode payments are not money.
697 assert!(!covered_by_revenue(PlanKind::Paid, false, false, false));
698 // The plan given by staff, comped, free: g1t pays.
699 assert!(!covered_by_revenue(PlanKind::Paid, false, true, true));
700 assert!(!covered_by_revenue(PlanKind::Internal, true, false, true));
701 assert!(!covered_by_revenue(PlanKind::Free, false, false, true));
702 }
703
704 #[test]
705 fn spend_a_testing_reset_wiped_is_found_by_the_reset_after_it() {
706 // syntaqx's $7.41 of 2026-10-02 to 10-05, reset on 10-07: still
707 // g1t's spend, gone from its ledger.
708 assert_eq!(crate::rename::parameters(RESET_SPEND_SQL), 1);
709 assert!(RESET_SPEND_SQL.contains("a.action = 'reset'") && RESET_SPEND_SQL.contains("substr(a.created_at, 1, 10) >= s.day"));
710 }
711
712 #[test]
713 fn amounts_read_in_cents() {
714 assert_eq!(cents(150_000_000), "$150.00");
715 assert_eq!(cents(1_234_567), "$1.23");
716 assert_eq!(cents(5_000), "$0.01");
717 }
718}