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
503 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.

Usage, Billing settings and prepaid AI credit; fixes from the UX audit1//! The Usage page: a workspace's usage over a range of days, at price, by
2//! product family, meter, project and day, with what paid for it.
3//!
4//! Every figure comes from the ledger's own lines, measured as the
5//! statement measures them (`statement::PRICE_SQL`: what was charged, plus
6//! what included usage, the trial, a pool or g1t paid, plus what a discount
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.7//! took off), so the Usage page and Billing show the same numbers. Usage metered through the month and charged
Usage, Billing settings and prepaid AI credit; fixes from the UX audit8//! when it closes (storage, git operations, scans, embeddings, domains,
9//! app traffic) is added from `pending_usage`, at cost plus the margin, and
10//! said to be pending.
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.11//!
12//! Lines from before g1t stopped running agents and selling model access
13//! (agent runs, the agent rate, AI Gateway requests), and any line on a
14//! task this page does not know, are kept on one meter, Other, in a
15//! product of their own: nothing charged is ever left out.
Usage, Billing settings and prepaid AI credit; fixes from the UX audit16
17use std::collections::{BTreeMap, BTreeSet};
18
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.19use futures_util::future::{try_join, try_join3};
Usage, Billing settings and prepaid AI credit; fixes from the UX audit20use g1t_contracts::billing::{
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.21 Allowance, MeterLine, PlanKind, ProductUsage, ProjectUsage, UsageDay, UsageReport, UsageReportArgs, UsageTotals, PRODUCTS,
Usage, Billing settings and prepaid AI credit; fixes from the UX audit22};
23use g1t_contracts::time::{parse_rfc3339, rfc3339};
24use g1t_contracts::{FailureCode, Outcome};
25use g1t_kit::now_ms;
26use serde::Deserialize;
27use worker::Result;
28
Spend adds up on one ledger. Charged this month has one definition, price less discount, included usage and credit, shared by the plan card, the spend limit, Spend and the top bar; the limit had counted usage not yet closed at full price before the discount, so a comped workspace read as charged a cent. Every agent line on the ledger names the agent and who asked, repository runs by g1t included, so Spent is the sum of its products, Agents is the agent product, and by agent adds up to it; the billing API returns by_agent and by_person. The usage and billing guide says how spend is counted.29use crate::charged::{charged, pending_split};
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.30use crate::statement::PRICE_SQL;
Usage, Billing settings and prepaid AI credit; fixes from the UX audit31use crate::{Billing, members_only};
32
33/// The longest range the page reads at once.
34pub(crate) const MAX_DAYS: u64 = 400;
35const DAY_MS: u64 = 86_400_000;
36
37/// Which meter a ledger line is on. One SQL expression, so every query
38/// agrees with `METERS`.
39pub(crate) const METER_KEY_SQL: &str = "CASE
40 WHEN task = 'sandbox' THEN 'sandbox'
41 WHEN task = 'self_hosted' THEN 'self_hosted'
42 WHEN task = 'deployments' AND reference LIKE 'deploy/%' THEN 'builds'
43 WHEN task = 'deployments' THEN 'requests'
44 WHEN task = 'domains' THEN 'domains'
45 WHEN task = 'git' THEN 'git'
46 WHEN task = 'storage' THEN 'storage'
47 WHEN task = 'cache' THEN 'cache'
48 WHEN task = 'package_storage' THEN 'packages'
49 WHEN task = 'security' THEN 'security'
50 WHEN task = 'context' THEN 'context'
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.51 ELSE 'other' END";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit52
53/// Every meter: key, name, product family and the unit of its quantity.
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.54pub(crate) const METERS: [(&str, &str, &str, &str); 12] = [
Usage, Billing settings and prepaid AI credit; fixes from the UX audit55 ("sandbox", "Sandbox time", "sandboxes", "seconds"),
56 ("self_hosted", "Self-hosted runner time", "sandboxes", "seconds"),
57 ("builds", "Builds", "deployments", "entries"),
58 ("requests", "Requests & CPU", "deployments", "entries"),
59 ("domains", "Custom domains", "deployments", "entries"),
60 ("git", "Git operations", "git_storage", "operations"),
61 ("storage", "Private storage", "git_storage", "bytes"),
62 ("cache", "Actions cache", "git_storage", "entries"),
63 ("packages", "Package storage", "packages", "entries"),
64 ("security", "Security scans", "security", "entries"),
65 ("context", "Search embeddings", "search", "entries"),
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.66 // Kept apart so nothing is ever lost: a line whose meter is unknown,
67 // and agents' and models' usage from before they were retired.
68 ("other", "Other", OTHER, "entries"),
Usage, Billing settings and prepaid AI credit; fixes from the UX audit69];
70
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.71/// The product of the Other meter: not one of `PRODUCTS`, listed last and
72/// only when it has something.
73pub(crate) const OTHER: &str = "other";
74
Usage, Billing settings and prepaid AI credit; fixes from the UX audit75/// The meter month-end usage noted so far (`pending_usage.source`) is on.
76pub(crate) fn meter_of_pending(source: &str) -> &'static str {
77 match source {
78 "deployments" => "requests",
79 "domains" => "domains",
80 "storage" => "storage",
81 "git" => "git",
82 "cache" => "cache",
83 "package_storage" => "packages",
84 "security" => "security",
85 "context" => "context",
86 _ => "other",
87 }
88}
89
90/// Every day from `from` to `until`, both included, or why not.
91pub(crate) fn days_between(from: &str, until: &str) -> std::result::Result<Vec<String>, &'static str> {
92 let (Some(start), Some(end)) = (parse_rfc3339(&format!("{from}T00:00:00Z")), parse_rfc3339(&format!("{until}T00:00:00Z"))) else {
93 return Err("Give the range as days, YYYY-MM-DD.");
94 };
95 if end < start {
96 return Err("The range ends before it starts.");
97 }
98 if (end - start) / DAY_MS + 1 > MAX_DAYS {
99 return Err("Ask for at most 400 days at a time.");
100 }
101 Ok((0..=(end - start) / DAY_MS).map(|n| rfc3339(start + n * DAY_MS)[..10].to_owned()).collect())
102}
103
104/// One group of ledger lines: a day, a meter and a project.
105#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
106pub(crate) struct Cell {
107 pub day: String,
108 pub meter: String,
109 #[serde(default)]
110 pub project: String,
111 #[serde(default)]
112 pub price: Option<f64>,
113 #[serde(default)]
114 pub quantity: Option<f64>,
115 #[serde(default)]
116 pub entries: Option<f64>,
117 #[serde(default)]
118 pub discount: Option<f64>,
119 #[serde(default)]
120 pub covered: Option<f64>,
121 #[serde(default)]
122 pub cost: Option<f64>,
123}
124
Spend adds up on one ledger. Charged this month has one definition, price less discount, included usage and credit, shared by the plan card, the spend limit, Spend and the top bar; the limit had counted usage not yet closed at full price before the discount, so a comped workspace read as charged a cent. Every agent line on the ledger names the agent and who asked, repository runs by g1t included, so Spent is the sum of its products, Agents is the agent product, and by agent adds up to it; the billing API returns by_agent and by_person. The usage and billing guide says how spend is counted.125/// What is metered this month and charged when it closes, by meter: at
126/// price, with what g1t covers of it and what the discount takes off, as
127/// the close will enter it (`charged::pending_split`).
128#[derive(Clone, Debug, Default, PartialEq)]
Usage, Billing settings and prepaid AI credit; fixes from the UX audit129pub(crate) struct Pending {
130 pub meter: &'static str,
131 pub micros: i64,
Spend adds up on one ledger. Charged this month has one definition, price less discount, included usage and credit, shared by the plan card, the spend limit, Spend and the top bar; the limit had counted usage not yet closed at full price before the discount, so a comped workspace read as charged a cent. Every agent line on the ledger names the agent and who asked, repository runs by g1t included, so Spent is the sum of its products, Agents is the agent product, and by agent adds up to it; the billing API returns by_agent and by_person. The usage and billing guide says how spend is counted.132 pub covered: i64,
133 pub discount: i64,
Usage, Billing settings and prepaid AI credit; fixes from the UX audit134}
135
Money is written one way. A single formatter turns millionths of a dollar into dollars, rounding half up on whole micros rather than on a float, so the same sum reads the same on every page: under a cent reads <$0.01 on a total and exactly nothing is $0.00, while the statement's lines, a session's receipt, the price book and an agent's effort costs carry up to four places where the fraction of a cent is the point; the six formatters that each rounded their own way are gone. This month is the calendar month in UTC from its first day to today everywhere, and billing counts the month's not-yet-closed usage in any range that reaches into the current month, so the top bar's pill, Spend, Home and Usage ask for the same days and get the same figure; Home now reads the usage report the others read instead of adding up statements. Usage's pending sentence says what of that usage the close will charge after the discount and included usage, which the billing API returns as pending_charged_micros. A reconciliation test holds the pill, Spend, Home and Usage to one number for one month. The usage and billing guide says how amounts are written and what this month means.136/// Whether a range of days reaches into the current month, so the month's
137/// usage not yet closed (`pending_usage`, which has no day of its own) is
138/// counted in it. `today` is billing's own UTC day, so the top bar, Spend,
139/// Home and Usage, which all ask for the first of the month to today, get
140/// the same answer whatever `until` they send: a range need not reach
141/// month end to include what the month has metered so far.
142pub(crate) fn reaches_this_month(from: &str, until: &str, today: &str) -> bool {
143 from <= today && until >= &format!("{}-01", &today[..7])[..]
Spend adds up on one ledger. Charged this month has one definition, price less discount, included usage and credit, shared by the plan card, the spend limit, Spend and the top bar; the limit had counted usage not yet closed at full price before the discount, so a comped workspace read as charged a cent. Every agent line on the ledger names the agent and who asked, repository runs by g1t included, so Spent is the sum of its products, Agents is the agent product, and by agent adds up to it; the billing API returns by_agent and by_person. The usage and billing guide says how spend is counted.144}
145
Usage, Billing settings and prepaid AI credit; fixes from the UX audit146/// Shapes the groups into the page: products in order, each with its
147/// meters, each meter with every day of the range and its projects. Only
148/// the products and projects asked for (all, when none are).
149pub(crate) fn shape(
150 days: &[String],
151 cells: &[Cell],
152 pending: &[Pending],
153 products: &[String],
154 projects: &[String],
155) -> (Vec<ProductUsage>, Vec<UsageDay>, UsageTotals, Vec<String>) {
156 let wanted_product = |product: &str| products.is_empty() || products.iter().any(|p| p == product);
157 let wanted_project = |project: &str| projects.is_empty() || projects.iter().any(|p| p == project);
158 let index: BTreeMap<&str, usize> = days.iter().enumerate().map(|(i, d)| (d.as_str(), i)).collect();
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.159 let product_of = |meter: &str| METERS.iter().find(|m| m.0 == meter).map_or(OTHER, |m| m.2);
Usage, Billing settings and prepaid AI credit; fixes from the UX audit160 let mut all_projects = BTreeSet::new();
161 let mut lines: Vec<MeterLine> = METERS
162 .iter()
163 .map(|(key, label, product, unit)| MeterLine {
164 key: (*key).to_owned(),
165 label: (*label).to_owned(),
166 product: (*product).to_owned(),
167 unit: (*unit).to_owned(),
168 quantity: 0.0,
169 micros: 0,
170 pending_micros: 0,
171 daily: vec![0; days.len()],
172 allowance: None,
173 by_project: vec![],
Merge branch 'model-routing'174 note: None,
Usage, Billing settings and prepaid AI credit; fixes from the UX audit175 })
176 .collect();
177 let mut by_day: BTreeMap<(String, String), i64> = BTreeMap::new();
178 let mut totals = UsageTotals::default();
179 let mut projects_of: BTreeMap<(String, String), (i64, f64)> = BTreeMap::new();
180 for cell in cells {
181 if !cell.project.is_empty() {
182 all_projects.insert(cell.project.clone());
183 }
184 let product = product_of(&cell.meter);
185 if !wanted_product(product) || !wanted_project(&cell.project) {
186 continue;
187 }
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.188 // A meter this page does not know is Other's: never dropped.
189 let meter = if METERS.iter().any(|m| m.0 == cell.meter) { cell.meter.as_str() } else { "other" };
190 let Some(line) = lines.iter_mut().find(|l| l.key == meter) else { continue };
Usage, Billing settings and prepaid AI credit; fixes from the UX audit191 let price = cell.price.unwrap_or(0.0).round() as i64;
192 line.micros += price;
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.193 line.quantity += if line.unit == "entries" { cell.entries.unwrap_or(0.0) } else { cell.quantity.unwrap_or(0.0) };
Usage, Billing settings and prepaid AI credit; fixes from the UX audit194 if let Some(i) = index.get(cell.day.as_str()) {
195 line.daily[*i] += price;
196 }
197 *by_day.entry((cell.day.clone(), product.to_owned())).or_default() += price;
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.198 let part = projects_of.entry((meter.to_owned(), cell.project.clone())).or_default();
Usage, Billing settings and prepaid AI credit; fixes from the UX audit199 part.0 += price;
200 part.1 += if line.unit == "entries" { cell.entries.unwrap_or(0.0) } else { cell.quantity.unwrap_or(0.0) };
201 totals.price_micros += price;
202 totals.discount_micros += cell.discount.unwrap_or(0.0).round() as i64;
203 totals.included_micros += cell.covered.unwrap_or(0.0).round() as i64;
204 totals.cost_micros += cell.cost.unwrap_or(0.0).round() as i64;
205 }
206 // Pending usage is no one project's: only when all projects are shown.
207 if projects.is_empty() {
208 for p in pending {
209 let product = product_of(p.meter);
210 if !wanted_product(product) {
211 continue;
212 }
213 if let Some(line) = lines.iter_mut().find(|l| l.key == p.meter) {
214 line.micros += p.micros;
215 line.pending_micros += p.micros;
216 }
217 totals.price_micros += p.micros;
218 totals.pending_micros += p.micros;
Money is written one way. A single formatter turns millionths of a dollar into dollars, rounding half up on whole micros rather than on a float, so the same sum reads the same on every page: under a cent reads <$0.01 on a total and exactly nothing is $0.00, while the statement's lines, a session's receipt, the price book and an agent's effort costs carry up to four places where the fraction of a cent is the point; the six formatters that each rounded their own way are gone. This month is the calendar month in UTC from its first day to today everywhere, and billing counts the month's not-yet-closed usage in any range that reaches into the current month, so the top bar's pill, Spend, Home and Usage ask for the same days and get the same figure; Home now reads the usage report the others read instead of adding up statements. Usage's pending sentence says what of that usage the close will charge after the discount and included usage, which the billing API returns as pending_charged_micros. A reconciliation test holds the pill, Spend, Home and Usage to one number for one month. The usage and billing guide says how amounts are written and what this month means.219 totals.pending_charged_micros += p.micros - p.covered - p.discount;
Spend adds up on one ledger. Charged this month has one definition, price less discount, included usage and credit, shared by the plan card, the spend limit, Spend and the top bar; the limit had counted usage not yet closed at full price before the discount, so a comped workspace read as charged a cent. Every agent line on the ledger names the agent and who asked, repository runs by g1t included, so Spent is the sum of its products, Agents is the agent product, and by agent adds up to it; the billing API returns by_agent and by_person. The usage and billing guide says how spend is counted.220 totals.included_micros += p.covered;
221 totals.discount_micros += p.discount;
Usage, Billing settings and prepaid AI credit; fixes from the UX audit222 }
223 }
224 for ((meter, project), (micros, quantity)) in projects_of {
225 if let Some(line) = lines.iter_mut().find(|l| l.key == meter) {
226 line.by_project.push(ProjectUsage { project, micros, quantity });
227 }
228 }
229 for line in &mut lines {
230 line.by_project.sort_by(|a, b| b.micros.cmp(&a.micros).then(a.project.cmp(&b.project)));
231 }
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.232 // The products that still have meters, in order, then Other when it
233 // has something.
234 let other_used = lines.iter().any(|l| l.product == OTHER && (l.micros != 0 || l.quantity != 0.0));
Usage, Billing settings and prepaid AI credit; fixes from the UX audit235 let products_out = PRODUCTS
236 .iter()
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.237 .copied()
238 .filter(|(key, _)| METERS.iter().any(|m| m.2 == *key))
239 .chain(other_used.then_some((OTHER, "Other")))
Usage, Billing settings and prepaid AI credit; fixes from the UX audit240 .filter(|(key, _)| wanted_product(key))
241 .map(|(key, label)| {
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.242 let meters: Vec<MeterLine> = lines.iter().filter(|l| l.product == key).cloned().collect();
Usage, Billing settings and prepaid AI credit; fixes from the UX audit243 ProductUsage {
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.244 key: key.to_owned(),
245 label: label.to_owned(),
Usage, Billing settings and prepaid AI credit; fixes from the UX audit246 micros: meters.iter().map(|m| m.micros).sum(),
247 meters,
248 }
249 })
250 .collect();
251 let days_out = by_day.into_iter().filter(|(_, micros)| *micros != 0).map(|((day, product), micros)| UsageDay { day, product, micros }).collect();
252 (products_out, days_out, totals, all_projects.into_iter().collect())
253}
254
255impl Billing {
256 /// `usage_report`: members only.
257 pub(crate) async fn usage_report(&self, a: UsageReportArgs) -> Result<Outcome<UsageReport>> {
258 let workspace = a.workspace.to_lowercase();
259 if !a.viewer.is_some_and(|viewer| viewer.is_member(&workspace)) {
260 return Ok(members_only());
261 }
262 let days = match days_between(&a.from, &a.until) {
263 Ok(days) => days,
264 Err(why) => return Ok(Outcome::fail(FailureCode::Invalid, why)),
265 };
266 let from = days[0].clone();
267 let until = days[days.len() - 1].clone();
268 // Lines entered on the last day count, whatever the hour.
269 let end = rfc3339(parse_rfc3339(&format!("{until}T00:00:00Z")).unwrap_or(0) + DAY_MS);
270 let measure = if self.free { "COALESCE(cost_micros, 0)" } else { PRICE_SQL };
271 let now = rfc3339(now_ms());
272 let month = now[..7].to_owned();
Money is written one way. A single formatter turns millionths of a dollar into dollars, rounding half up on whole micros rather than on a float, so the same sum reads the same on every page: under a cent reads <$0.01 on a total and exactly nothing is $0.00, while the statement's lines, a session's receipt, the price book and an agent's effort costs carry up to four places where the fraction of a cent is the point; the six formatters that each rounded their own way are gone. This month is the calendar month in UTC from its first day to today everywhere, and billing counts the month's not-yet-closed usage in any range that reaches into the current month, so the top bar's pill, Spend, Home and Usage ask for the same days and get the same figure; Home now reads the usage report the others read instead of adding up statements. Usage's pending sentence says what of that usage the close will charge after the discount and included usage, which the billing API returns as pending_charged_micros. A reconciliation test holds the pill, Spend, Home and Usage to one number for one month. The usage and billing guide says how amounts are written and what this month means.273 let this_month = reaches_this_month(&from, &until, &now[..10]);
Usage, Billing settings and prepaid AI credit; fixes from the UX audit274
275 let cells = async {
276 self.db
277 .prepare(format!(
278 "SELECT substr(created_at, 1, 10) AS day, {meter} AS meter, COALESCE(repo, '') AS project,
279 SUM({measure}) AS price, SUM(COALESCE(quantity, 0)) AS quantity, COUNT(*) AS entries,
280 SUM(COALESCE(discount_micros, 0)) AS discount,
281 SUM(COALESCE(credit_micros, 0) + COALESCE(trial_micros, 0) + COALESCE(oss_micros, 0) + COALESCE(given_micros, 0)) AS covered,
282 SUM(COALESCE(cost_micros, 0)) AS cost
283 FROM ledger WHERE workspace = ?1 AND kind = 'usage' AND created_at >= ?2 AND created_at < ?3
284 GROUP BY 1, 2, 3 LIMIT 20000",
285 meter = METER_KEY_SQL
286 ))
287 .bind(&[workspace.as_str().into(), from.as_str().into(), end.as_str().into()])?
288 .all()
289 .await?
290 .results::<Cell>()
291 };
292 let pending = async {
293 if !this_month {
294 return Ok(vec![]);
295 }
Spend adds up on one ledger. Charged this month has one definition, price less discount, included usage and credit, shared by the plan card, the spend limit, Spend and the top bar; the limit had counted usage not yet closed at full price before the discount, so a comped workspace read as charged a cent. Every agent line on the ledger names the agent and who asked, repository runs by g1t included, so Spent is the sum of its products, Agents is the agent product, and by agent adds up to it; the billing API returns by_agent and by_person. The usage and billing guide says how spend is counted.296 self.pending_this_month(&[workspace.as_str().into()], &month).await
297 };
Usage, Billing settings and prepaid AI credit; fixes from the UX audit298 let credits = self.credit_paid_between(&workspace, &from, &end);
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.299 let ((cells, pending, (credits_paid, credits)), account) =
300 try_join(try_join3(cells, pending, credits), self.account_of(&workspace)).await?;
Spend adds up on one ledger. Charged this month has one definition, price less discount, included usage and credit, shared by the plan card, the spend limit, Spend and the top bar; the limit had counted usage not yet closed at full price before the discount, so a comped workspace read as charged a cent. Every agent line on the ledger names the agent and who asked, repository runs by g1t included, so Spent is the sum of its products, Agents is the agent product, and by agent adds up to it; the billing API returns by_agent and by_person. The usage and billing guide says how spend is counted.301 let percent = account.terms.percent_off();
302 // What is metered so far is entered on the account's terms when the
303 // month closes: its discount, and what g1t covers, come off it now.
Usage, Billing settings and prepaid AI credit; fixes from the UX audit304 let pending: Vec<Pending> = pending
305 .into_iter()
Spend adds up on one ledger. Charged this month has one definition, price less discount, included usage and credit, shared by the plan card, the spend limit, Spend and the top bar; the limit had counted usage not yet closed at full price before the discount, so a comped workspace read as charged a cent. Every agent line on the ledger names the agent and who asked, repository runs by g1t included, so Spent is the sum of its products, Agents is the agent product, and by agent adds up to it; the billing API returns by_agent and by_person. The usage and billing guide says how spend is counted.306 .map(|p| {
307 let split = pending_split(p.cost(), p.charge(), self.margin_percent, percent);
308 Pending { meter: meter_of_pending(&p.source), micros: split.price, covered: split.covered, discount: split.discount }
Usage, Billing settings and prepaid AI credit; fixes from the UX audit309 })
310 .filter(|p| p.micros > 0)
311 .collect();
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.312 let (mut products, days_out, mut totals, all_projects) = shape(&days, &cells, &pending, &a.products, &a.projects);
Usage, Billing settings and prepaid AI credit; fixes from the UX audit313 let filtered = !a.products.is_empty() || !a.projects.is_empty();
314 // Credit is the workspace's, not a product's or a project's: only
315 // without a filter is it taken off.
316 totals.credits_micros = if filtered { 0 } else { credits_paid.max(0) };
317 totals.charged_micros = charged(&totals);
318 let plan = self.plan_kind_for(&workspace, &account).await?;
319 // Allowances, for the range that includes this month.
320 let mut included = None;
321 let mut trial = None;
322 if this_month {
323 let (used, stored, operations) = futures_util::future::try_join3(
324 async {
325 if plan == PlanKind::Paid { self.allowance_used("plan_credit", &workspace, &month).await } else { Ok(0) }
326 },
327 self.private_storage(&workspace),
328 self.git_operations_this_month(&workspace),
329 )
330 .await?;
331 if plan == PlanKind::Paid {
332 included = Some(Allowance { used: used as f64, of: self.plans.plan_included_micros as f64, unit: "micros".to_owned() });
333 }
334 for product in &mut products {
335 for meter in &mut product.meters {
336 match meter.key.as_str() {
337 "storage" => {
338 meter.quantity = stored as f64;
339 meter.allowance = Some(Allowance { used: stored as f64, of: self.plans.free_storage_bytes as f64, unit: "bytes".to_owned() });
340 }
341 "git" => {
342 meter.quantity = operations as f64;
343 meter.allowance = Some(Allowance { used: operations as f64, of: self.plans.git_included as f64, unit: "operations".to_owned() });
344 }
345 _ => {}
346 }
347 }
348 }
349 if plan == PlanKind::Free
350 && let Some(grant) = self.grant_of(&workspace).await?
351 {
352 trial = Some(crate::credits::left(grant.granted_micros, grant.used_micros));
353 }
354 }
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.355 // AI credit bought before g1t stopped selling it: still the
356 // workspace's, and kept apart from credit for everything.
Usage, Billing settings and prepaid AI credit; fixes from the UX audit357 let ai: i64 = credits.grants.iter().filter(|g| g.scope == "models").map(|g| g.left_micros).sum();
358 Ok(Outcome::Ok(UsageReport {
359 from,
360 until,
361 totals,
362 days: days_out,
363 products,
364 projects: all_projects,
365 included,
366 discount_percent: (percent > 0).then_some(percent),
367 ai_credit_micros: ai,
368 credit_micros: credits.left_micros - ai,
369 trial_micros: trial,
370 plan,
371 free: self.free,
372 }))
Merge branch 'model-routing'373 }
374}
375
Usage, Billing settings and prepaid AI credit; fixes from the UX audit376#[cfg(test)]
377mod tests {
378 use super::*;
379
380 fn cell(day: &str, meter: &str, project: &str, price: f64) -> Cell {
381 Cell { day: day.into(), meter: meter.into(), project: project.into(), price: Some(price), entries: Some(1.0), ..Cell::default() }
382 }
383
384 #[test]
385 fn a_range_is_every_day_in_it() {
386 let days = days_between("2026-10-01", "2026-10-03").unwrap();
387 assert_eq!(days, ["2026-10-01", "2026-10-02", "2026-10-03"]);
388 assert_eq!(days_between("2026-10-01", "2026-10-01").unwrap().len(), 1);
389 assert!(days_between("2026-10-03", "2026-10-01").is_err());
390 assert!(days_between("2025-01-01", "2026-10-01").is_err());
391 assert!(days_between("Oct 1", "2026-10-01").is_err());
392 }
393
394 #[test]
395 fn usage_is_shaped_into_products_meters_days_and_projects() {
396 let days = days_between("2026-10-01", "2026-10-03").unwrap();
397 let mut sandbox = cell("2026-10-02", "sandbox", "acme/web", 120_000.0);
398 sandbox.quantity = Some(600.0);
399 let cells = vec![
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.400 cell("2026-10-01", "requests", "acme/web", 2_000_000.0),
401 cell("2026-10-03", "requests", "acme/api", 1_000_000.0),
Usage, Billing settings and prepaid AI credit; fixes from the UX audit402 sandbox,
403 Cell { covered: Some(1_000_000.0), discount: Some(0.0), ..cell("2026-10-03", "storage", "", 30_000.0) },
404 ];
Spend adds up on one ledger. Charged this month has one definition, price less discount, included usage and credit, shared by the plan card, the spend limit, Spend and the top bar; the limit had counted usage not yet closed at full price before the discount, so a comped workspace read as charged a cent. Every agent line on the ledger names the agent and who asked, repository runs by g1t included, so Spent is the sum of its products, Agents is the agent product, and by agent adds up to it; the billing API returns by_agent and by_person. The usage and billing guide says how spend is counted.405 let pending = vec![Pending { meter: "git", micros: 40_000, ..Pending::default() }];
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.406 let (products, by_day, totals, projects) = shape(&days, &cells, &pending, &[], &[]);
407 // Every product family with meters, in order; no Other without usage on it.
408 assert_eq!(products.iter().map(|p| p.key.as_str()).collect::<Vec<_>>(), ["sandboxes", "deployments", "git_storage", "packages", "security", "search"]);
409 let deployments = &products[1];
410 assert_eq!(deployments.micros, 3_000_000);
411 let requests = deployments.meters.iter().find(|m| m.key == "requests").unwrap();
412 assert_eq!(requests.daily, [2_000_000, 0, 1_000_000]);
413 assert_eq!(requests.by_project[0].project, "acme/web");
414 let sandbox = products[0].meters.iter().find(|m| m.key == "sandbox").unwrap();
Usage, Billing settings and prepaid AI credit; fixes from the UX audit415 assert_eq!((sandbox.micros, sandbox.quantity), (120_000, 600.0));
416 // Pending usage counts in the totals and its meter, not on a day.
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.417 let git = products[2].meters.iter().find(|m| m.key == "git").unwrap();
Usage, Billing settings and prepaid AI credit; fixes from the UX audit418 assert_eq!((git.micros, git.pending_micros), (40_000, 40_000));
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.419 assert_eq!(totals.price_micros, 3_000_000 + 120_000 + 30_000 + 40_000);
Usage, Billing settings and prepaid AI credit; fixes from the UX audit420 assert_eq!(totals.pending_micros, 40_000);
421 assert_eq!(totals.included_micros, 1_000_000);
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.422 assert_eq!(by_day.iter().filter(|d| d.product == "deployments").map(|d| d.micros).sum::<i64>(), 3_000_000);
Usage, Billing settings and prepaid AI credit; fixes from the UX audit423 assert_eq!(projects, ["acme/api", "acme/web"]);
424 }
425
426 #[test]
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.427 fn usage_from_retired_or_unknown_meters_is_other_and_never_lost() {
428 let days = days_between("2026-10-01", "2026-10-02").unwrap();
429 // Lines from when g1t ran agents, as the ledger still has them, and
430 // a meter nothing knows.
431 let cells = vec![
432 cell("2026-10-01", "other", "acme/web", 2_000_000.0),
433 cell("2026-10-01", "agent_rate", "acme/web", 500_000.0),
434 cell("2026-10-02", "gateway", "", 70_000.0),
435 cell("2026-10-02", "sandbox", "acme/web", 10_000.0),
436 ];
437 let (products, by_day, totals, _) = shape(&days, &cells, &[], &[], &[]);
438 let other = products.last().unwrap();
439 assert_eq!((other.key.as_str(), other.label.as_str(), other.micros), ("other", "Other", 2_570_000));
440 assert_eq!(other.meters.len(), 1);
441 assert_eq!(other.meters[0].daily, [2_500_000, 70_000]);
442 // Everything charged is in the totals and on its day.
443 assert_eq!(totals.price_micros, 2_580_000);
444 assert_eq!(products.iter().map(|p| p.micros).sum::<i64>(), totals.price_micros);
445 assert_eq!(by_day.iter().map(|d| d.micros).sum::<i64>(), totals.price_micros);
446 // Asked for by name, Other is shown alone.
447 let (only, _, totals, _) = shape(&days, &cells, &[], &["other".into()], &[]);
448 assert_eq!(only.len(), 1);
449 assert_eq!(totals.price_micros, 2_570_000);
450 }
451
452 #[test]
Usage, Billing settings and prepaid AI credit; fixes from the UX audit453 fn filters_keep_only_the_products_and_projects_asked_for() {
454 let days = days_between("2026-10-01", "2026-10-01").unwrap();
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.455 let cells = vec![cell("2026-10-01", "sandbox", "acme/web", 100.0), cell("2026-10-01", "sandbox", "acme/api", 50.0), cell("2026-10-01", "requests", "acme/web", 10.0)];
Spend adds up on one ledger. Charged this month has one definition, price less discount, included usage and credit, shared by the plan card, the spend limit, Spend and the top bar; the limit had counted usage not yet closed at full price before the discount, so a comped workspace read as charged a cent. Every agent line on the ledger names the agent and who asked, repository runs by g1t included, so Spent is the sum of its products, Agents is the agent product, and by agent adds up to it; the billing API returns by_agent and by_person. The usage and billing guide says how spend is counted.456 let pending = vec![Pending { meter: "git", micros: 7, ..Pending::default() }];
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.457 let (products, _, totals, projects) = shape(&days, &cells, &pending, &["sandboxes".into()], &["acme/web".into()]);
Usage, Billing settings and prepaid AI credit; fixes from the UX audit458 assert_eq!(products.len(), 1);
459 assert_eq!(totals.price_micros, 100);
460 // Every project stays listed, for the filter itself.
461 assert_eq!(projects, ["acme/api", "acme/web"]);
462 }
463
464 #[test]
Spend adds up on one ledger. Charged this month has one definition, price less discount, included usage and credit, shared by the plan card, the spend limit, Spend and the top bar; the limit had counted usage not yet closed at full price before the discount, so a comped workspace read as charged a cent. Every agent line on the ledger names the agent and who asked, repository runs by g1t included, so Spent is the sum of its products, Agents is the agent product, and by agent adds up to it; the billing API returns by_agent and by_person. The usage and billing guide says how spend is counted.465 fn pending_usage_counts_at_price_with_what_is_covered_and_discounted() {
466 let days = days_between("2026-10-01", "2026-10-02").unwrap();
467 // Storage on a 100% discount, and scans g1t covers on a free workspace.
468 let pending = [
469 Pending { meter: "storage", micros: 9_000, covered: 0, discount: 9_000 },
470 Pending { meter: "security", micros: 2_400, covered: 2_400, discount: 0 },
471 ];
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.472 let (_, _, totals, _) = shape(&days, &[], &pending, &[], &[]);
Spend adds up on one ledger. Charged this month has one definition, price less discount, included usage and credit, shared by the plan card, the spend limit, Spend and the top bar; the limit had counted usage not yet closed at full price before the discount, so a comped workspace read as charged a cent. Every agent line on the ledger names the agent and who asked, repository runs by g1t included, so Spent is the sum of its products, Agents is the agent product, and by agent adds up to it; the billing API returns by_agent and by_person. The usage and billing guide says how spend is counted.473 assert_eq!(totals.price_micros, 11_400);
474 assert_eq!(totals.pending_micros, 11_400);
Money is written one way. A single formatter turns millionths of a dollar into dollars, rounding half up on whole micros rather than on a float, so the same sum reads the same on every page: under a cent reads <$0.01 on a total and exactly nothing is $0.00, while the statement's lines, a session's receipt, the price book and an agent's effort costs carry up to four places where the fraction of a cent is the point; the six formatters that each rounded their own way are gone. This month is the calendar month in UTC from its first day to today everywhere, and billing counts the month's not-yet-closed usage in any range that reaches into the current month, so the top bar's pill, Spend, Home and Usage ask for the same days and get the same figure; Home now reads the usage report the others read instead of adding up statements. Usage's pending sentence says what of that usage the close will charge after the discount and included usage, which the billing API returns as pending_charged_micros. A reconciliation test holds the pill, Spend, Home and Usage to one number for one month. The usage and billing guide says how amounts are written and what this month means.475 assert_eq!(totals.pending_charged_micros, 11_400 - totals.included_micros - totals.discount_micros);
Spend adds up on one ledger. Charged this month has one definition, price less discount, included usage and credit, shared by the plan card, the spend limit, Spend and the top bar; the limit had counted usage not yet closed at full price before the discount, so a comped workspace read as charged a cent. Every agent line on the ledger names the agent and who asked, repository runs by g1t included, so Spent is the sum of its products, Agents is the agent product, and by agent adds up to it; the billing API returns by_agent and by_person. The usage and billing guide says how spend is counted.476 assert_eq!(totals.included_micros, 2_400);
477 assert_eq!(totals.discount_micros, 9_000);
478 assert_eq!(charged(&totals), 0);
479 // With a project chosen, pending usage (no project's) is left out.
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.480 let (_, _, filtered, _) = shape(&days, &[], &pending, &[], &["acme/api".to_owned()]);
Spend adds up on one ledger. Charged this month has one definition, price less discount, included usage and credit, shared by the plan card, the spend limit, Spend and the top bar; the limit had counted usage not yet closed at full price before the discount, so a comped workspace read as charged a cent. Every agent line on the ledger names the agent and who asked, repository runs by g1t included, so Spent is the sum of its products, Agents is the agent product, and by agent adds up to it; the billing API returns by_agent and by_person. The usage and billing guide says how spend is counted.481 assert_eq!(filtered.pending_micros, 0);
482 }
483
484 #[test]
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.485 fn pending_usage_is_on_its_meter() {
Usage, Billing settings and prepaid AI credit; fixes from the UX audit486 assert_eq!(meter_of_pending("package_storage"), "packages");
487 assert_eq!(meter_of_pending("nonsense"), "other");
488 }
Money is written one way. A single formatter turns millionths of a dollar into dollars, rounding half up on whole micros rather than on a float, so the same sum reads the same on every page: under a cent reads <$0.01 on a total and exactly nothing is $0.00, while the statement's lines, a session's receipt, the price book and an agent's effort costs carry up to four places where the fraction of a cent is the point; the six formatters that each rounded their own way are gone. This month is the calendar month in UTC from its first day to today everywhere, and billing counts the month's not-yet-closed usage in any range that reaches into the current month, so the top bar's pill, Spend, Home and Usage ask for the same days and get the same figure; Home now reads the usage report the others read instead of adding up statements. Usage's pending sentence says what of that usage the close will charge after the discount and included usage, which the billing API returns as pending_charged_micros. A reconciliation test holds the pill, Spend, Home and Usage to one number for one month. The usage and billing guide says how amounts are written and what this month means.489
490 #[test]
491 fn pending_usage_counts_in_any_range_that_reaches_into_this_month() {
492 // The first of the month to today, as every page asks: counted.
493 assert!(reaches_this_month("2026-10-01", "2026-10-10", "2026-10-10"));
494 // To month end, or only the last week: counted too.
495 assert!(reaches_this_month("2026-10-01", "2026-10-31", "2026-10-10"));
496 assert!(reaches_this_month("2026-10-04", "2026-10-10", "2026-10-10"));
497 // Last month, or a month that has not started: not this month's.
498 assert!(!reaches_this_month("2026-09-01", "2026-09-30", "2026-10-10"));
499 assert!(!reaches_this_month("2026-11-01", "2026-11-30", "2026-10-10"));
500 // The month has turned on billing's clock: September's range no longer has it.
501 assert!(!reaches_this_month("2026-09-01", "2026-09-30", "2026-10-01"));
502 }
Usage, Billing settings and prepaid AI credit; fixes from the UX audit503}

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