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
561 linesCodeBlameRaw
1//! Usage other services meter through the month, charged once it is over:
2//! security scans, the actions cache, and two billing measures itself each
3//! day: private repository storage and git operations. `context` (search
4//! embeddings) is no longer reported, since the context service was
5//! retired; it stays a source so usage noted before is still charged and
6//! closed.
7//!
8//! Each reports what it cost g1t so far this month (`note_pending`), so the
9//! workspace's limit counts it as it happens. When the month is over,
10//! billing charges it once: at cost plus the margin, on the account's
11//! terms, after the plan's included usage and the trial credit (see
12//! `credits`), dated the month's last second so it falls in that month's
13//! statement and invoice.
14//!
15//! The forge is free for every workspace up to the same amounts, on the
16//! plan or not; past them, a workspace on the plan pays at cost plus the
17//! margin and is never refused or slowed, and a free workspace is never
18//! charged but is held to them.
19//!
20//! **Git operations.** Cloudflare Artifacts charges g1t $0.15 per 1,000
21//! operations (clones, fetches, pushes) from 2026-10-14. The repos service
22//! counts those through g1t's git endpoints. Every workspace has
23//! `GIT_OPERATIONS_INCLUDED` (50,000) a month free; past it, the plan pays
24//! at cost plus the margin, and a free workspace is slowed down instead
25//! (the repos service's `GIT_OPERATIONS_FREE_CAP`, the same number).
26//!
27//! **Actions cache.** The actions service reports what each workspace's
28//! `actions/cache` entries held each day (source `cache`), at what R2
29//! charges g1t to store them ($0.015 a GB-month). Only the plan is charged
30//! for it, from the first byte; a free workspace is never charged, and its
31//! repositories are held to the cache's quota like everyone's.
32//!
33//! **Storage.** The git store does not report a repository's size, so the
34//! repos service counts the packs pushed through g1t's git endpoints (see
35//! `g1t_contracts::repos::StorageArgs`): a lower bound. Each day billing
36//! records what each workspace's private repositories hold and what is
37//! free that day (`FREE_PRIVATE_STORAGE_BYTES`, 1 GB, for everyone). Like
38//! Cloudflare's own storage billing, a month's GB-months are the days'
39//! amounts past the free one, added up and divided by 30, and only the
40//! plan is charged for them. A free workspace is never charged for
41//! storage: pushes to its private repositories stop once they hold its
42//! free amount. Public repositories are never charged.
43
44use g1t_contracts::billing::PlanKind;
45use g1t_contracts::new_id;
46use g1t_contracts::repos::{GitOperationsArgs, StorageArgs, WorkspaceGitOperations, WorkspaceStorage};
47use g1t_contracts::time::rfc3339;
48use g1t_kit::now_ms;
49use serde::Deserialize;
50use worker::Result;
51
52use crate::credits::{self, Drawn, Eligible};
53use crate::{Billing, optional};
54
55/// Sources billing charges itself when the month is over.
56pub(crate) const CHARGED_HERE: [&str; 5] = ["security", "context", "storage", "git", "cache"];
57
58/// Sources only the plan is charged for: a free workspace's are not kept.
59pub(crate) const PLAN_ONLY: [&str; 1] = ["cache"];
60
61/// Sources g1t pays for itself on a free workspace: security scans and
62/// (from before the context service was retired) search embeddings. No security feature is held back for the plan, and
63/// a free workspace never runs up a bill, so on a workspace without the
64/// plan they are recorded at cost, covered by g1t, and charged nothing
65/// (not even from the trial). On the plan they are charged like any usage.
66pub(crate) const COVERED_FOR_FREE: [&str; 2] = ["security", "context"];
67
68/// What pays for a month-end source's charge: the plan's included usage
69/// and the trial, and for a covered source on a free workspace, g1t.
70pub(crate) fn month_end_eligible(source: &str, plan: bool) -> Eligible {
71 let covered = !plan && COVERED_FOR_FREE.contains(&source);
72 Eligible { trial: !covered, repo: None, cover_rest: covered }
73}
74
75/// What Artifacts charges g1t, when the price book cannot be read: $0.50
76/// a GB-month of storage, and $0.15 per 1,000 git operations.
77pub(crate) const STORAGE_MICROS_PER_GB_MONTH: i64 = 500_000;
78pub(crate) const GIT_MICROS_PER_THOUSAND: i64 = 150_000;
79
80/// When Cloudflare starts charging for Artifacts operations: none before
81/// count.
82pub(crate) const GIT_BILLING_STARTS: &str = "2026-10-14T00";
83
84/// What git operations past the free amount cost g1t, at
85/// `micros_per_thousand`: nothing up to it.
86pub(crate) fn git_cost(operations: u64, included: u64, micros_per_thousand: f64) -> i64 {
87 let past = operations.saturating_sub(included);
88 (past as f64 * micros_per_thousand / 1000.0).ceil() as i64
89}
90
91/// The first hour of `month` to count git operations from.
92pub(crate) fn git_since(month: &str) -> String {
93 let start = format!("{month}-01T00");
94 if start.as_str() < GIT_BILLING_STARTS { GIT_BILLING_STARTS.to_owned() } else { start }
95}
96
97/// A gigabyte, as Cloudflare bills storage.
98pub(crate) const GB: f64 = 1_000_000_000.0;
99
100/// GB-months from a month's daily measures, each `(private, free)` bytes:
101/// what was past the free amount each day, over 30 days.
102pub(crate) fn storage_gb_months(days: &[(i64, i64)]) -> f64 {
103 days.iter().map(|(private, free)| (private - free).max(0) as f64).sum::<f64>() / GB / 30.0
104}
105
106/// GB-months of package storage past the free amounts from a month's
107/// daily measures, each `(public, private, public_free, private_free)`:
108/// public and private past their own amounts, over 30 days.
109pub(crate) fn package_gb_months(days: &[(i64, i64, i64, i64)]) -> f64 {
110 days.iter()
111 .map(|(public, private, public_free, private_free)| ((public - public_free).max(0) + (private - private_free).max(0)) as f64)
112 .sum::<f64>()
113 / GB
114 / 30.0
115}
116
117/// What R2 charges g1t a GB-month, when the price book cannot be read.
118pub(crate) const PACKAGE_MICROS_PER_GB_MONTH: i64 = 15_000;
119
120/// What `gb_months` cost g1t at `micros_per_gb_month`, rounded up.
121pub(crate) fn storage_cost(gb_months: f64, micros_per_gb_month: f64) -> i64 {
122 (gb_months * micros_per_gb_month).ceil() as i64
123}
124
125/// What a source is called on the statement.
126pub(crate) fn title(source: &str) -> &'static str {
127 match source {
128 "security" => "Security scans",
129 "context" => "Search embeddings",
130 "storage" => "Private repository storage past the free amount",
131 "package_storage" => "Package storage past the free amounts",
132 "git" => "Git operations past the free amount",
133 "cache" => "Actions cache storage",
134 "domains" => "Custom domains",
135 _ => "Metered usage",
136 }
137}
138
139/// A usage entry to put on the ledger.
140pub(crate) struct UsageLine<'a> {
141 pub workspace: &'a str,
142 /// What the workspace is charged, after terms and what paid for it.
143 pub charged: i64,
144 pub description: &'a str,
145 pub repo: Option<&'a str>,
146 pub task: &'a str,
147 pub cost: i64,
148 pub reference: &'a str,
149 pub created_at: &'a str,
150 pub drawn: Drawn,
151}
152
153impl Billing {
154 /// Puts a usage entry on the ledger and takes it off the balance, as
155 /// one write.
156 pub(crate) async fn post_usage(&self, line: UsageLine<'_>) -> Result<()> {
157 // The price versions it was charged at (pricing.rs).
158 let mut versions = Vec::new();
159 for meter in crate::pricing::meters_of(line.task) {
160 versions.extend(self.version_now(meter).await?);
161 }
162 let price_version = versions.join(",");
163 self.db
164 .batch(vec![
165 self.db
166 .prepare(
167 "INSERT INTO ledger
168 (id, workspace, kind, amount_micros, description, repo, task, cost_micros, reference,
169 created_at, billed_to, credit_micros, trial_micros, oss_micros, price_version)
170 VALUES (?, ?, 'usage', ?, ?, ?, ?, ?, ?, ?, 'g1t', ?, ?, ?, ?)",
171 )
172 .bind(&[
173 new_id("led", now_ms()).into(),
174 line.workspace.into(),
175 (-(line.charged as f64)).into(),
176 line.description.into(),
177 optional(line.repo),
178 line.task.into(),
179 (line.cost as f64).into(),
180 line.reference.into(),
181 line.created_at.into(),
182 (line.drawn.credit as f64).into(),
183 (line.drawn.trial as f64).into(),
184 (line.drawn.oss as f64).into(),
185 optional(Some(price_version.as_str()).filter(|v| !v.is_empty())),
186 ])?,
187 self.db
188 .prepare(
189 "INSERT INTO accounts (workspace, balance_micros, created_at) VALUES (?1, ?2, ?3)
190 ON CONFLICT (workspace) DO UPDATE SET balance_micros = balance_micros + ?2",
191 )
192 .bind(&[line.workspace.into(), (-(line.charged as f64)).into(), rfc3339(now_ms()).into()])?,
193 ])
194 .await?;
195 Ok(())
196 }
197
198 /// Writes down what a source cost g1t so far in `month`, what it will
199 /// be charged, and how much of it there was (`detail`, for the Billing
200 /// page), replacing the last figure.
201 pub(crate) async fn set_pending(
202 &self,
203 workspace: &str,
204 source: &str,
205 month: &str,
206 cost_micros: i64,
207 detail: Option<&str>,
208 ) -> Result<()> {
209 // A covered source on a free workspace is never charged, so it does
210 // not count toward the workspace's limit either.
211 let covered = COVERED_FOR_FREE.contains(&source) && !self.has_plan(&workspace.to_lowercase()).await?;
212 let charge = if covered { 0 } else { credits::with_margin(cost_micros, self.margin_percent) };
213 self.db
214 .prepare(
215 "INSERT INTO pending_usage (workspace, source, month, charge_micros, cost_micros, updated_at, detail)
216 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)
217 ON CONFLICT (workspace, source, month) DO UPDATE SET
218 charge_micros = ?4, cost_micros = ?5, updated_at = ?6, detail = COALESCE(?7, detail)",
219 )
220 .bind(&[
221 workspace.to_lowercase().into(),
222 source.into(),
223 month.into(),
224 (charge as f64).into(),
225 (cost_micros.max(0) as f64).into(),
226 rfc3339(now_ms()).into(),
227 optional(detail),
228 ])?
229 .run()
230 .await?;
231 Ok(())
232 }
233
234 /// Charges every month that is over for the usage billing charges
235 /// itself, once each.
236 pub(crate) async fn charge_pending(&self) -> Result<()> {
237 if self.stripe.is_none() {
238 return Ok(());
239 }
240 let now = rfc3339(now_ms());
241 let current = credits::month_of(&now);
242 #[derive(Deserialize)]
243 struct Row {
244 workspace: String,
245 source: String,
246 month: String,
247 cost_micros: Option<i64>,
248 }
249 let marks = CHARGED_HERE.iter().map(|s| format!("'{s}'")).collect::<Vec<_>>().join(", ");
250 let due = self
251 .db
252 .prepare(format!(
253 "SELECT workspace, source, month, cost_micros FROM pending_usage
254 WHERE month < ? AND charged_at IS NULL AND source IN ({marks}) ORDER BY month LIMIT 50"
255 ))
256 .bind(&[current.as_str().into()])?
257 .all()
258 .await?
259 .results::<Row>()?;
260 for row in due {
261 // Claimed first, so two crons never charge it twice.
262 let claimed = self
263 .db
264 .prepare(
265 "UPDATE pending_usage SET charged_at = ?1
266 WHERE workspace = ?2 AND source = ?3 AND month = ?4 AND charged_at IS NULL RETURNING workspace",
267 )
268 .bind(&[now.as_str().into(), row.workspace.as_str().into(), row.source.as_str().into(), row.month.as_str().into()])?
269 .first::<serde_json::Value>(None)
270 .await?;
271 let cost = row.cost_micros.unwrap_or(0);
272 if claimed.is_none() || cost <= 0 {
273 continue;
274 }
275 let base = credits::with_margin(cost, self.margin_percent);
276 let (charge, terms_note, discount) = self.charged(&row.workspace, base).await?;
277 let plan = self.has_plan(&row.workspace).await?;
278 let drawn = self.draw(&row.workspace, charge, &row.month, &month_end_eligible(&row.source, plan)).await?;
279 let detail = if row.source == "storage" {
280 let gb_months = self.gb_months(&row.workspace, &row.month).await?;
281 format!(": {gb_months:.2} GB-months")
282 } else {
283 String::new()
284 };
285 let description = format!("{} in {}{detail}{terms_note}{}", title(&row.source), row.month, drawn.note());
286 let reference = format!("{}/{}/{}", row.source, row.workspace, row.month);
287 let created_at = credits::month_end(&row.month);
288 self.post_usage(UsageLine {
289 workspace: &row.workspace,
290 charged: charge - drawn.total(),
291 description: &description,
292 repo: None,
293 task: &row.source,
294 cost,
295 reference: &reference,
296 created_at: &created_at,
297 drawn,
298 })
299 .await?;
300 self.record_discount(&reference, discount).await?;
301 }
302 Ok(())
303 }
304
305 /// A workspace's private storage past the free amount in `month`.
306 async fn gb_months(&self, workspace: &str, month: &str) -> Result<f64> {
307 #[derive(Deserialize)]
308 struct Day {
309 private_bytes: i64,
310 free_bytes: i64,
311 }
312 let days = self
313 .db
314 .prepare("SELECT private_bytes, free_bytes FROM storage_days WHERE workspace = ? AND substr(day, 1, 7) = ?")
315 .bind(&[workspace.into(), month.into()])?
316 .all()
317 .await?
318 .results::<Day>()?;
319 Ok(storage_gb_months(&days.iter().map(|d| (d.private_bytes, d.free_bytes)).collect::<Vec<_>>()))
320 }
321
322 /// Once a day: what each workspace's private repositories hold, and
323 /// what this month's storage past the free amount comes to so far.
324 pub(crate) async fn measure_storage(&self) -> Result<()> {
325 let Some(repos) = &self.repos else { return Ok(()) };
326 let list: Vec<WorkspaceStorage> = g1t_kit::call(repos, "storage", &StorageArgs {}).await?;
327 let now = rfc3339(now_ms());
328 let (day, month) = (&now[..10], credits::month_of(&now));
329 let price = self.price("private_storage").await?.map_or(STORAGE_MICROS_PER_GB_MONTH as f64, |(cost, _)| cost);
330 for workspace in list {
331 let slug = workspace.namespace.to_lowercase();
332 let plan = self.has_plan(&slug).await?;
333 // The same free amount for everyone: the plan pays past it.
334 let free = self.plans.free_storage_bytes;
335 self.db
336 .prepare(
337 "INSERT INTO storage_days (workspace, day, private_bytes, free_bytes) VALUES (?1, ?2, ?3, ?4)
338 ON CONFLICT (workspace, day) DO UPDATE SET private_bytes = ?3, free_bytes = ?4",
339 )
340 .bind(&[slug.as_str().into(), day.into(), (workspace.private_bytes as f64).into(), (free as f64).into()])?
341 .run()
342 .await?;
343 // Only the plan pays for storage past its amount. A free
344 // workspace is never charged: the repos service stops its pushes
345 // to private repositories once it is full (see git_ops.rs there).
346 let gb_months = if plan { self.gb_months(&slug, &month).await? } else { 0.0 };
347 if gb_months > 0.0 {
348 let detail = format!("{gb_months:.2} GB-months past the free {}", crate::features::bytes(free));
349 self.set_pending(&slug, "storage", &month, storage_cost(gb_months, price), Some(&detail)).await?;
350 }
351 }
352 Ok(())
353 }
354
355 /// Once a day: what each workspace's packages hold, public and private,
356 /// from the packages service, and for the plan what this month's
357 /// storage past the free amounts comes to so far. A free workspace is
358 /// never charged: the packages service refuses its pushes past them.
359 pub(crate) async fn measure_packages(&self) -> Result<()> {
360 #[derive(Deserialize)]
361 struct Held {
362 workspace: String,
363 public_bytes: i64,
364 private_bytes: i64,
365 }
366 #[derive(Deserialize)]
367 struct Day {
368 public_bytes: i64,
369 private_bytes: i64,
370 public_free_bytes: i64,
371 private_free_bytes: i64,
372 }
373 let Some(packages) = &self.packages else { return Ok(()) };
374 let list: Vec<Held> = g1t_kit::call(packages, "storage_all", &serde_json::json!({})).await?;
375 let now = rfc3339(now_ms());
376 let (day, month) = (&now[..10], credits::month_of(&now));
377 let price = self.price("package_storage").await?.map_or(PACKAGE_MICROS_PER_GB_MONTH as f64, |(cost, _)| cost);
378 let (public_free, private_free) = (self.plans.public_package_free_bytes, self.plans.private_package_free_bytes);
379 for held in list {
380 let slug = held.workspace.to_lowercase();
381 self.db
382 .prepare(
383 "INSERT INTO package_storage_days (workspace, day, public_bytes, private_bytes, public_free_bytes, private_free_bytes)
384 VALUES (?1, ?2, ?3, ?4, ?5, ?6)
385 ON CONFLICT (workspace, day) DO UPDATE SET public_bytes = ?3, private_bytes = ?4, public_free_bytes = ?5, private_free_bytes = ?6",
386 )
387 .bind(&[
388 slug.as_str().into(),
389 day.into(),
390 (held.public_bytes as f64).into(),
391 (held.private_bytes as f64).into(),
392 (public_free as f64).into(),
393 (private_free as f64).into(),
394 ])?
395 .run()
396 .await?;
397 if !self.has_plan(&slug).await? {
398 continue;
399 }
400 let days = self
401 .db
402 .prepare(
403 "SELECT public_bytes, private_bytes, public_free_bytes, private_free_bytes FROM package_storage_days
404 WHERE workspace = ? AND substr(day, 1, 7) = ?",
405 )
406 .bind(&[slug.as_str().into(), month.as_str().into()])?
407 .all()
408 .await?
409 .results::<Day>()?;
410 let gb_months = package_gb_months(
411 &days.iter().map(|d| (d.public_bytes, d.private_bytes, d.public_free_bytes, d.private_free_bytes)).collect::<Vec<_>>(),
412 );
413 if gb_months > 0.0 {
414 let detail = format!(
415 "{gb_months:.2} GB-months past the free {} public and {} private",
416 crate::features::bytes(public_free),
417 crate::features::bytes(private_free)
418 );
419 self.set_pending(&slug, "package_storage", &month, storage_cost(gb_months, price), Some(&detail)).await?;
420 }
421 }
422 Ok(())
423 }
424
425 /// Git operations this month for each workspace, from the repos
426 /// service: what is past the free amount goes to the month's pending
427 /// usage for workspaces on the plan, which are never slowed or refused
428 /// for them. Free workspaces are never charged for them.
429 pub(crate) async fn measure_git(&self) -> Result<()> {
430 let Some(repos) = &self.repos else { return Ok(()) };
431 let month = credits::month_of(&rfc3339(now_ms()));
432 let list: Vec<WorkspaceGitOperations> =
433 g1t_kit::call(repos, "git_operations", &GitOperationsArgs { since: Some(git_since(&month)), month: month.clone(), namespace: None }).await?;
434 let price = self.price("git_operations").await?.map_or(GIT_MICROS_PER_THOUSAND as f64, |(cost, _)| cost);
435 for workspace in list {
436 let slug = workspace.namespace.to_lowercase();
437 if self.plan_kind(&slug).await? == PlanKind::Free {
438 continue;
439 }
440 let cost = git_cost(workspace.operations, self.plans.git_included, price);
441 if cost > 0 {
442 let detail = format!(
443 "{} operations, {} of them free",
444 crate::features::thousands(workspace.operations),
445 crate::features::thousands(self.plans.git_included)
446 );
447 self.set_pending(&slug, "git", &month, cost, Some(&detail)).await?;
448 }
449 }
450 Ok(())
451 }
452
453 /// The workspace's git operations this month, as last measured.
454 pub(crate) async fn git_operations_this_month(&self, workspace: &str) -> Result<u64> {
455 let Some(repos) = &self.repos else { return Ok(0) };
456 let month = credits::month_of(&rfc3339(now_ms()));
457 let list: Result<Vec<WorkspaceGitOperations>> =
458 g1t_kit::call(repos, "git_operations", &GitOperationsArgs { since: Some(format!("{month}-01T00")), month, namespace: Some(workspace.to_lowercase()) }).await;
459 Ok(list
460 .ok()
461 .and_then(|list| list.into_iter().find(|w| w.namespace.eq_ignore_ascii_case(workspace)))
462 .map_or(0, |w| w.operations))
463 }
464}
465
466#[cfg(test)]
467mod tests {
468 use super::*;
469
470 #[test]
471 fn scans_and_embeddings_on_a_free_workspace_are_covered_by_g1t() {
472 // No plan: g1t pays, and the trial is left alone.
473 let free = month_end_eligible("security", false);
474 assert!(free.cover_rest && !free.trial);
475 assert!(month_end_eligible("context", false).cover_rest);
476 // On the plan: charged like any usage, from its included usage first.
477 let plan = month_end_eligible("security", true);
478 assert!(!plan.cover_rest && plan.trial);
479 // Storage, git and the cache are never kept for a free workspace;
480 // were one at the close, it would not be covered either.
481 assert!(!month_end_eligible("storage", false).cover_rest);
482 assert!(!month_end_eligible("git", false).cover_rest);
483 // A covered source is still a source billing charges at the close.
484 assert!(COVERED_FOR_FREE.iter().all(|s| CHARGED_HERE.contains(s)));
485 }
486
487 #[test]
488 fn storage_past_the_free_amount_is_counted_by_the_day() {
489 // 3 GB private with 1 GB free, every day of a 30-day month: 2 GB-months.
490 let month = vec![(3_000_000_000, 1_000_000_000); 30];
491 assert!((storage_gb_months(&month) - 2.0).abs() < 1e-9);
492 // Under the free amount: nothing.
493 assert_eq!(storage_gb_months(&[(500_000_000, 1_000_000_000); 30]), 0.0);
494 // The plan has the same 1 GB free: 11 GB on it all month is 10
495 // GB-months, $5.00 to g1t, $6.00 charged, from its included usage.
496 let days = vec![(11_000_000_000, 1_000_000_000); 30];
497 assert!((storage_gb_months(&days) - 10.0).abs() < 1e-9);
498 assert_eq!(credits::with_margin(storage_cost(storage_gb_months(&days), STORAGE_MICROS_PER_GB_MONTH as f64), 20), 6_000_000);
499 // Ten days of 4 GB past it: a third of 4 GB-months.
500 let days = vec![(5_000_000_000, 1_000_000_000); 10];
501 assert!((storage_gb_months(&days) - 4.0 / 3.0).abs() < 1e-9);
502 }
503
504 #[test]
505 fn package_storage_counts_public_and_private_past_their_own_free_amounts() {
506 let gb = GB as i64;
507 // 12 GB public with 10 free, 0.5 GB private with 0.5 free: 2 GB past, for 15 days.
508 let days = vec![(12 * gb, gb / 2, 10 * gb, gb / 2); 15];
509 assert!((package_gb_months(&days) - 1.0).abs() < 1e-9);
510 // Under both amounts: nothing.
511 assert_eq!(package_gb_months(&[(gb, gb / 4, 10 * gb, gb / 2)]), 0.0);
512 // At R2's rate, a GB-month is $0.015.
513 assert_eq!(storage_cost(1.0, PACKAGE_MICROS_PER_GB_MONTH as f64), 15_000);
514 }
515
516 #[test]
517 fn storage_is_priced_at_cloudflares_rate_plus_the_margin() {
518 // $0.50 a GB-month to g1t: 2 GB-months cost $1.00, charged $1.20.
519 let cost = storage_cost(2.0, 500_000.0);
520 assert_eq!(cost, 1_000_000);
521 assert_eq!(credits::with_margin(cost, 20), 1_200_000);
522 // A fraction of a millionth rounds up.
523 assert_eq!(storage_cost(0.000_000_001, 500_000.0), 1);
524 }
525
526 #[test]
527 fn embeddings_and_scans_are_charged_at_cost_plus_the_margin() {
528 // 10 million tokens at $0.067 a million: $0.67, charged $0.804.
529 assert_eq!(credits::with_margin(670_000, 20), 804_000);
530 assert_eq!(title("context"), "Search embeddings");
531 assert_eq!(title("security"), "Security scans");
532 assert!(CHARGED_HERE.contains(&"storage") && !CHARGED_HERE.contains(&"deployments"));
533 assert!(CHARGED_HERE.contains(&"git"));
534 }
535
536 #[test]
537 fn git_operations_are_charged_past_the_free_amount_at_cloudflares_price() {
538 // $0.15 per 1,000 to g1t; 50,000 a month free for everyone.
539 let per_thousand = GIT_MICROS_PER_THOUSAND as f64;
540 let free = crate::credits::Config::default().git_included;
541 assert_eq!(free, 50_000);
542 assert_eq!(git_cost(49_000, free, per_thousand), 0);
543 assert_eq!(git_cost(50_000, free, per_thousand), 0);
544 // A workspace on the plan pushes on past it, and pays: 70,000
545 // operations are 20,000 past it, $3.00 to g1t, $3.60 charged.
546 assert_eq!(git_cost(70_000, free, per_thousand), 3_000_000);
547 assert_eq!(credits::with_margin(git_cost(70_000, free, per_thousand), 20), 3_600_000);
548 // A million in a month: no cap, $142.50 to g1t.
549 assert_eq!(git_cost(1_000_000, free, per_thousand), 142_500_000);
550 // One past it: a fraction of a cent, rounded up to a millionth.
551 assert_eq!(git_cost(50_001, free, per_thousand), 150);
552 assert_eq!(title("git"), "Git operations past the free amount");
553 }
554
555 #[test]
556 fn git_operations_count_from_when_cloudflare_starts_charging() {
557 assert_eq!(git_since("2026-10"), "2026-10-14T00");
558 assert_eq!(git_since("2026-11"), "2026-11-01T00");
559 assert_eq!(git_since("2026-09"), "2026-10-14T00");
560 }
561}