Skip to content
561 linesCodeBlameRaw

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.

Projects: when each was last pushed, and how active it is lately1//! Usage other services meter through the month, charged once it is over:
Comments and the audit log stop speaking of g1t's agents as current. A workspace's runner settings send g1t's own work (checks, merge checks and the merge queue) to self-hosted runners, and the audit log now says "g1t's work on self-hosted runners" where it said "Agents on self-hosted runners"; the fields keep their names. The runner errors script names the sandboxes that run checks, workflow jobs and merge-queue builds, the reserved name waitlist is the retired waitlist's, and billing's context source is kept only so search embeddings noted before the context service was retired are still charged and closed. Checked: cargo test for g1t-contracts, g1t-billing (243) and g1t-actions (107), the wasm builds of billing and actions, and the ops scripts' tests.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.
Projects: when each was last pushed, and how active it is lately7//!
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
Comments and the audit log stop speaking of g1t's agents as current. A workspace's runner settings send g1t's own work (checks, merge checks and the merge queue) to self-hosted runners, and the audit log now says "g1t's work on self-hosted runners" where it said "Agents on self-hosted runners"; the fields keep their names. The runner errors script names the sandboxes that run checks, workflow jobs and merge-queue builds, the reserved name waitlist is the retired waitlist's, and billing's context source is kept only so search embeddings noted before the context service was retired are still charged and closed. Checked: cargo test for g1t-contracts, g1t-billing (243) and g1t-actions (107), the wasm builds of billing and actions, and the ops scripts' tests.62/// (from before the context service was retired) search embeddings. No security feature is held back for the plan, and
Projects: when each was last pushed, and how active it is lately63/// 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}

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