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