g1t/crates/contracts/src/billing.rs

2,727 lines98,532 bytesCodeBlame

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.

Agents as a team: lifecycle, merge queue, billing and a new shell1//! The billing service: what agents cost, charged to the workspace they
2//! worked for.
3//!
4//! A workspace buys credit and each agent run deducts what it cost, plus
5//! g1t's margin. With no credit, no agent starts. Money is held in
6//! millionths of a US dollar, so that a run costing a fraction of a cent is
7//! recorded exactly.
8//!
9//! Each `*Args` struct is the argument of the method of the same name,
10//! served at `POST /rpc/<method>`.
11
12use serde::{Deserialize, Serialize};
13
14use crate::repos::RepoPath;
15use crate::{User, Viewer};
16
17/// Millionths of a US dollar in one dollar.
18pub const MICROS_PER_DOLLAR: i64 = 1_000_000;
19
20/// Whether workspaces are charged for agents at all, and with real money.
21/// `status` takes nothing and returns this.
22#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
23pub struct Status {
24 /// False when no payment provider is configured: nothing is charged,
25 /// and who may run agents is decided some other way.
26 pub enabled: bool,
27 /// False while the payment provider is in its test mode, where cards
28 /// are not real.
29 pub live: bool,
Free while g1t is being built out; agents can check out their own forks30 /// True while g1t is being built out: runs are recorded, with what
31 /// they cost, but nothing is charged and no credit is needed. Not a
32 /// promise that it stays free.
33 #[serde(default)]
34 pub free: bool,
Agents as a team: lifecycle, merge queue, billing and a new shell35}
36
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put37/// `trial`: a workspace's trial credit, so people can try g1t (its agents on
38/// g1t's hosted models among it) without a key or a card of their own. Each
39/// new workspace gets one grant of usage credit (`TRIAL_WORKSPACE_MICROS`),
40/// made when it first uses something, out of a pool for everyone that
41/// resets each calendar month (`TRIAL_MONTHLY_POOL_MICROS`). When this
42/// month's pool is given out, new grants wait for the next month. Returns
43/// `Trial`.
A free allowance on g1t's models, so anyone can try its agents44#[derive(Debug, Serialize, Deserialize)]
45#[serde(rename_all = "camelCase")]
46pub struct TrialArgs {
47 pub workspace: String,
48 /// Workspaces open to hosted models anyway, whose use is not counted
49 /// against the pool.
50 #[serde(default)]
51 pub exempt: Vec<String>,
52}
53
54#[derive(Clone, Debug, Serialize, Deserialize)]
55#[serde(rename_all = "camelCase")]
56pub struct Trial {
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put57 /// Whether its agents may use g1t's hosted models on the trial now: it
58 /// has credit left, or this month's pool can still grant it some.
A free allowance on g1t's models, so anyone can try its agents59 pub open: bool,
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put60 /// What the trial has paid for so far, in millionths of a dollar.
A free allowance on g1t's models, so anyone can try its agents61 pub used_micros: i64,
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put62 /// Its grant, or what it would be granted.
A free allowance on g1t's models, so anyone can try its agents63 pub limit_micros: i64,
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put64 /// No longer used: the trial does not end on a date. Kept for older
65 /// readers; always null.
A free allowance on g1t's models, so anyone can try its agents66 pub ends_at: Option<String>,
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put67 /// Why it is closed: `off` (no trials), `used` (this workspace's grant
68 /// is spent) or `pool` (this month's grants are all given out; see
69 /// `waits_until`). `ended` is no longer sent.
A free allowance on g1t's models, so anyone can try its agents70 pub reason: Option<String>,
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put71 /// Whether the workspace has its grant already.
72 #[serde(default)]
73 pub granted: bool,
74 /// RFC 3339: when a workspace waiting for a grant can get one, the
75 /// first of next month. Only with reason `pool`.
76 #[serde(default)]
77 pub waits_until: Option<String>,
A free allowance on g1t's models, so anyone can try its agents78}
79
Agents as a team: lifecycle, merge queue, billing and a new shell80/// A workspace's standing.
81#[derive(Clone, Debug, Serialize, Deserialize)]
82#[serde(rename_all = "camelCase")]
83pub struct Account {
84 pub workspace: String,
85 /// Credit left, in millionths of a dollar. Can dip below zero by the
86 /// cost of the runs that were under way when it ran out.
87 pub balance_micros: i64,
88 pub status: Status,
89 /// What is added to a run's cost, in percent.
90 pub margin_percent: u32,
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace91 /// The card g1t charges as the workspace nears its limit and when a
92 /// month closes, if one is on file.
93 #[serde(default)]
94 pub card: Option<Card>,
Agents as a team: lifecycle, merge queue, billing and a new shell95}
96
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace97/// A saved card, as far as it is safe to show.
98#[derive(Clone, Debug, Serialize, Deserialize)]
99#[serde(rename_all = "camelCase")]
100pub struct Card {
101 /// `visa`, `mastercard`, ...
102 pub brand: String,
103 pub last4: String,
104 pub exp_month: u32,
105 pub exp_year: u32,
106}
107
108/// `billing_portal`: Stripe's hosted billing page for the workspace, where
109/// an owner adds or replaces the card, sees invoices and receipts, and sets
110/// the billing email and address. g1t never handles card numbers. Owners
111/// only. Returns `Outcome<Checkout>` (its `url`); Stripe sends them back
112/// to `return_url`.
113#[derive(Debug, Serialize, Deserialize)]
114pub struct BillingPortalArgs {
115 pub actor: User,
116 pub workspace: String,
117 pub return_url: String,
118}
119
120/// `admin_billing_link`: for staff to send a customer: their Stripe billing
121/// page. Returns `Outcome<BillingLink>`.
122#[derive(Debug, Serialize, Deserialize)]
123pub struct AdminBillingLinkArgs {
124 pub workspace: String,
125 pub by: String,
126}
127
128#[derive(Clone, Debug, Serialize, Deserialize)]
129#[serde(rename_all = "camelCase")]
130pub struct BillingLink {
131 /// A one-time session on Stripe's billing page, signed in already.
132 pub portal_url: String,
133 /// The billing page's sign-in page, which does not expire: the
134 /// customer signs in with the email Stripe has for them.
135 pub login_url: Option<String>,
136 pub customer_email: Option<String>,
137 pub expires_note: String,
138}
139
Agents as a team: lifecycle, merge queue, billing and a new shell140#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
141#[serde(rename_all = "snake_case")]
142pub enum EntryKind {
143 /// Credit bought with a card.
144 TopUp,
Paid features: a workspace turns on Deployments with a monthly plan145 /// An agent's run, or a paid feature's usage past its allowance.
Agents as a team: lifecycle, merge queue, billing and a new shell146 Usage,
147}
148
149/// One line of a workspace's statement.
150#[derive(Clone, Debug, Serialize, Deserialize)]
151#[serde(rename_all = "camelCase")]
152pub struct LedgerEntry {
153 pub id: String,
154 pub kind: EntryKind,
155 /// Positive for credit added, negative for usage.
156 pub amount_micros: i64,
157 pub description: String,
158 /// For usage: the repository and pull request the agent worked on.
159 pub repo: Option<String>,
160 pub number: Option<u32>,
161 /// For usage: `implement`, `review` or `update`.
162 pub task: Option<String>,
163 /// For usage: the model, by its public name.
164 pub model: Option<String>,
Integrations: your own model provider, alerts that open issues, tickets agents read165 /// For usage: `g1t` when g1t paid the model provider, `workspace` when
Prices are what g1t pays plus 20%, from the first second166 /// the workspace's own account did. Runs on the workspace's own
167 /// provider pay only their sandbox time now, so only older entries
168 /// are `workspace`.
Integrations: your own model provider, alerts that open issues, tickets agents read169 #[serde(default = "g1t")]
170 pub billed_to: String,
Agents as a team: lifecycle, merge queue, billing and a new shell171 /// For a top-up: the username of whoever paid.
172 pub created_by: Option<String>,
173 /// RFC 3339.
174 pub created_at: String,
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace175 /// The workspace the line belongs to, which tells an enterprise's
176 /// lines apart.
177 #[serde(default, skip_serializing_if = "Option::is_none")]
178 pub workspace: Option<String>,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look179 /// For usage: what the g1t plan's monthly included usage paid of it.
180 /// The entry's `amount_micros` is what is left to pay.
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put181 #[serde(default)]
182 pub credit_micros: i64,
183 /// For usage: what the workspace's trial credit paid of it.
184 #[serde(default)]
185 pub trial_micros: i64,
186 /// For usage: what g1t's open-source pool paid of it.
187 #[serde(default)]
188 pub oss_micros: i64,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look189 /// For usage: what g1t covered itself, such as the part of a free
190 /// workspace's last trial run that went past its trial credit.
191 #[serde(default)]
192 pub given_micros: i64,
Agents as a team: lifecycle, merge queue, billing and a new shell193}
194
Integrations: your own model provider, alerts that open issues, tickets agents read195fn g1t() -> String {
196 "g1t".to_owned()
197}
198
Agents as a team: lifecycle, merge queue, billing and a new shell199/// `account` (`Outcome<Account>`) and `ledger` (`Outcome<Vec<LedgerEntry>>`,
200/// newest first). Members of the workspace only.
201#[derive(Debug, Serialize, Deserialize)]
202pub struct AccountArgs {
203 pub workspace: String,
204 pub viewer: Viewer,
205}
206
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look207/// `checkout`: prepays usage: money paid in advance, drawn down by usage
208/// after the plan's included usage, which raises what can be used before
209/// work stops by the same amount at once. $25 at the least. By card, with
210/// 3-D Secure; from $1,000 also by bank transfer. Owners of the workspace
Agents as a team: lifecycle, merge queue, billing and a new shell211/// only. Returns `Outcome<Checkout>`.
212#[derive(Debug, Serialize, Deserialize)]
213#[serde(rename_all = "camelCase")]
214pub struct CheckoutArgs {
215 pub actor: User,
216 pub workspace: String,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look217 /// How much to prepay, in cents.
Agents as a team: lifecycle, merge queue, billing and a new shell218 pub amount_cents: u32,
219 /// Where the payment page sends the person afterwards. The payment's
220 /// id is appended as `session`.
221 pub return_url: String,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look222 /// `card` (the default) or `bank_transfer` (from $1,000): Stripe gives
223 /// the account details, and the money counts once it arrives.
224 #[serde(default)]
225 pub method: Option<String>,
Agents as a team: lifecycle, merge queue, billing and a new shell226}
227
228#[derive(Debug, Serialize, Deserialize)]
229pub struct Checkout {
230 /// The payment page to send the person to.
231 pub url: String,
232}
233
234/// `confirm`: credits a payment once the provider says it was made. Safe
235/// to call any number of times. Returns `Outcome<Account>`.
236#[derive(Debug, Serialize, Deserialize)]
237pub struct ConfirmArgs {
238 pub workspace: String,
239 pub viewer: Viewer,
240 /// The payment's id, as returned to `return_url`.
241 pub session: String,
242}
243
244/// `can_start`: whether a workspace may start an agent now, asked before
245/// anything is opened for it. Returns `Outcome<bool>`: a failure, with the
246/// reason to show, when it has no credit.
247#[derive(Debug, Serialize, Deserialize)]
248pub struct CanStartArgs {
249 pub workspace: String,
250}
251
252/// `start_run`: asks whether a workspace may start an agent, and opens the
253/// run it will be charged for. Called by the runner service. Returns
254/// `Outcome<Option<RunTicket>>`: no ticket when billing is off, a failure
255/// when the workspace has no credit.
256#[derive(Debug, Serialize, Deserialize)]
257pub struct StartRunArgs {
258 pub workspace: String,
259 pub repo: RepoPath,
260 pub number: u32,
261 /// `implement`, `review` or `update`.
262 pub task: String,
263 /// The model, by its public name.
264 pub model: String,
Integrations: your own model provider, alerts that open issues, tickets agents read265 /// `workspace` when the run uses the workspace's own model provider.
Models per workspace: several providers, routed by kind of work266 /// The runner, which is TypeScript, sends it as `billedTo`.
267 #[serde(default = "g1t", alias = "billedTo")]
Integrations: your own model provider, alerts that open issues, tickets agents read268 pub billed_to: String,
Prices keep themselves current with what g1t pays269 /// The model session's id, when its requests go through g1t's AI
270 /// Gateway: settling charges the run what the gateway priced them at.
271 #[serde(default)]
272 pub session: Option<String>,
Auto model routing: the cheapest tier that can do each piece of work, a retry goes up a tier, and each run records its tier273 /// `small` or `large`: the tier g1t routed the run to, when g1t pays
274 /// for its model. None on the workspace's own provider.
275 #[serde(default)]
276 pub tier: Option<String>,
Agents as a team: lifecycle, merge queue, billing and a new shell277}
278
279#[derive(Clone, Debug, Serialize, Deserialize)]
280#[serde(rename_all = "camelCase")]
281pub struct RunTicket {
282 pub run_id: String,
283 /// Lets the sandbox, and nothing else, report what this run cost.
284 pub token: String,
285}
286
287/// `finish_run`: what a run cost, as its sandbox reports it. Charged once.
288/// Returns `Outcome<bool>`.
289#[derive(Debug, Serialize, Deserialize)]
290#[serde(rename_all = "camelCase")]
291pub struct FinishRunArgs {
292 pub run_id: String,
293 pub token: String,
294 /// What the model provider charged, in US dollars.
295 pub cost_usd: f64,
296 #[serde(default)]
297 pub turns: u32,
298}
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request299
300
301/// `usage`: what a workspace's agents cost over a period, broken down.
302/// Members only. Returns `Outcome<Usage>`.
303#[derive(Debug, Serialize, Deserialize)]
304pub struct UsageArgs {
305 pub workspace: String,
306 pub viewer: Viewer,
307 /// RFC 3339: the start of the period. The period runs to now.
308 pub since: String,
309}
310
311/// One slice of usage: what it was for, what it cost, how many runs.
312#[derive(Clone, Debug, Serialize, Deserialize)]
313#[serde(rename_all = "camelCase")]
314pub struct UsageSlice {
315 pub key: String,
316 pub micros: i64,
317 pub runs: u32,
318}
319
320/// What a workspace's agents cost over a period.
321#[derive(Clone, Debug, Serialize, Deserialize)]
322#[serde(rename_all = "camelCase")]
323pub struct Usage {
324 pub since: String,
325 /// Charged, including g1t's margin.
326 pub spent_micros: i64,
Integrations: your own model provider, alerts that open issues, tickets agents read327 /// What g1t's model provider charged, before the margin.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request328 pub cost_micros: i64,
Integrations: your own model provider, alerts that open issues, tickets agents read329 /// What runs on the workspace's own provider cost there, as the harness
330 /// estimated it. Not charged by g1t.
331 pub provider_micros: i64,
Usage while free is shown at cost; agents get rustfmt and clippy332 /// What the runs used, at cost: g1t's models and the workspace's own
333 /// provider together, whatever was charged for them.
334 pub used_micros: i64,
335 /// g1t charges nothing for now. The slices then measure usage at cost,
336 /// since every charge is zero.
337 pub free: bool,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request338 pub runs: u32,
339 /// Spend per day (`YYYY-MM-DD`) and task, as `day/task` keys.
340 pub by_day: Vec<UsageSlice>,
341 /// Per task: implement, review, revise, update, plan.
342 pub by_task: Vec<UsageSlice>,
343 /// Per repository, `namespace/name`.
344 pub by_repo: Vec<UsageSlice>,
345 /// The pull requests that cost most, as `namespace/name#number`.
346 pub by_pull: Vec<UsageSlice>,
347 /// Per model, by its public name.
348 pub by_model: Vec<UsageSlice>,
349 /// Credit bought in the period.
350 pub added_micros: i64,
351}
Models per workspace: several providers, routed by kind of work352
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look353/// What a workspace pays a monthly price for. There is one plan, `plan`
354/// ("g1t"): a flat price per workspace, never per person, with included
355/// usage each month, more private storage, and deployments. Never free:
356/// `FREE_WHILE_BUILDING` does not cover it.
357///
358/// `deployments` is not sold on its own any more: it comes with the plan.
359/// A service that asks `has_feature` for it is told whether the workspace
360/// has the plan, and a Deployments subscription bought before the change
361/// keeps working until its period ends.
Paid features: a workspace turns on Deployments with a monthly plan362#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
363#[serde(rename_all = "snake_case")]
364pub enum Feature {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look365 /// The g1t plan. Older readers called it `team`.
366 #[serde(alias = "team")]
367 Plan,
368 /// Previews per pull request and production on g1t.page: part of the
369 /// plan.
Paid features: a workspace turns on Deployments with a monthly plan370 Deployments,
371}
372
373impl Feature {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look374 /// What is sold: the plan alone.
375 pub const ALL: [Feature; 1] = [Feature::Plan];
Paid features: a workspace turns on Deployments with a monthly plan376
377 pub fn as_str(self) -> &'static str {
378 match self {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look379 Feature::Plan => "plan",
Paid features: a workspace turns on Deployments with a monthly plan380 Feature::Deployments => "deployments",
381 }
382 }
383
384 pub fn parse(name: &str) -> Option<Feature> {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look385 match name {
386 "plan" | "team" => Some(Feature::Plan),
387 "deployments" => Some(Feature::Deployments),
388 _ => None,
389 }
Paid features: a workspace turns on Deployments with a monthly plan390 }
391
392 pub fn title(self) -> &'static str {
393 match self {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look394 Feature::Plan => "g1t",
Paid features: a workspace turns on Deployments with a monthly plan395 Feature::Deployments => "Deployments",
396 }
397 }
398}
399
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas400/// What deployments cost g1t, in millionths of a dollar: fallbacks for
401/// when billing's price book cannot be read. Nothing here is an allowance:
402/// on the plan every unit is metered from the first, at cost plus the
403/// margin, and drawn from the plan's included usage before anything is
404/// charged. Projects, previews and the apps behind them are not metered at
405/// all: Cloudflare's Workers for Platforms includes far more scripts than
406/// g1t runs, so an app costs g1t only the requests and CPU it answers with.
407pub mod deployment_costs {
408 /// Workers for Platforms: $0.30 per million requests.
Paid features: a workspace turns on Deployments with a monthly plan409 pub const MICROS_PER_MILLION_REQUESTS: i64 = 300_000;
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas410 /// $0.02 per million CPU milliseconds.
Paid features: a workspace turns on Deployments with a monthly plan411 pub const MICROS_PER_MILLION_CPU_MS: i64 = 20_000;
Deployments: a preview for every pull request, production on g1t.page412 /// What one second of a build's sandbox costs g1t (Cloudflare
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look413 /// Containers, standard-1: half a vCPU, 4 GiB, 8 GB disk), rounded up,
414 /// as the price keeper measured it on 2026-10-05 (14.5). Only a
415 /// fallback: billing charges builds at the price book's `build_second`,
416 /// which the keeper keeps current.
417 pub const MICROS_PER_BUILD_SECOND: i64 = 15;
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas418 /// What one custom hostname costs g1t a month (Cloudflare for SaaS):
419 /// $0.10.
Agents and memory, checks and conflicts, profiles, slug renames, custom domains420 pub const MICROS_PER_DOMAIN_MONTH: i64 = 100_000;
Paid features: a workspace turns on Deployments with a monthly plan421}
422
Every sandbox is metered by the second423/// `record_sandbox`: how long one sandbox ran for a workspace, reported by
Prices are what g1t pays plus 20%, from the first second424/// the runner when it stops. Every sandbox g1t starts for a workspace
425/// (agents, reviews, checks, the merge queue, workflow jobs) is metered by
426/// the second, from the first: recorded once per `reference`, with what it
427/// cost g1t, and charged at the price book's `sandbox_second` price unless
428/// `FREE_WHILE_BUILDING`. Deploy builds are charged by the Deployments plan
429/// instead.
Every sandbox is metered by the second430/// Returns `Outcome<bool>`: false if that reference was recorded before.
431#[derive(Debug, Serialize, Deserialize)]
432#[serde(rename_all = "camelCase")]
433pub struct RecordSandboxArgs {
434 pub workspace: String,
435 pub seconds: u32,
436 /// What ran, e.g. `Checks on acme/api#12`.
437 pub description: String,
438 /// `namespace/name`.
439 pub repo: Option<String>,
440 /// Unique to the run.
441 pub reference: String,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look442 /// What ran: `agent`, `check`, `workflow` or `queue`. Decides whether
443 /// g1t's open-source pool may pay for it (checks, workflows and the
444 /// merge queue on public repositories). Absent: not the pool.
445 #[serde(default)]
446 pub kind: Option<ComputeKind>,
447 /// The vCPU-seconds the sandbox used, when it can tell. With it, the
448 /// run is priced on its own CPU (`sandbox_base_second` per second plus
449 /// `sandbox_cpu_second` per vCPU-second); without it, at the average
450 /// (`sandbox_second`).
451 #[serde(default, alias = "cpu_seconds")]
452 pub cpu_seconds: Option<f64>,
453 /// The reservation the work started under, settled with this cost.
454 #[serde(default, alias = "reservation_id")]
455 pub reservation_id: Option<String>,
Fast pages, required checks on the branch, self-hosted runners, honest incidents456 /// It ran on one of the workspace's self-hosted runners: recorded as
457 /// self-hosted time, for the minutes, at $0.
458 #[serde(default, alias = "self_hosted")]
459 pub self_hosted: bool,
460 /// The machine it ran on, by label (`g1t-4core`); absent, the standard
461 /// one. A larger machine's memory and disk cost more each second.
462 #[serde(default)]
463 pub instance: Option<String>,
Every sandbox is metered by the second464}
465
Usage limits: unpaid usage can only go so far466/// How much a workspace has earned g1t's trust with money, which sets how
467/// far its unpaid usage can go before its work stops.
468#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
469#[serde(rename_all = "snake_case")]
470pub enum Trust {
471 /// No live payment yet: only a little past the free allowances.
472 New,
473 /// Has paid g1t real money: the ceiling grows with what it has paid.
474 Paid,
Two limits, real invoices, trust that grows by itself, sales signals475 /// Has paid steadily for months, with nothing disputed or declined:
476 /// the ceiling follows its monthly spend, up to $10,000, by itself.
477 Established,
Usage limits: unpaid usage can only go so far478 /// A ceiling g1t set by hand, after talking to the workspace.
479 Reviewed,
480 /// g1t's own workspaces: no ceiling.
481 Internal,
482}
483
484#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
485#[serde(rename_all = "snake_case")]
486pub enum LimitState {
487 Ok,
488 /// Past 80% of the ceiling.
489 Warning,
490 /// At or past it: no new sandboxes, builds or app requests.
491 Stopped,
492}
493
494/// How far a workspace's unpaid usage has gone this month, and where its
495/// work stops: like Fly's or Cloudflare's limits for new accounts, so no
496/// one runs up costs g1t cannot collect. Usage counts at what it cost g1t
497/// or what it is charged, whichever is more, so it counts while g1t is
498/// free too.
499#[derive(Clone, Debug, Serialize, Deserialize)]
500#[serde(rename_all = "camelCase")]
501pub struct Limit {
502 pub workspace: String,
Billing accounts, terms and enterprises; g1t is no longer free503 /// The account that pays, whose usage and payments the limit counts:
504 /// the workspace's own, or its enterprise's.
505 #[serde(default)]
506 pub account: String,
507 #[serde(default)]
508 pub account_name: String,
Usage limits: unpaid usage can only go so far509 pub trust: Trust,
510 /// Usage this month (UTC) less what was paid this month.
511 pub exposure_micros: i64,
512 /// Where work stops: the lower of g1t's ceiling and the owner's own
513 /// spend limit. None for g1t's own workspaces.
514 pub ceiling_micros: Option<i64>,
515 /// The ceiling g1t sets from `trust`.
516 pub trust_ceiling_micros: Option<i64>,
517 /// The owner's own monthly limit, if they set one.
518 pub spend_limit_micros: Option<i64>,
519 pub state: LimitState,
520 /// What to tell people when work is stopped or close to it.
521 pub message: Option<String>,
Two limits, real invoices, trust that grows by itself, sales signals522 /// Charged this month, which the spend limit is measured against.
523 #[serde(default)]
524 pub spent_micros: i64,
525 /// True while the owners have not chosen a spend limit of their own, so
526 /// the automatic one applies: $200, or twice last month's spend.
527 #[serde(default)]
528 pub default_spend_limit: bool,
529 /// The most the owners may set their own limit to: g1t's ceiling. To
530 /// go past it, they contact g1t.
531 #[serde(default)]
532 pub available_micros: Option<i64>,
533 /// How the ceiling grows from here, in a sentence.
534 #[serde(default)]
535 pub growth: Option<String>,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look536 /// Money paid in advance and not used yet. It raises what can be used
537 /// before work stops by the same amount, at once.
538 #[serde(default)]
539 pub prepaid_micros: i64,
540 /// The highest ceiling the workspace has ever had. Owners may set their
541 /// spend limit anywhere up to it (plus what is prepaid) without asking.
542 #[serde(default)]
543 pub max_ceiling_micros: Option<i64>,
544 /// The most the owners may raise the limit to themselves, once, with
545 /// `raise_once`: twice the highest ceiling. None once it is used.
546 #[serde(default)]
547 pub raise_once_micros: Option<i64>,
548 /// When the one-time raise was used, RFC 3339.
549 #[serde(default)]
550 pub raised_at: Option<String>,
551 /// True in a paid workspace's first billing cycle, when the ceiling is
552 /// the starting one (`LIMIT_PAID_START_MICROS`).
553 #[serde(default)]
554 pub first_month: bool,
Usage limits: unpaid usage can only go so far555}
556
557/// `limit`: a workspace's limit, for its members. Returns `Outcome<Limit>`.
558#[derive(Debug, Serialize, Deserialize)]
559pub struct LimitArgs {
560 pub workspace: String,
561 pub viewer: Viewer,
562}
563
564/// `check_limit`: the same, for the services that enforce it. Returns
565/// `Outcome<Limit>`.
566#[derive(Debug, Serialize, Deserialize)]
567pub struct CheckLimitArgs {
568 pub workspace: String,
569}
570
Prices keep themselves current with what g1t pays571/// `note_pending`: usage this month that will be charged later, such as
572/// app traffic past a plan, so the workspace's limit counts it now. Each
573/// report replaces the last for that workspace, source and month. Called
574/// by the service that meters it. Returns `bool`.
575#[derive(Debug, Serialize, Deserialize)]
576#[serde(rename_all = "camelCase")]
577pub struct NotePendingArgs {
578 pub workspace: String,
Fast pages, required checks on the branch, self-hosted runners, honest incidents579 /// `deployments`, `security` (scans), `context` (search embeddings),
580 /// `storage` or `cache` (actions/cache, plan only). Billing charges
581 /// `security`, `context`, `storage` and `cache` itself once the month
582 /// is over; `deployments` charges its own.
Prices keep themselves current with what g1t pays583 pub source: String,
584 /// What it cost g1t so far this month, before the margin.
585 pub cost_micros: i64,
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas586 /// How much of it, for the Billing page: `1.2 million requests and
587 /// 3.4 million CPU ms`, `2 custom domains`.
588 #[serde(default)]
589 pub detail: Option<String>,
Prices keep themselves current with what g1t pays590}
591
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas592/// `usage_meters`: this month's usage for a workspace, one line per kind
593/// of meter, at what it is charged (cost plus the margin, on the account's
594/// terms) before the plan's included usage, the trial or g1t's pools paid
595/// for any of it. Members only. Returns `Outcome<Vec<MeterUsage>>`.
596#[derive(Debug, Serialize, Deserialize)]
597pub struct UsageMetersArgs {
598 pub workspace: String,
599 pub viewer: Viewer,
600}
601
602/// One kind of meter's usage this month.
603#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
604#[serde(rename_all = "camelCase")]
605pub struct MeterUsage {
606 /// `agents` (agent runs, models and sandboxes for checks, workflows
607 /// and the merge queue), `builds`, `requests` (app requests and CPU),
608 /// `domains`, `git_storage` (git operations and private storage) or
609 /// `search_scans` (search embeddings and security scans).
610 pub key: String,
611 pub label: String,
612 /// At price, before what paid for it.
613 pub micros: i64,
614 /// How much, when it is known: `12 runs`, `41 build minutes`.
615 #[serde(default)]
616 pub quantity: Option<String>,
617}
618
Usage limits: unpaid usage can only go so far619/// `set_spend_limit`: the owner's own monthly ceiling, under g1t's; None
620/// removes it. Owners only. Returns `Outcome<Limit>`.
621#[derive(Debug, Serialize, Deserialize)]
622#[serde(rename_all = "camelCase")]
623pub struct SetSpendLimitArgs {
624 pub actor: User,
625 pub workspace: String,
Two limits, real invoices, trust that grows by itself, sales signals626 /// A monthly limit, at most what is available; None goes back to the
627 /// default.
Usage limits: unpaid usage can only go so far628 pub spend_limit_micros: Option<i64>,
Two limits, real invoices, trust that grows by itself, sales signals629 /// Use everything available, with no limit of their own.
630 #[serde(default)]
631 pub use_full_limit: bool,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look632 /// Use the one-time raise: up to twice the highest ceiling the
633 /// workspace has had, without asking. Once per workspace.
634 #[serde(default, alias = "raiseOnce")]
635 pub raise_once: bool,
Usage limits: unpaid usage can only go so far636}
637
Prices keep themselves current with what g1t pays638/// One metered unit: what it costs g1t, and what it is sold at. The price
639/// is always `cost × (100 + markup) / 100`, so it follows the cost.
640#[derive(Clone, Debug, Serialize, Deserialize)]
641#[serde(rename_all = "camelCase")]
642pub struct Price {
643 /// `sandbox_second`, `build_second`, `app_requests`, `app_cpu`, `app_month`.
644 pub meter: String,
645 pub title: String,
646 pub unit: String,
647 /// Millionths of a dollar per unit; may have a fraction.
648 pub cost_micros: f64,
649 pub markup_percent: u32,
650 pub price_micros: f64,
651 /// `list`: Cloudflare's published price. `cloudflare`: what Cloudflare
652 /// actually billed g1t, measured.
653 pub source: String,
654 /// When it was last checked against Cloudflare's bill.
655 pub checked_at: Option<String>,
656 pub updated_at: String,
657}
658
659impl Price {
660 pub fn price_for(cost_micros: f64, markup_percent: u32) -> f64 {
661 cost_micros * f64::from(100 + markup_percent) / 100.0
662 }
663}
664
665/// A cost that moved.
666#[derive(Clone, Debug, Serialize, Deserialize)]
667#[serde(rename_all = "camelCase")]
668pub struct PriceChange {
669 pub meter: String,
670 pub old_cost_micros: f64,
671 pub new_cost_micros: f64,
672 pub markup_percent: u32,
Prices are what g1t pays plus 20%, from the first second673 /// The markup before, when the change was to the markup rather than
674 /// to the cost. Absent when the markup stayed `markup_percent`.
675 #[serde(default, skip_serializing_if = "Option::is_none")]
676 pub old_markup_percent: Option<u32>,
Prices keep themselves current with what g1t pays677 pub reason: String,
678 pub created_at: String,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily679 /// When a change still to come takes effect: a rise is announced
680 /// before it is charged. Absent for changes already made.
681 #[serde(default, skip_serializing_if = "Option::is_none")]
682 pub effective_at: Option<String>,
Prices keep themselves current with what g1t pays683}
684
685/// `prices`: every metered price and the recent changes. Public. Returns
686/// `PriceBook`.
687#[derive(Clone, Debug, Serialize, Deserialize)]
688#[serde(rename_all = "camelCase")]
689pub struct PriceBook {
690 pub prices: Vec<Price>,
691 pub changes: Vec<PriceChange>,
692 /// The margin on model usage, which is charged at what AI Gateway
693 /// priced each request at.
694 pub model_margin_percent: u32,
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put695 /// Every plan, as it is sold now.
696 #[serde(default)]
697 pub plans: Vec<Plan>,
698 /// What is free, and what pays for it.
699 #[serde(default)]
700 pub free: Option<FreeTier>,
Prices keep themselves current with what g1t pays701}
702
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put703/// What g1t gives without a plan, each with what pays for it: a capped
704/// budget, never an open-ended allowance.
705#[derive(Clone, Debug, Default, Serialize, Deserialize)]
706#[serde(rename_all = "camelCase")]
707pub struct FreeTier {
708 /// Each new workspace's trial credit, once.
709 pub trial_workspace_micros: i64,
710 /// Trial grants each month, in all; new trials wait when it is spent.
711 pub trial_monthly_pool_micros: i64,
712 /// g1t's open-source pool each month, and any one repository's share.
713 pub oss_pool_micros: i64,
714 pub oss_repo_micros: i64,
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas715 /// Private repository storage that is free for every workspace. Past
716 /// it, the plan pays at cost plus the margin; a free workspace's pushes
717 /// to private repositories stop instead.
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put718 pub free_private_storage_bytes: i64,
Audit logs are kept by plan: a week on free, 90 days on the plan, and what staff set for an account in sudo719 /// Days of audit log a free workspace keeps.
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put720 pub audit_retention_days: u32,
Audit logs are kept by plan: a week on free, 90 days on the plan, and what staff set for an account in sudo721 /// Days of audit log the g1t plan keeps, and g1t's own and enterprise
722 /// workspaces. Longer is by arrangement, set per account in sudo.
723 #[serde(default)]
724 pub plan_audit_retention_days: u32,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look725 /// The smallest amount a card is charged when a month closes; less
726 /// carries over. Charges at a limit always go through.
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put727 pub min_charge_micros: i64,
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas728 /// Git operations (clones, fetches and pushes through g1t) that are
729 /// free for every workspace each month. Past it, the plan pays at cost
730 /// plus the margin and is never slowed; a free workspace is slowed
731 /// down, never charged.
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look732 #[serde(default)]
733 pub git_operations_included: u64,
734 /// A new paid workspace's ceiling in its first month.
735 #[serde(default)]
736 pub paid_start_ceiling_micros: i64,
737 /// The most a one-click goodwill credit can cost g1t.
738 #[serde(default)]
739 pub overage_forgive_cost_micros: i64,
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put740}
741
Billing accounts, terms and enterprises; g1t is no longer free742/// Who pays: a billing account. Every workspace has one; by default its
743/// own. An enterprise account pays for several workspaces at once, as
744/// GitHub Enterprise does: one bill, one limit, one set of terms.
745#[derive(Clone, Debug, Serialize, Deserialize)]
746#[serde(rename_all = "camelCase")]
747pub struct BillingAccount {
748 /// `ws_<slug>` for a workspace's own account; `ent_…` for an enterprise.
749 pub id: String,
750 pub kind: AccountKind,
751 pub name: String,
752 pub terms: Terms,
753 /// The workspaces it pays for.
754 pub workspaces: Vec<String>,
Stripe webhooks, enterprise invoices, and sudo for both755 /// Where an enterprise's invoices go.
756 #[serde(default)]
757 pub billing_email: Option<String>,
758 /// An enterprise's invoices, newest first. Empty for a workspace's own.
759 #[serde(default)]
760 pub invoices: Vec<EnterpriseInvoice>,
Billing accounts, terms and enterprises; g1t is no longer free761 pub created_at: String,
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put762 /// What g1t staff set for the account beyond its terms.
763 #[serde(default)]
764 pub allowances: Allowances,
765}
766
767/// Set per account by g1t staff in sudo, on top of its terms.
768#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
769#[serde(rename_all = "camelCase")]
770pub struct Allowances {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look771 /// The g1t plan without paying for its monthly price, such as for a
772 /// partner. Usage is charged as usual. Comped accounts have it anyway.
773 #[serde(default, alias = "team")]
774 pub plan: bool,
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put775 /// Each of the account's public repositories' monthly cap on g1t's
776 /// open-source pool, in place of `OSS_REPO_MICROS`. None: the default.
777 #[serde(default)]
778 pub oss_repo_micros: Option<i64>,
779 /// The trial credit each of its workspaces gets, in place of
780 /// `TRIAL_WORKSPACE_MICROS`, outside the monthly pool. None: the default.
781 #[serde(default)]
782 pub trial_micros: Option<i64>,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look783 /// Agents at once, in place of the plan's (2 in the first month or on
784 /// the trial, then 10). None: the default.
785 #[serde(default)]
786 pub max_concurrent_agents: Option<u32>,
787 /// One run's spend cap, in place of `RUN_CAP_MICROS` and the owners'
788 /// own. None: theirs, or the default.
789 #[serde(default)]
790 pub run_cap_micros: Option<i64>,
791 /// What the agents on one issue may spend in all, in place of
792 /// `ISSUE_CAP_MICROS` and the owners' own. None: theirs, or the default.
793 #[serde(default)]
794 pub issue_cap_micros: Option<i64>,
Audit logs are kept by plan: a week on free, 90 days on the plan, and what staff set for an account in sudo795 /// Days of audit log its workspaces keep, in place of the plan's (7
796 /// free, 90 on the plan), longer or shorter. None: the plan's.
797 #[serde(default)]
798 pub audit_retention_days: Option<u32>,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look799 /// A hold g1t staff put on new compute, with why. None: no hold.
800 #[serde(default)]
801 pub hold: Option<String>,
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put802}
803
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look804/// `admin_set_allowances`: the plan on or off without charge, overrides of
805/// the plan's caps, a hold, and the account's share of g1t's pools.
806/// Recorded with who and why. Returns `Outcome<BillingAccount>`.
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put807#[derive(Debug, Serialize, Deserialize)]
808pub struct AdminSetAllowancesArgs {
809 pub id: String,
810 pub allowances: Allowances,
811 pub note: String,
812 pub by: String,
813}
814
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look815// --- Entitlements, and compute started under a reservation -----------------
816//
817// Every service that starts compute (sandboxes for agents, checks,
818// workflows and the merge queue; builds; models; semantic search) asks
819// billing first:
820//
821// 1. `entitlements { workspace }` says what the workspace may do at all:
822// its plan, whether it may start compute, its caps, and whether compute
823// is paused.
824// 2. `reserve { workspace, repo, public, kind, estimate_micros }` holds the
825// work's estimated cost against what may pay for it, so that starts at
826// the same moment cannot overshoot the ceiling together. It answers who
827// pays first, or refuses with a stable code and a message for the owner.
828// 3. `settle { reservation_id, actual_micros }` releases the hold once the
829// work is done. The charge itself goes on the ledger the usual way
830// (`finish_run`, `record_sandbox`, `charge_feature`, `note_pending`).
831//
832// A reservation never settled expires after `RESERVATION_HOURS`.
833
834/// A reservation that is never settled stops holding after this long.
835pub const RESERVATION_HOURS: u64 = 3;
836/// What a ceiling reads as when there is none (g1t's own workspaces): a
837/// billion dollars, which JavaScript holds exactly.
838pub const UNLIMITED_MICROS: i64 = 1_000_000_000_000_000;
839
840/// What a workspace pays g1t on, as far as compute is concerned.
841#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
842#[serde(rename_all = "snake_case")]
843pub enum PlanKind {
844 /// No plan: the forge is free; compute only from a trial or g1t's
845 /// open-source pool, after a card check.
846 Free,
847 /// The g1t plan, paid for (or given by g1t staff without its price).
848 Paid,
849 /// g1t's own workspaces and Flagon's (comped terms): the plan without
850 /// being charged. Usage is still recorded at what it cost.
851 Internal,
852 /// Paid for by an enterprise account, invoiced.
853 Enterprise,
854}
855
856impl PlanKind {
857 pub fn as_str(self) -> &'static str {
858 match self {
859 PlanKind::Free => "free",
860 PlanKind::Paid => "paid",
861 PlanKind::Internal => "internal",
862 PlanKind::Enterprise => "enterprise",
863 }
864 }
865
866 /// Whether usage past what is included may be charged (on demand).
867 pub fn on_demand(self) -> bool {
868 !matches!(self, PlanKind::Free)
869 }
870}
871
872/// What compute is for.
873#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
874#[serde(rename_all = "snake_case")]
875pub enum ComputeKind {
876 /// An agent's run: its sandbox and its model.
877 Agent,
878 /// Checks on a pull request.
879 Check,
880 /// A workflow job.
881 Workflow,
882 /// The merge queue's checks.
883 Queue,
884 /// A deployment's build.
885 Deploy,
886 /// Semantic search: embeddings in the context hub.
887 Embedding,
888}
889
890impl ComputeKind {
891 pub fn as_str(self) -> &'static str {
892 match self {
893 ComputeKind::Agent => "agent",
894 ComputeKind::Check => "check",
895 ComputeKind::Workflow => "workflow",
896 ComputeKind::Queue => "queue",
897 ComputeKind::Deploy => "deploy",
898 ComputeKind::Embedding => "embedding",
899 }
900 }
901
902 /// Whether g1t's open-source pool may pay for it on a public
903 /// repository: checks, workflows and the merge queue only.
904 pub fn open_source_pool(self) -> bool {
905 matches!(self, ComputeKind::Check | ComputeKind::Workflow | ComputeKind::Queue)
906 }
907}
908
909/// Who pays first for reserved work. What the first source cannot cover
910/// falls to the next, in this order.
911#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
912#[serde(rename_all = "snake_case")]
913pub enum PaidBy {
914 /// The plan's included usage this month.
915 Credit,
916 /// The workspace's one-time trial credit.
917 Trial,
918 /// g1t's open-source pool.
919 Oss,
920 /// Charged to the workspace, at cost plus the margin.
921 OnDemand,
922}
923
924/// `entitlements`: what a workspace may do now, for the services that
925/// start compute and the pages that show it. Takes `EntitlementsArgs`;
926/// returns `Entitlements`. No viewer: callers decide who sees it.
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put927#[derive(Debug, Serialize, Deserialize)]
928pub struct EntitlementsArgs {
929 pub workspace: String,
930}
931
Audit logs are kept by plan: a week on free, 90 days on the plan, and what staff set for an account in sudo932/// `audit_retention`: how many days of audit log each workspace keeps, for
933/// the events service's daily purge. Takes `AuditRetentionArgs`; returns
934/// `Vec<AuditRetention>`, one for each workspace asked about.
935#[derive(Debug, Serialize, Deserialize)]
936pub struct AuditRetentionArgs {
937 pub workspaces: Vec<String>,
938}
939
940#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
941pub struct AuditRetention {
942 pub workspace: String,
943 pub days: u32,
944}
945
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put946#[derive(Clone, Debug, Serialize, Deserialize)]
947#[serde(rename_all = "camelCase")]
948pub struct Entitlements {
949 pub workspace: String,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look950 pub plan: PlanKind,
951 /// May start sandboxes, models, deployments and semantic search at all:
952 /// paid, internal and enterprise workspaces, or a free one with trial
953 /// credit left. A free workspace may still use the open-source pool
954 /// for checks, workflows and the merge queue on public repositories
955 /// after a card check; `reserve` decides that per start.
956 pub compute: bool,
957 /// The one-time trial credit left; 0 if none was granted or it is used.
958 pub trial_micros_left: i64,
959 /// A card check has been done. The trial and the open-source pool need
960 /// it.
961 pub trial_verified: bool,
962 /// A paid workspace still in its first billing cycle.
963 pub first_month: bool,
964 /// Agents at once: 2 in the first month or on the trial, 10 after;
965 /// staff can override it.
966 pub max_concurrent_agents: u32,
967 /// The longest one run may take: 60 minutes in the first month or on
968 /// the trial; otherwise the guardrails' own caps (`MAX_MINUTES`).
969 pub max_run_minutes: u32,
970 /// One run's spend cap (`RUN_CAP_MICROS`, $2 by default); staff can
971 /// override it.
972 pub run_cap_micros: i64,
973 /// What agents may spend on one issue in all (`ISSUE_CAP_MICROS`, $10
974 /// by default); the owners can set it (`set_caps`), and staff override.
975 pub issue_cap_micros: i64,
976 /// Where on-demand work stops: g1t's ceiling on usage not yet paid
977 /// for. `UNLIMITED_MICROS` for g1t's own workspaces; 0 for a free one,
978 /// which has no on-demand usage.
979 pub ceiling_micros: i64,
980 /// Usage not yet paid for this month, with prepayment taken off.
981 pub exposure_micros: i64,
982 /// Why new compute is paused, for the owner: the limit is reached, a
983 /// spend spike is waiting for an owner to confirm it, or g1t staff put
984 /// a hold on it. None when it is not.
985 pub paused: Option<String>,
986 // What the workspace's plan gives it, for its pages.
987 /// What open reservations hold now.
988 #[serde(default)]
989 pub held_micros: i64,
990 /// Paid in advance and not used yet.
991 #[serde(default)]
992 pub prepaid_micros: i64,
993 /// The plan's included usage each month, and what of it is used.
994 #[serde(default)]
995 pub included_micros: i64,
996 #[serde(default)]
997 pub included_used_micros: i64,
Audit logs are kept by plan: a week on free, 90 days on the plan, and what staff set for an account in sudo998 /// How far back the audit log can be read and exported, and what is
999 /// kept: the plan's days, or what g1t staff set for the account.
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1000 pub audit_retention_days: u32,
Audit logs are kept by plan: a week on free, 90 days on the plan, and what staff set for an account in sudo1001 /// Whether `audit_retention_days` is what staff set for the account
1002 /// rather than the plan's.
1003 #[serde(default)]
1004 pub audit_retention_custom: bool,
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1005 /// Private repository storage that is free for every workspace: past
1006 /// it, the plan pays for it and a free workspace's pushes stop.
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1007 pub free_private_storage_bytes: i64,
1008 /// The last daily measure of the workspace's private repositories.
1009 pub private_storage_bytes: i64,
1010 /// What g1t's open-source pool paid for the workspace this month.
1011 pub oss_paid_micros: i64,
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1012 /// Deploy build time this month, every second of it metered.
1013 #[serde(default)]
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1014 pub build_seconds_used: u32,
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1015 /// Git operations this month, and how many are free for every
1016 /// workspace (past it: metered on the plan, slowed when free).
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1017 #[serde(default)]
1018 pub git_operations: u64,
1019 #[serde(default)]
1020 pub git_operations_included: u64,
1021 /// The smallest amount a card is charged when a month closes.
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1022 pub min_charge_micros: i64,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1023 /// A spend spike waiting for an owner, or decided.
1024 #[serde(default)]
1025 pub spike: Option<Spike>,
1026 /// Where usage stands against what is included and the limits, from 50%.
1027 #[serde(default)]
1028 pub alerts: Vec<UsageAlert>,
1029}
1030
1031/// One level reached: 50, 75, 90 or 100 percent of something.
1032#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1033#[serde(rename_all = "camelCase")]
1034pub struct UsageAlert {
1035 /// `included` (the plan's included usage), `spend_limit` (the owners'
1036 /// own limit) or `ceiling` (g1t's, on usage not yet paid for).
1037 pub meter: String,
1038 pub level: u32,
1039 pub used_micros: i64,
1040 pub limit_micros: i64,
1041 pub message: String,
Billing accounts, terms and enterprises; g1t is no longer free1042}
1043
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1044/// An hour's spend well above the workspace's usual pace: new compute
1045/// waits until an owner says to keep going.
1046#[derive(Clone, Debug, Serialize, Deserialize)]
1047#[serde(rename_all = "camelCase")]
1048pub struct Spike {
1049 pub id: String,
1050 /// `open` (waiting for an owner), `continued` (an owner said keep
1051 /// going) or `stopped` (an owner said stop).
1052 pub status: String,
1053 /// The hour's spend when it was found, and the usual hour's.
1054 pub hour_micros: i64,
1055 pub average_micros: i64,
1056 pub detected_at: String,
1057 #[serde(default)]
1058 pub decided_by: Option<String>,
1059 #[serde(default)]
1060 pub decided_at: Option<String>,
1061 /// While continued: until when, unless spend doubles again first.
1062 #[serde(default)]
1063 pub until: Option<String>,
1064}
1065
1066/// `reserve`: holds an estimate of a start's cost before the work starts.
1067/// Returns `Outcome<Reservation>`, or a failure whose code says why not:
1068///
1069/// - `paused`: a spend spike waiting for an owner, or a hold.
1070/// - `limit`: the spend limit or g1t's ceiling would be passed.
1071/// - `not_paid`: no plan, and nothing else pays for this kind of work (or
1072/// no card check yet).
1073/// - `trial_used`: the one-time trial is spent.
1074/// - `oss_pool_empty`: the open-source pool, or the repository's share of
1075/// it, is spent this month.
1076///
1077/// The message says exactly what to do, with the page to do it on (such as
1078/// `/acme/-/billing`).
1079#[derive(Debug, Serialize, Deserialize)]
1080#[serde(rename_all = "camelCase")]
1081pub struct ReserveArgs {
1082 pub workspace: String,
1083 pub repo: RepoPath,
1084 /// Whether the repository is public: the open-source pool pays only for
1085 /// public repositories' checks, workflows and merge queue.
1086 pub public: bool,
1087 pub kind: ComputeKind,
1088 /// The most the work is expected to cost g1t, before the margin, in
1089 /// millionths of a dollar (billing adds the margin, as it does to every
1090 /// charge). For an agent, its model's average plus its sandbox for its
1091 /// whole time cap.
1092 #[serde(alias = "estimate_micros")]
1093 pub estimate_micros: i64,
Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays1094 /// An agent run on g1t's hosted models (not the workspace's own
1095 /// provider). Unsaid, an agent run is taken to be one. g1t's daily
1096 /// spend breaker pauses these when g1t is paying for them.
1097 #[serde(default, alias = "hosted_model")]
1098 pub hosted_model: Option<bool>,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1099}
1100
1101#[derive(Clone, Debug, Serialize, Deserialize)]
1102#[serde(rename_all = "camelCase")]
1103pub struct Reservation {
1104 pub id: String,
1105 pub paid_by: PaidBy,
1106 /// What is held, at cost; less than the estimate when a free
1107 /// workspace's last bit of trial credit is all there is.
1108 #[serde(default)]
1109 pub held_micros: i64,
1110 /// RFC 3339: when the hold lapses if never settled.
1111 #[serde(default)]
1112 pub expires_at: String,
1113}
1114
1115/// `settle`: releases a reservation's hold with what the work cost. The
1116/// charge goes on the ledger the usual way. Safe to repeat. Returns
1117/// `Outcome<bool>`: false if it was settled or had lapsed before.
1118#[derive(Debug, Serialize, Deserialize)]
1119#[serde(rename_all = "camelCase")]
1120pub struct SettleArgs {
1121 #[serde(alias = "reservation_id")]
1122 pub reservation_id: String,
1123 /// What the work cost g1t, before the margin.
1124 #[serde(alias = "actual_micros")]
1125 pub actual_micros: i64,
1126}
1127
1128// --- Card checks, the plan, prepayment -------------------------------------
1129
1130/// `card_check`: starts Stripe's page to save and verify a card: a setup
1131/// with 3-D Secure where the card supports it, which the card's bank sees
1132/// as a $0 or $1 authorization that is never charged. The trial and the
1133/// open-source pool need it, and it is the card the plan uses. Owners only.
1134/// Returns `Outcome<Checkout>`; the page's id comes back to `return_url` as
1135/// `session`, for `confirm_card_check`.
1136#[derive(Debug, Serialize, Deserialize)]
1137#[serde(rename_all = "camelCase")]
1138pub struct CardCheckArgs {
1139 pub actor: User,
1140 pub workspace: String,
1141 #[serde(alias = "return_url")]
1142 pub return_url: String,
1143}
1144
1145/// `confirm_card_check`: records the check once Stripe says the card was
1146/// verified, and grants the trial if the month's pool has room and the card
1147/// has not had one before. Safe to repeat. Returns `Outcome<Entitlements>`.
1148#[derive(Debug, Serialize, Deserialize)]
1149pub struct ConfirmCardCheckArgs {
1150 pub workspace: String,
1151 pub viewer: Viewer,
1152 pub session: String,
1153}
1154
1155// --- Limits: raising them, and spikes ---------------------------------------
1156
1157/// A request to g1t: a higher limit, or help with usage that went past
1158/// what was meant.
1159#[derive(Clone, Debug, Serialize, Deserialize)]
1160#[serde(rename_all = "camelCase")]
1161pub struct LimitRequest {
1162 pub id: String,
1163 pub workspace: String,
1164 /// `limit` (raise my limit) or `overage` (spent more than meant to).
1165 pub kind: String,
1166 /// The limit asked for; for an overage, what they think went wrong.
1167 pub amount_micros: i64,
1168 pub reason: String,
1169 pub expected_monthly_micros: i64,
1170 /// `open`, `approved` or `declined`.
1171 pub status: String,
1172 /// What was approved, which may differ from what was asked.
1173 #[serde(default)]
1174 pub decided_micros: Option<i64>,
1175 #[serde(default)]
1176 pub decided_by: Option<String>,
1177 /// The answer, as the owner sees it.
1178 #[serde(default)]
1179 pub answer: Option<String>,
1180 pub created_by: String,
1181 pub created_at: String,
1182 #[serde(default)]
1183 pub decided_at: Option<String>,
1184}
1185
1186/// `request_limit`: an owner asks g1t for more, or for help with usage past
1187/// what they meant. Answered within one business day, in the app and by
1188/// email. Owners only. Returns `Outcome<LimitRequest>`.
1189#[derive(Debug, Serialize, Deserialize)]
1190#[serde(rename_all = "camelCase")]
1191pub struct RequestLimitArgs {
1192 pub actor: User,
1193 pub workspace: String,
1194 /// `limit` or `overage`.
1195 pub kind: String,
1196 #[serde(alias = "amount_micros")]
1197 pub amount_micros: i64,
1198 pub reason: String,
1199 #[serde(default, alias = "expected_monthly_micros")]
1200 pub expected_monthly_micros: i64,
1201}
1202
1203/// `limit_requests`: a workspace's requests, newest first. Members only.
1204/// Returns `Outcome<Vec<LimitRequest>>`.
1205#[derive(Debug, Serialize, Deserialize)]
1206pub struct LimitRequestsArgs {
1207 pub workspace: String,
1208 pub viewer: Viewer,
1209}
1210
1211/// `confirm_spike`: an owner's answer to a spend spike. Keep going lifts the
1212/// pause for 24 hours, or until the hour's spend doubles again; stop keeps
1213/// new compute paused until an owner says to keep going. Owners only.
1214/// Returns `Outcome<Entitlements>`.
1215#[derive(Debug, Serialize, Deserialize)]
1216#[serde(rename_all = "camelCase")]
1217pub struct ConfirmSpikeArgs {
1218 pub actor: User,
1219 pub workspace: String,
1220 #[serde(alias = "keep_going")]
1221 pub keep_going: bool,
1222}
1223
1224/// `set_caps`: the owners' own caps on agents: one run's spend ($0.10 to
1225/// $100) and what the agents on one issue may spend in all ($1 to $1,000).
1226/// None goes back to the default ($2 and $10). A cap g1t staff set for the
1227/// account wins over both. Owners only. Returns `Outcome<Entitlements>`.
1228#[derive(Debug, Serialize, Deserialize)]
1229#[serde(rename_all = "camelCase")]
1230pub struct SetCapsArgs {
1231 pub actor: User,
1232 pub workspace: String,
1233 #[serde(default, alias = "run_cap_micros")]
1234 pub run_cap_micros: Option<i64>,
1235 #[serde(default, alias = "issue_cap_micros")]
1236 pub issue_cap_micros: Option<i64>,
1237}
1238
1239/// What staff see beside a request: the workspace's history with g1t.
1240#[derive(Clone, Debug, Default, Serialize, Deserialize)]
1241#[serde(rename_all = "camelCase")]
1242pub struct WorkspaceHistory {
1243 pub plan: Option<PlanKind>,
1244 /// The last six months, oldest first.
1245 pub months: Vec<MonthFigures>,
1246 /// Live payments that have cleared, and how many.
1247 pub paid_cleared_micros: i64,
1248 pub payments: u32,
1249 pub disputes: u32,
1250 pub declines: u32,
1251 /// The first time the workspace appears in billing, RFC 3339.
1252 pub first_seen: Option<String>,
1253 pub ceiling_micros: Option<i64>,
1254 pub max_ceiling_micros: Option<i64>,
1255 pub spend_limit_micros: Option<i64>,
1256 /// Recent velocity: the last hour, the usual hour over the last week,
1257 /// and the last 24 hours, at price.
1258 pub last_hour_micros: i64,
1259 pub average_hour_micros: i64,
1260 pub last_day_micros: i64,
1261}
1262
1263#[derive(Clone, Debug, Serialize, Deserialize)]
1264#[serde(rename_all = "camelCase")]
1265pub struct LimitRequestReview {
1266 pub request: LimitRequest,
1267 pub history: WorkspaceHistory,
1268}
1269
1270/// `admin_limit_requests`: requests for staff, oldest open first. Returns
1271/// `Vec<LimitRequestReview>`.
1272#[derive(Debug, Default, Serialize, Deserialize)]
1273pub struct AdminLimitRequestsArgs {
1274 /// `open` (the default), `approved`, `declined` or `all`.
1275 #[serde(default)]
1276 pub status: Option<String>,
1277}
1278
1279/// `admin_decide_limit_request`: approve (at the amount asked, or
1280/// `amount_micros`) or decline. The owner is told in the app and by email.
1281/// Recorded with who and why. Returns `Outcome<LimitRequest>`.
1282#[derive(Debug, Serialize, Deserialize)]
1283pub struct AdminDecideLimitRequestArgs {
1284 pub id: String,
1285 /// `approve` or `decline`.
1286 pub decision: String,
1287 #[serde(default)]
1288 pub amount_micros: Option<i64>,
1289 /// What the owner is told, beside the decision.
1290 #[serde(default)]
1291 pub note: String,
1292 pub by: String,
1293}
1294
1295/// `admin_record_payment`: money that reached g1t outside the card pages,
1296/// such as a bank transfer, entered as a payment (it raises the limit like
1297/// one). Recorded with who and the transfer's reference. Returns
1298/// `Outcome<LedgerEntry>`.
1299#[derive(Debug, Serialize, Deserialize)]
1300pub struct AdminRecordPaymentArgs {
1301 pub workspace: String,
1302 pub amount_micros: i64,
1303 /// The bank's reference for the transfer, or Stripe's payment id.
1304 pub reference: String,
1305 pub note: String,
1306 pub by: String,
1307}
1308
1309// --- Overages and goodwill (sudo) --------------------------------------------
1310
1311/// What a one-time goodwill credit would come to: g1t's margin on the
1312/// overage, always, plus as much of its underlying cost as the cap allows.
1313#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1314#[serde(rename_all = "camelCase")]
1315pub struct Goodwill {
1316 /// This month's charges above the workspace's typical month.
1317 pub overage_micros: i64,
1318 /// The part of the overage that is g1t's margin.
1319 pub margin_micros: i64,
1320 /// The part that is what g1t paid its providers.
1321 pub cost_micros: i64,
1322 /// The one-click credit: the margin plus the cost up to the cap.
1323 pub credit_micros: i64,
1324 /// Of the credit, the real cost g1t absorbs.
1325 pub absorbed_micros: i64,
1326}
1327
1328/// A workspace whose month went well past its usual, or hit a spike.
1329#[derive(Clone, Debug, Serialize, Deserialize)]
1330#[serde(rename_all = "camelCase")]
1331pub struct Overage {
1332 pub workspace: String,
1333 pub plan: PlanKind,
1334 /// The median of its last three months' charges.
1335 pub typical_month_micros: i64,
1336 pub this_month_micros: i64,
1337 /// What this month cost g1t, and what g1t keeps of it.
1338 pub cost_micros: i64,
1339 pub margin_micros: i64,
1340 /// A spike this month, if there was one.
1341 pub spike: Option<Spike>,
1342 /// The runs that cost the most this month.
1343 pub top_entries: Vec<LedgerEntry>,
1344 pub goodwill: Goodwill,
1345 /// False when a goodwill credit was given in the last 12 months.
1346 pub goodwill_available: bool,
1347 pub last_goodwill_at: Option<String>,
1348 /// An open overage request from the owner, if there is one.
1349 pub request: Option<LimitRequest>,
1350}
1351
1352/// `admin_overages`: the Overages queue. Returns `Vec<Overage>`.
1353#[derive(Debug, Default, Serialize, Deserialize)]
1354pub struct AdminOveragesArgs {}
1355
1356/// `admin_goodwill`: credits a workspace for accidental usage. With no
1357/// amount, the one-click credit (`Goodwill::credit_micros`), once per
1358/// workspace in 12 months. A larger amount, or a second within 12 months,
1359/// needs a typed reason. It shows on the statement as "Credit from g1t:
1360/// accidental usage on <date>". Returns `Outcome<LedgerEntry>`.
1361#[derive(Debug, Serialize, Deserialize)]
1362pub struct AdminGoodwillArgs {
1363 pub workspace: String,
1364 #[serde(default)]
1365 pub amount_micros: Option<i64>,
1366 /// Why, typed by staff; needed past the one-click credit.
1367 #[serde(default)]
1368 pub reason: String,
1369 /// The day the accidental usage happened, `YYYY-MM-DD`; today if absent.
1370 #[serde(default)]
1371 pub day: Option<String>,
1372 pub by: String,
1373}
1374
1375/// One workspace's recent pace, for sudo's velocity view.
1376#[derive(Clone, Debug, Serialize, Deserialize)]
1377#[serde(rename_all = "camelCase")]
1378pub struct Velocity {
1379 pub workspace: String,
1380 pub plan: PlanKind,
1381 pub last_hour_micros: i64,
1382 pub average_hour_micros: i64,
1383 pub last_day_micros: i64,
1384 pub this_month_micros: i64,
1385 /// The last hour over the usual hour; 0 with no history.
1386 pub ratio: f64,
1387 pub spike: Option<Spike>,
1388 pub first_seen: Option<String>,
1389}
1390
1391/// `admin_velocity`: workspaces spending in the last day, fastest first.
1392/// Returns `Vec<Velocity>`.
1393#[derive(Debug, Default, Serialize, Deserialize)]
1394pub struct AdminVelocityArgs {}
1395
Billing accounts, terms and enterprises; g1t is no longer free1396#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1397#[serde(rename_all = "snake_case")]
1398pub enum AccountKind {
1399 Workspace,
1400 Enterprise,
1401}
1402
1403/// How an account is charged. Standard unless g1t set otherwise in sudo.
1404#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1405#[serde(rename_all = "camelCase")]
1406pub struct Terms {
1407 pub kind: TermsKind,
1408 /// Off every usage charge, in percent. Custom terms only.
1409 #[serde(default)]
1410 pub discount_percent: u32,
1411 /// A ceiling on unpaid usage that replaces the one trust would give.
1412 #[serde(default)]
1413 pub ceiling_micros: Option<i64>,
1414 /// Why, for whoever looks next.
1415 #[serde(default)]
1416 pub note: String,
1417 /// When the terms end and the account goes back to standard.
1418 #[serde(default)]
1419 pub until: Option<String>,
1420 #[serde(default)]
1421 pub set_by: Option<String>,
1422 #[serde(default)]
1423 pub set_at: Option<String>,
1424}
1425
1426impl Terms {
1427 pub fn standard() -> Self {
1428 Terms {
1429 kind: TermsKind::Standard,
1430 discount_percent: 0,
1431 ceiling_micros: None,
1432 note: String::new(),
1433 until: None,
1434 set_by: None,
1435 set_at: None,
1436 }
1437 }
1438
1439 /// What a charge becomes under these terms.
1440 pub fn apply(&self, charge_micros: i64) -> i64 {
1441 match self.kind {
1442 TermsKind::Comped => 0,
1443 TermsKind::Custom => charge_micros * i64::from(100 - self.discount_percent.min(100)) / 100,
1444 TermsKind::Standard => charge_micros,
1445 }
1446 }
1447}
1448
1449#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1450#[serde(rename_all = "snake_case")]
1451pub enum TermsKind {
1452 /// Prices as published, limits by trust.
1453 Standard,
1454 /// Nothing charged; usage still recorded with its cost. Paid features
1455 /// are on without a plan. For g1t's own workspaces, partners, and the
1456 /// like.
1457 Comped,
1458 /// A discount, a ceiling, or both.
1459 Custom,
1460}
1461
Stripe webhooks, enterprise invoices, and sudo for both1462/// `stripe_webhook`: an event from Stripe, as the API received it: the raw
1463/// body and its `Stripe-Signature` header. Billing checks the signature
1464/// against the secret of the endpoint it registered, and handles each
1465/// event once. Returns `Outcome<bool>`: false for one already handled.
1466#[derive(Debug, Serialize, Deserialize)]
1467pub struct StripeWebhookArgs {
1468 pub payload: String,
1469 pub signature: String,
1470}
1471
1472/// `admin_stripe`: where billing stands with Stripe. Staff only. Returns
Stripe's webhook secret is a Worker secret, STRIPE_WEBHOOK_SECRET, from a destination made in Stripe's dashboard1473/// `StripeStatus`. With `fix: true`, first enables the destination at
1474/// billing's address and gives it the events billing needs.
Stripe webhooks, enterprise invoices, and sudo for both1475#[derive(Debug, Default, Serialize, Deserialize)]
1476pub struct AdminStripeArgs {
1477 #[serde(default)]
Stripe's webhook secret is a Worker secret, STRIPE_WEBHOOK_SECRET, from a destination made in Stripe's dashboard1478 pub fix: bool,
Stripe webhooks, enterprise invoices, and sudo for both1479 #[serde(default)]
1480 pub by: Option<String>,
1481}
1482
1483#[derive(Clone, Debug, Serialize, Deserialize)]
1484#[serde(rename_all = "camelCase")]
1485pub struct StripeStatus {
1486 /// `test` or `live`, from the key; `off` without one.
1487 pub mode: String,
Stripe's webhook secret is a Worker secret, STRIPE_WEBHOOK_SECRET, from a destination made in Stripe's dashboard1488 /// Whether `STRIPE_WEBHOOK_SECRET` is set, so events can be checked.
1489 pub secret_set: bool,
1490 /// The destination at billing's address in Stripe, as Stripe has it.
Stripe webhooks, enterprise invoices, and sudo for both1491 pub webhook: Option<StripeWebhook>,
Stripe's webhook secret is a Worker secret, STRIPE_WEBHOOK_SECRET, from a destination made in Stripe's dashboard1492 /// Events billing handles that the destination does not send.
1493 pub missing_events: Vec<String>,
Stripe webhooks, enterprise invoices, and sudo for both1494 /// The latest events handled, newest first.
1495 pub recent_events: Vec<StripeEventSummary>,
Stripe's webhook secret is a Worker secret, STRIPE_WEBHOOK_SECRET, from a destination made in Stripe's dashboard1496 /// What went wrong reading or fixing the destination, if it did.
Stripe webhooks, enterprise invoices, and sudo for both1497 pub error: Option<String>,
1498}
1499
1500#[derive(Clone, Debug, Serialize, Deserialize)]
1501#[serde(rename_all = "camelCase")]
1502pub struct StripeWebhook {
1503 pub url: String,
1504 pub endpoint_id: String,
Stripe's webhook secret is a Worker secret, STRIPE_WEBHOOK_SECRET, from a destination made in Stripe's dashboard1505 /// `enabled` or `disabled`.
1506 pub status: String,
Stripe webhooks, enterprise invoices, and sudo for both1507 pub events: Vec<String>,
1508 pub created_at: String,
1509}
1510
1511#[derive(Clone, Debug, Serialize, Deserialize)]
1512#[serde(rename_all = "camelCase")]
1513pub struct StripeEventSummary {
1514 pub id: String,
1515 pub kind: String,
1516 pub outcome: String,
1517 pub received_at: String,
1518}
1519
1520/// `admin_enterprise_billing`: where an enterprise's invoices go. Creates
1521/// or updates its Stripe customer. Returns `Outcome<BillingAccount>`.
1522#[derive(Debug, Serialize, Deserialize)]
1523pub struct AdminEnterpriseBillingArgs {
1524 pub id: String,
1525 pub email: String,
1526 pub by: String,
1527}
1528
1529/// `admin_invoice_enterprise`: sends an enterprise its invoice now, for
1530/// what its workspaces owe, rather than waiting for the month to close.
1531/// Returns `Outcome<EnterpriseInvoice>`.
1532#[derive(Debug, Serialize, Deserialize)]
1533pub struct AdminInvoiceEnterpriseArgs {
1534 pub id: String,
1535 pub by: String,
1536}
1537
1538/// An enterprise's invoice: one line per workspace, paid on Stripe.
1539#[derive(Clone, Debug, Serialize, Deserialize)]
1540#[serde(rename_all = "camelCase")]
1541pub struct EnterpriseInvoice {
1542 pub invoice_id: String,
1543 /// Stripe's page for it, where it is paid.
1544 pub hosted_url: Option<String>,
1545 pub amount_micros: i64,
1546 /// `open`, `paid`, `overdue` or `void`.
1547 pub status: String,
1548 pub period: String,
1549 pub lines: Vec<InvoiceLine>,
1550 pub created_at: String,
1551}
1552
1553#[derive(Clone, Debug, Serialize, Deserialize)]
1554#[serde(rename_all = "camelCase")]
1555pub struct InvoiceLine {
1556 pub workspace: String,
1557 pub amount_micros: i64,
1558}
1559
Two limits, real invoices, trust that grows by itself, sales signals1560/// A workspace's invoice from g1t: one per month, and one each time it is
1561/// charged near its limit. Itemised, charged to the card on file, and kept
1562/// in Stripe's billing page with its PDF.
1563#[derive(Clone, Debug, Serialize, Deserialize)]
1564#[serde(rename_all = "camelCase")]
1565pub struct WorkspaceInvoice {
1566 pub invoice_id: String,
1567 pub workspace: String,
1568 /// `month` (2026-10) or `threshold`.
1569 pub reason: String,
1570 pub period: String,
1571 pub amount_micros: i64,
1572 /// `paid`, `open`, `failed` or `void`.
1573 pub status: String,
1574 pub hosted_url: Option<String>,
1575 pub pdf_url: Option<String>,
1576 pub lines: Vec<InvoiceItem>,
1577 pub created_at: String,
1578}
1579
1580#[derive(Clone, Debug, Serialize, Deserialize)]
1581#[serde(rename_all = "camelCase")]
1582pub struct InvoiceItem {
1583 pub description: String,
1584 pub amount_micros: i64,
1585}
1586
1587/// `invoices`: a workspace's invoices from g1t, newest first. Members
1588/// only. Returns `Outcome<Vec<WorkspaceInvoice>>`.
1589#[derive(Debug, Serialize, Deserialize)]
1590pub struct InvoicesArgs {
1591 pub workspace: String,
1592 pub viewer: Viewer,
1593}
1594
1595/// `admin_workspace_invoices`: the same, for staff. Returns
1596/// `Vec<WorkspaceInvoice>`.
1597#[derive(Debug, Serialize, Deserialize)]
1598pub struct AdminWorkspaceInvoicesArgs {
1599 pub workspace: String,
1600}
1601
The statement is a month at a time, a line per kind of charge1602/// `statement`: a month of a workspace's ledger, grouped by day (or by
1603/// project) with a line per kind of charge. Members only. Returns
1604/// `Outcome<Statement>`.
1605#[derive(Debug, Serialize, Deserialize)]
1606pub struct StatementArgs {
1607 pub workspace: String,
1608 pub viewer: Viewer,
1609 /// YYYY-MM; this month when absent.
1610 #[serde(default)]
1611 pub month: Option<String>,
1612 /// `day` (the default) or `project`.
1613 #[serde(default)]
1614 pub group: Option<String>,
1615}
1616
1617#[derive(Clone, Debug, Serialize, Deserialize)]
1618#[serde(rename_all = "camelCase")]
1619pub struct Statement {
1620 pub month: String,
1621 /// Months with any entries, newest first.
1622 pub months: Vec<String>,
1623 pub groups: Vec<StatementGroup>,
1624 pub totals: StatementTotals,
1625}
1626
1627#[derive(Clone, Debug, Serialize, Deserialize)]
1628#[serde(rename_all = "camelCase")]
1629pub struct StatementGroup {
1630 /// The day (YYYY-MM-DD) or the project (`owner/name`, or empty).
1631 pub key: String,
1632 pub label: String,
1633 pub lines: Vec<StatementLine>,
1634 /// What the group's charges come to.
1635 pub charged_micros: i64,
1636}
1637
1638#[derive(Clone, Debug, Serialize, Deserialize)]
1639#[serde(rename_all = "camelCase")]
1640pub struct StatementLine {
1641 /// Agent runs, Sandbox time, Deployments, Payments, Credits from g1t,
Prices are what g1t pays plus 20%, from the first second1642 /// Refunds, and, for older entries, Runs on your own model provider.
The statement is a month at a time, a line per kind of charge1643 pub kind: String,
1644 pub count: u32,
1645 /// Charges positive; money in (payments, credits) negative.
1646 pub charged_micros: i64,
1647 pub cost_micros: i64,
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1648 /// Of the usage on the line, what was paid for before it was charged:
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1649 /// by the plan's included usage, the trial credit, g1t's open-source
1650 /// pool, or g1t itself. Not in `charged_micros`.
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1651 #[serde(default)]
1652 pub covered_micros: i64,
The statement is a month at a time, a line per kind of charge1653}
1654
1655#[derive(Clone, Debug, Serialize, Deserialize)]
1656#[serde(rename_all = "camelCase")]
1657pub struct StatementTotals {
1658 pub charged_micros: i64,
1659 pub paid_micros: i64,
1660 pub cost_micros: i64,
1661 pub entries: u32,
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1662 /// What paid for usage before it was charged, one line per source,
1663 /// such as "Paid by g1t's open-source pool".
1664 #[serde(default)]
1665 pub covered: Vec<Covered>,
1666 /// Owed when the month closed but under the minimum charge, so it
1667 /// carries over to the next invoice. Zero when nothing carried.
1668 #[serde(default)]
1669 pub carried_micros: i64,
1670}
1671
1672/// One source that paid for usage before it was charged.
1673#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1674#[serde(rename_all = "camelCase")]
1675pub struct Covered {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1676 /// `included`, `trial`, `oss_pool` or `given`.
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1677 pub source: String,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1678 /// "Paid by your plan's included usage", "Paid by your trial credit",
1679 /// "Paid by g1t's open-source pool", "Covered by g1t".
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1680 pub label: String,
1681 pub micros: i64,
The statement is a month at a time, a line per kind of charge1682}
1683
1684/// `statement_entries`: one statement line's entries, newest first, 50 at
1685/// a time (`before` = the last id seen). Returns `Outcome<Vec<LedgerEntry>>`.
1686#[derive(Debug, Serialize, Deserialize)]
1687pub struct StatementEntriesArgs {
1688 pub workspace: String,
1689 pub viewer: Viewer,
1690 pub month: String,
1691 pub kind: String,
1692 #[serde(default)]
1693 pub day: Option<String>,
1694 #[serde(default)]
1695 pub project: Option<String>,
1696 #[serde(default)]
1697 pub before: Option<String>,
1698}
1699
Two limits, real invoices, trust that grows by itself, sales signals1700// --- Sales (sudo.g1t.sh) ------------------------------------------------------
1701//
1702// What staff need to know to reach out: who is growing, who is close to
1703// their limit, who was declined, who has become a steady customer. And what
1704// was done about it: a stage, an owner on g1t's side, a next step, notes.
1705
1706/// Why a workspace is worth a look.
1707#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1708#[serde(rename_all = "snake_case")]
1709pub enum SignalKind {
1710 /// At its limit, or its own spend limit: work is stopped.
1711 AtLimit,
1712 /// Past 80% of what is available to it: about to need more.
1713 NearCeiling,
1714 /// Its card was declined or a payment disputed.
1715 Declined,
1716 /// This month is well ahead of last month.
1717 Growing,
1718 /// Became Established: the ceiling now follows its spend.
1719 Established,
1720 /// Paid g1t for the first time.
1721 FirstPayment,
1722 /// Spending enough that custom terms or an enterprise may suit it.
1723 HighSpend,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1724 /// Costs g1t more on Cloudflare than it pays, over 30 days: a pricing
1725 /// gap or abuse to look at (billing's `margin`).
1726 CostOverRevenue,
Two limits, real invoices, trust that grows by itself, sales signals1727}
1728
1729#[derive(Clone, Debug, Serialize, Deserialize)]
1730#[serde(rename_all = "camelCase")]
1731pub struct Signal {
1732 pub workspace: String,
1733 pub kind: SignalKind,
1734 /// One sentence, with the figures.
1735 pub detail: String,
1736 /// The figure that matters, such as this month's spend.
1737 pub value_micros: i64,
1738 /// Its sales stage, if staff gave it one.
1739 pub stage: Option<String>,
1740 pub owner: Option<String>,
Billing lists every invoice and every staff change; signals carry follow-ups1741 #[serde(default)]
1742 pub next_step: Option<String>,
1743 /// When the next step is due, `YYYY-MM-DD`.
1744 #[serde(default)]
1745 pub next_at: Option<String>,
1746}
1747
1748/// `admin_invoices`: every invoice g1t has sent, workspaces' and
1749/// enterprises', newest first. Returns `Vec<InvoiceSummary>`.
1750#[derive(Debug, Default, Serialize, Deserialize)]
1751pub struct AdminInvoicesArgs {
1752 /// `paid`, `open`, `failed`, `overdue` or `void`.
1753 #[serde(default)]
1754 pub status: Option<String>,
1755 /// YYYY-MM, by when it was sent.
1756 #[serde(default)]
1757 pub month: Option<String>,
1758}
1759
1760#[derive(Clone, Debug, Serialize, Deserialize)]
1761#[serde(rename_all = "camelCase")]
1762pub struct InvoiceSummary {
1763 pub invoice_id: String,
1764 /// `workspace` or `enterprise`.
1765 pub kind: String,
1766 /// The workspace's slug, or the enterprise's account id.
1767 pub account: String,
1768 /// What to call it: the workspace, or the enterprise's name.
1769 pub name: String,
1770 pub reason: String,
1771 pub period: String,
1772 pub amount_micros: i64,
1773 pub status: String,
1774 pub hosted_url: Option<String>,
1775 pub created_at: String,
1776 pub paid_at: Option<String>,
1777}
1778
1779/// `admin_audit`: every change made in sudo, and by Stripe, newest first.
1780/// Returns `Vec<AdminAction>`.
1781#[derive(Debug, Default, Serialize, Deserialize)]
1782pub struct AdminAuditArgs {
1783 #[serde(default)]
1784 pub by: Option<String>,
1785 #[serde(default)]
1786 pub action: Option<String>,
1787 /// Only those before this time, for paging.
1788 #[serde(default)]
1789 pub before: Option<String>,
Two limits, real invoices, trust that grows by itself, sales signals1790}
1791
1792/// `admin_signals`: every workspace worth reaching out to, most urgent
1793/// first. Returns `Vec<Signal>`.
1794#[derive(Debug, Default, Serialize, Deserialize)]
1795pub struct AdminSignalsArgs {}
1796
1797/// What staff are doing about a workspace.
1798#[derive(Clone, Debug, Serialize, Deserialize)]
1799#[serde(rename_all = "camelCase")]
1800pub struct SalesRecord {
1801 pub workspace: String,
1802 /// `none`, `lead`, `contacted`, `negotiating`, `won`, `lost` or `churn_risk`.
1803 pub stage: String,
1804 /// The staff member looking after it.
1805 pub owner: Option<String>,
1806 pub next_step: Option<String>,
1807 /// RFC 3339 date.
1808 pub next_at: Option<String>,
1809 pub notes: Vec<SalesNote>,
1810 pub updated_at: Option<String>,
1811}
1812
1813#[derive(Clone, Debug, Serialize, Deserialize)]
1814#[serde(rename_all = "camelCase")]
1815pub struct SalesNote {
1816 pub id: String,
1817 pub text: String,
1818 pub by: String,
1819 pub created_at: String,
1820}
1821
1822/// `admin_sales`: a workspace's sales record. Returns `SalesRecord`.
1823#[derive(Debug, Serialize, Deserialize)]
1824pub struct AdminSalesArgs {
1825 pub workspace: String,
1826}
1827
1828/// `admin_set_sales`: its stage, owner and next step. Returns `Outcome<SalesRecord>`.
1829#[derive(Debug, Serialize, Deserialize)]
1830pub struct AdminSetSalesArgs {
1831 pub workspace: String,
1832 pub stage: String,
1833 #[serde(default)]
1834 pub owner: Option<String>,
1835 #[serde(default)]
1836 pub next_step: Option<String>,
1837 #[serde(default)]
1838 pub next_at: Option<String>,
1839 pub by: String,
1840}
1841
1842/// `admin_add_note`. Returns `Outcome<SalesRecord>`.
1843#[derive(Debug, Serialize, Deserialize)]
1844pub struct AdminAddNoteArgs {
1845 pub workspace: String,
1846 pub text: String,
1847 pub by: String,
1848}
1849
1850/// `admin_overview`: the business at a glance. Returns `Overview`.
1851#[derive(Debug, Default, Serialize, Deserialize)]
1852pub struct AdminOverviewArgs {}
1853
1854#[derive(Clone, Debug, Serialize, Deserialize)]
1855#[serde(rename_all = "camelCase")]
1856pub struct Overview {
1857 /// YYYY-MM.
1858 pub month: String,
1859 /// The last six months, oldest first, all workspaces together.
1860 pub months: Vec<MonthFigures>,
1861 /// This month by kind of usage: models, sandbox, deployments, plans.
1862 pub by_kind: Vec<KindFigures>,
1863 pub paying_workspaces: u32,
1864 pub stopped: u32,
1865 pub near_ceiling: u32,
1866 pub declined: u32,
1867 /// Sent and not yet paid, workspaces and enterprises.
1868 pub open_invoices_micros: i64,
1869 /// Follow-ups due today or earlier.
1870 pub follow_ups_due: u32,
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1871 /// The capped budgets g1t pays from, this month.
1872 #[serde(default)]
1873 pub pools: Option<Pools>,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1874 /// This month's revenue: usage charged plus the plan's price paid.
1875 #[serde(default)]
1876 pub revenue_micros: i64,
1877 /// Workspaces on the paid plan now, and what their price comes to a
1878 /// month.
1879 #[serde(default)]
1880 pub active_plans: u32,
1881 #[serde(default)]
1882 pub plan_mrr_micros: i64,
1883 /// What g1t gave this month, by source, apart from its margin.
1884 #[serde(default)]
1885 pub given: Vec<GivenFigures>,
1886 /// g1t's own and Flagon's workspaces this month: what their use cost,
1887 /// and why they are not charged.
1888 #[serde(default)]
1889 pub internal: Vec<InternalUse>,
1890 /// Open limit requests, and workspaces in the Overages queue.
1891 #[serde(default)]
1892 pub open_requests: u32,
1893 #[serde(default)]
1894 pub overages: u32,
1895 /// Spend spikes waiting for an owner.
1896 #[serde(default)]
1897 pub open_spikes: u32,
Two limits, real invoices, trust that grows by itself, sales signals1898}
1899
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1900/// What g1t gave this month from one source.
1901#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1902#[serde(rename_all = "camelCase")]
1903pub struct GivenFigures {
1904 /// `internal`, `trial`, `oss_pool`, `goodwill` or `covered`.
1905 pub source: String,
1906 pub label: String,
1907 /// At price, and what it cost g1t.
1908 pub micros: i64,
1909 pub cost_micros: i64,
1910}
1911
1912/// One internal workspace's use this month.
1913#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1914#[serde(rename_all = "camelCase")]
1915pub struct InternalUse {
1916 pub workspace: String,
1917 /// Why it is not charged: its terms' note.
1918 pub reason: String,
1919 pub cost_micros: i64,
1920 pub entries: u32,
1921}
1922
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put1923/// g1t's capped budgets for free usage, this calendar month (UTC).
1924#[derive(Clone, Debug, Default, Serialize, Deserialize)]
1925#[serde(rename_all = "camelCase")]
1926pub struct Pools {
1927 /// YYYY-MM.
1928 pub month: String,
1929 /// Trial grants made this month, against the month's pool.
1930 pub trial_granted_micros: i64,
1931 pub trial_pool_micros: i64,
1932 pub trial_grants: u32,
1933 /// What the open-source pool paid this month, against its cap.
1934 pub oss_used_micros: i64,
1935 pub oss_pool_micros: i64,
1936 /// Each public repository's monthly cap on the pool.
1937 pub oss_repo_micros: i64,
1938}
1939
Two limits, real invoices, trust that grows by itself, sales signals1940#[derive(Clone, Debug, Serialize, Deserialize)]
1941#[serde(rename_all = "camelCase")]
1942pub struct KindFigures {
1943 pub kind: String,
1944 pub charged_micros: i64,
1945 pub cost_micros: i64,
1946}
1947
Billing accounts, terms and enterprises; g1t is no longer free1948// --- Staff (sudo.g1t.sh) ------------------------------------------------------
1949//
1950// Called only by the sudo app, which only g1t staff can reach (behind
1951// Cloudflare Access). Each change names who made it, and is kept in the
1952// audit log.
1953
1954/// `admin_accounts`: every billing account, with where each stands this
1955/// month. Returns `Vec<AccountSummary>`.
1956#[derive(Debug, Default, Serialize, Deserialize)]
1957pub struct AdminAccountsArgs {
1958 #[serde(default)]
1959 pub query: Option<String>,
Stripe webhooks, enterprise invoices, and sudo for both1960 /// Exactly these workspaces' accounts, such as one page of sudo's
1961 /// list; every account with activity when absent.
1962 #[serde(default)]
1963 pub workspaces: Option<Vec<String>>,
Billing accounts, terms and enterprises; g1t is no longer free1964}
1965
1966#[derive(Clone, Debug, Serialize, Deserialize)]
1967#[serde(rename_all = "camelCase")]
1968pub struct AccountSummary {
1969 pub account: BillingAccount,
1970 pub limit: Limit,
1971 /// Charged this month, after terms.
1972 pub charged_micros: i64,
1973 /// What this month's usage cost g1t.
1974 pub cost_micros: i64,
1975 /// Paid, ever.
1976 pub paid_micros: i64,
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace1977 /// The same figures for each of the account's workspaces that has
1978 /// any, so staff can see what one member of an enterprise used.
1979 #[serde(default)]
1980 pub by_workspace: Vec<WorkspaceFigures>,
Two limits, real invoices, trust that grows by itself, sales signals1981 /// The last six months, oldest first, for trends.
1982 #[serde(default)]
1983 pub months: Vec<MonthFigures>,
1984}
1985
1986/// One month of an account's billing.
1987#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1988#[serde(rename_all = "camelCase")]
1989pub struct MonthFigures {
1990 /// YYYY-MM.
1991 pub month: String,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1992 /// Usage charged, after what paid for it first.
Two limits, real invoices, trust that grows by itself, sales signals1993 pub charged_micros: i64,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1994 /// What usage cost g1t: only what g1t paid for, never a workspace's own
1995 /// model provider.
Two limits, real invoices, trust that grows by itself, sales signals1996 pub cost_micros: i64,
1997 pub paid_micros: i64,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1998 /// The plan's monthly price, paid.
1999 #[serde(default)]
2000 pub plans_micros: i64,
2001 /// What g1t gave, at price: internal (comped) use, trials, the
2002 /// open-source pool, goodwill credits and what g1t covered. Not margin.
2003 #[serde(default)]
2004 pub given_micros: i64,
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace2005}
2006
2007/// One workspace's share of an [`AccountSummary`].
2008#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
2009#[serde(rename_all = "camelCase")]
2010pub struct WorkspaceFigures {
2011 pub workspace: String,
2012 pub charged_micros: i64,
2013 pub cost_micros: i64,
2014 pub paid_micros: i64,
Billing accounts, terms and enterprises; g1t is no longer free2015}
2016
2017/// `admin_account`: one account in full. Returns `Outcome<AccountDetail>`.
2018#[derive(Debug, Serialize, Deserialize)]
2019pub struct AdminAccountArgs {
2020 /// An account id, or a workspace slug.
2021 pub id: String,
2022}
2023
2024#[derive(Clone, Debug, Serialize, Deserialize)]
2025#[serde(rename_all = "camelCase")]
2026pub struct AccountDetail {
2027 pub summary: AccountSummary,
2028 /// Each workspace's limit, for an enterprise.
2029 pub workspaces: Vec<Limit>,
2030 pub ledger: Vec<LedgerEntry>,
2031 pub audit: Vec<AdminAction>,
2032}
2033
2034/// `admin_set_terms`. Returns `Outcome<BillingAccount>`.
2035#[derive(Debug, Serialize, Deserialize)]
2036pub struct AdminSetTermsArgs {
2037 pub id: String,
2038 pub terms: Terms,
2039 pub by: String,
2040}
2041
2042/// `admin_create_enterprise`. Returns `Outcome<BillingAccount>`.
2043#[derive(Debug, Serialize, Deserialize)]
2044pub struct AdminCreateEnterpriseArgs {
2045 pub name: String,
2046 pub workspaces: Vec<String>,
2047 pub by: String,
2048}
2049
2050/// `admin_attach`: moves a workspace onto an enterprise account, or back
2051/// onto its own with `account: None`. Returns `Outcome<BillingAccount>`.
2052#[derive(Debug, Serialize, Deserialize)]
2053pub struct AdminAttachArgs {
2054 pub workspace: String,
2055 pub account: Option<String>,
2056 pub by: String,
2057}
2058
2059/// `admin_credit`: money g1t gives a workspace, such as a refund or a
2060/// goodwill credit. Returns `Outcome<LedgerEntry>`.
2061#[derive(Debug, Serialize, Deserialize)]
2062pub struct AdminCreditArgs {
2063 pub workspace: String,
2064 pub amount_micros: i64,
2065 pub note: String,
2066 pub by: String,
2067}
2068
2069/// One change made in sudo.
2070#[derive(Clone, Debug, Serialize, Deserialize)]
2071#[serde(rename_all = "camelCase")]
2072pub struct AdminAction {
2073 pub id: String,
2074 pub account: String,
2075 pub action: String,
2076 pub detail: String,
2077 pub by: String,
2078 pub created_at: String,
2079}
2080
Paid features: a workspace turns on Deployments with a monthly plan2081/// What a feature's plan costs and includes.
2082#[derive(Clone, Debug, Serialize, Deserialize)]
2083#[serde(rename_all = "camelCase")]
2084pub struct Plan {
2085 pub feature: Feature,
2086 pub title: String,
2087 /// Charged every month while the plan is on, in cents.
2088 pub monthly_cents: u32,
2089 /// What the monthly price includes, one line each, for people to read.
2090 pub includes: Vec<String>,
2091 /// How usage past the allowance is charged, for people to read.
2092 pub overage: String,
2093}
2094
2095#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
2096#[serde(rename_all = "snake_case")]
2097pub enum SubscriptionStatus {
2098 /// Paid up; the feature works.
2099 Active,
2100 /// Paid up to the end of the period, and ends then.
2101 Canceling,
2102 /// The last payment failed; the feature is off until it is paid.
2103 PastDue,
2104 /// Ended.
2105 Canceled,
2106}
2107
2108impl SubscriptionStatus {
2109 /// Whether the feature works in this state.
2110 pub fn on(self) -> bool {
2111 matches!(self, SubscriptionStatus::Active | SubscriptionStatus::Canceling)
2112 }
2113}
2114
2115/// A workspace's plan for one feature.
2116#[derive(Clone, Debug, Serialize, Deserialize)]
2117#[serde(rename_all = "camelCase")]
2118pub struct Subscription {
2119 pub feature: Feature,
2120 pub status: SubscriptionStatus,
2121 /// RFC 3339: when the period paid for ends, and the plan renews or
2122 /// ends.
2123 pub period_end: Option<String>,
2124 /// Username of whoever turned it on.
2125 pub started_by: String,
2126 /// RFC 3339.
2127 pub started_at: String,
2128}
2129
2130/// A feature as a workspace sees it: what it costs, and its plan if it has
2131/// one.
2132#[derive(Clone, Debug, Serialize, Deserialize)]
2133#[serde(rename_all = "camelCase")]
2134pub struct FeatureState {
2135 pub plan: Plan,
2136 pub subscription: Option<Subscription>,
2137 /// Whether the feature works for the workspace now.
2138 pub on: bool,
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put2139 /// On without a plan: comped terms, or given by g1t. Nothing to pay
2140 /// and nothing to turn off.
2141 #[serde(default)]
2142 pub included: bool,
Paid features: a workspace turns on Deployments with a monthly plan2143}
2144
2145/// `features`: every paid feature and the workspace's plan for each.
2146/// Members only. Returns `Outcome<Vec<FeatureState>>`.
2147#[derive(Debug, Serialize, Deserialize)]
2148pub struct FeaturesArgs {
2149 pub workspace: String,
2150 pub viewer: Viewer,
2151}
2152
2153/// `subscribe`: starts the card page for a feature's monthly plan. Owners
2154/// only. Returns `Outcome<Checkout>`; the page's id comes back to
2155/// `return_url` as `session`, for `confirm_subscription`.
2156#[derive(Debug, Serialize, Deserialize)]
2157#[serde(rename_all = "camelCase")]
2158pub struct SubscribeArgs {
2159 pub actor: User,
2160 pub workspace: String,
2161 pub feature: Feature,
2162 pub return_url: String,
2163}
2164
2165/// `confirm_subscription`: turns the feature on once the processor says
2166/// the plan was paid for. Safe to call any number of times. Returns
2167/// `Outcome<FeatureState>`.
2168#[derive(Debug, Serialize, Deserialize)]
2169pub struct ConfirmSubscriptionArgs {
2170 pub workspace: String,
2171 pub viewer: Viewer,
2172 pub session: String,
2173}
2174
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look2175/// `close_workspace`: settles a workspace that is about to be deleted.
2176/// Owners only. Refused while it has an invoice that failed, while it
2177/// holds prepaid credit, or while it owes money it cannot be charged for
2178/// now; otherwise what it owes is invoiced to its card at once (no
2179/// minimum), its plan is cancelled at Stripe straight away, and its
2180/// account is marked closed, so the month-end close, autopay and limit
2181/// warnings pass it by. Its ledger, invoices and statements stay. With
2182/// `dry_run`, only says whether it could, changing nothing. Returns
2183/// `Outcome<bool>`.
2184#[derive(Debug, Serialize, Deserialize)]
2185#[serde(rename_all = "camelCase")]
2186pub struct CloseWorkspaceArgs {
2187 pub actor: User,
2188 pub workspace: String,
2189 #[serde(default)]
2190 pub dry_run: bool,
2191}
2192
Paid features: a workspace turns on Deployments with a monthly plan2193/// `cancel_subscription` (`resume` false) ends a plan at the end of the
2194/// period paid for; with `resume` true, takes that back. Owners only.
2195/// Returns `Outcome<FeatureState>`.
2196#[derive(Debug, Serialize, Deserialize)]
2197pub struct CancelSubscriptionArgs {
2198 pub actor: User,
2199 pub workspace: String,
2200 pub feature: Feature,
2201 #[serde(default)]
2202 pub resume: bool,
2203}
2204
2205/// `has_feature`: whether a feature works for a workspace now, asked by the
2206/// service that provides it before doing paid work. Returns
2207/// `Outcome<bool>`: a failure, with the reason to show, when it does not.
2208/// True everywhere when no card processor is configured.
2209#[derive(Debug, Serialize, Deserialize)]
2210pub struct HasFeatureArgs {
2211 pub workspace: String,
2212 pub feature: Feature,
2213}
2214
2215/// `charge_feature`: usage of a feature past its plan's allowance, charged
2216/// from the workspace's credit at cost plus the margin, whatever
2217/// `FREE_WHILE_BUILDING` says. Called by the service that provides it.
2218/// Charged once per `reference`. Returns `Outcome<bool>`: false if that
2219/// reference was charged before.
2220#[derive(Debug, Serialize, Deserialize)]
2221#[serde(rename_all = "camelCase")]
2222pub struct ChargeFeatureArgs {
2223 pub workspace: String,
2224 pub feature: Feature,
2225 /// What it cost g1t, in millionths of a dollar, before the margin.
2226 pub cost_micros: i64,
2227 pub description: String,
2228 /// `namespace/name`, when the usage was one repository's.
2229 pub repo: Option<String>,
2230 /// Unique to this charge, e.g. `deployments/acme/2026-10`.
2231 pub reference: String,
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put2232 /// For a build: how long it ran. The plan's included build time this
2233 /// month pays for what it can, and only the rest of `cost_micros` is
2234 /// charged.
2235 #[serde(default)]
2236 pub build_seconds: Option<u32>,
Paid features: a workspace turns on Deployments with a monthly plan2237}
2238
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily2239// ---------------------------------------------------------------------
2240// Costs and margin: what Cloudflare charges g1t against what g1t
2241// charges (billing's costs.rs, margin.rs and pricing.rs). Staff only.
2242// ---------------------------------------------------------------------
2243
2244/// `admin_costs`: the Costs & margin page. Returns `CostsReport`.
2245#[derive(Debug, Default, Serialize, Deserialize)]
2246pub struct AdminCostsArgs {
2247 /// How many days back, 7 to 90; 30 when absent.
2248 #[serde(default)]
2249 pub days: Option<u32>,
2250}
2251
2252/// One of g1t's products on one day.
2253#[derive(Clone, Debug, Default, Serialize, Deserialize)]
2254#[serde(rename_all = "camelCase")]
2255pub struct CostDay {
2256 pub day: String,
2257 pub bucket: String,
2258 /// What Cloudflare charged g1t.
2259 pub cf_cost_micros: i64,
2260 /// What g1t's meters recorded it cost, at the price book's cost.
2261 pub own_cost_micros: i64,
2262 /// What customers were charged for it at price, before included
2263 /// usage, trials and pools paid for some.
2264 pub value_micros: i64,
2265 /// Of that, what workspaces paid.
2266 pub cash_micros: i64,
2267}
2268
2269/// One product over the range.
2270#[derive(Clone, Debug, Default, Serialize, Deserialize)]
2271#[serde(rename_all = "camelCase")]
2272pub struct ProductMargin {
2273 pub bucket: String,
2274 pub title: String,
2275 /// The cost the margin is taken from: Cloudflare's bill, or g1t's own
2276 /// figure for what Cloudflare does not bill (models).
2277 pub cost_micros: i64,
2278 pub cf_cost_micros: i64,
2279 pub own_cost_micros: i64,
2280 pub value_micros: i64,
2281 pub margin_micros: i64,
2282 pub margin_percent: Option<f64>,
2283 /// `cloudflare` or `ledger`.
2284 pub cost_source: String,
2285 /// Running g1t itself, paid for by the plan.
2286 pub overhead: bool,
2287}
2288
2289/// All of g1t over the range: money in against every cost.
2290#[derive(Clone, Debug, Default, Serialize, Deserialize)]
2291#[serde(rename_all = "camelCase")]
2292pub struct OverallMargin {
2293 /// What workspaces paid for usage, and for the plan.
2294 pub usage_micros: i64,
2295 pub plans_micros: i64,
2296 pub cost_micros: i64,
2297 pub margin_micros: i64,
2298 pub margin_percent: Option<f64>,
2299}
2300
2301/// A count, cost or leak that does not add up.
2302#[derive(Clone, Debug, Serialize, Deserialize)]
2303#[serde(rename_all = "camelCase")]
2304pub struct CostDrift {
2305 pub bucket: String,
2306 pub title: String,
2307 /// `count` (units g1t counted against Cloudflare's), `cost` (the bill
2308 /// against the price book's cost of the same usage), or `leak`.
2309 pub kind: String,
2310 pub ours: f64,
2311 pub cloudflare: f64,
2312 pub delta_percent: Option<f64>,
2313 pub detail: String,
2314 pub found_at: String,
2315}
2316
2317/// A margin alert, open while its condition lasts.
2318#[derive(Clone, Debug, Serialize, Deserialize)]
2319#[serde(rename_all = "camelCase")]
2320pub struct MarginAlert {
2321 pub id: String,
2322 /// `margin`, `overall`, `leak`, `drift` or `workspace`.
2323 pub kind: String,
2324 /// The product, or the workspace.
2325 pub subject: String,
2326 pub detail: String,
2327 pub since: String,
2328 pub opened_at: String,
2329 pub emailed_at: Option<String>,
2330}
2331
2332/// A change to a price the reconciler measured.
2333#[derive(Clone, Debug, Serialize, Deserialize)]
2334#[serde(rename_all = "camelCase")]
2335pub struct PriceProposal {
2336 pub id: String,
2337 pub meter: String,
2338 pub title: String,
2339 pub unit: String,
2340 pub current_cost_micros: f64,
2341 pub proposed_cost_micros: f64,
2342 pub change_percent: f64,
2343 pub markup_percent: u32,
2344 pub reason: String,
2345 /// `keeper` or `reconciler`.
2346 pub source: String,
2347 /// Far off the current cost: look before approving.
2348 pub suspect: bool,
2349 /// `open`, `applied`, `approved`, `rejected` or `superseded`.
2350 pub status: String,
2351 pub created_at: String,
2352 pub decided_at: Option<String>,
2353 pub decided_by: Option<String>,
2354 pub note: Option<String>,
2355 /// When it takes or took effect, once approved or applied.
2356 pub effective_at: Option<String>,
2357}
2358
2359/// One version of one meter's price. Never changed once written.
2360#[derive(Clone, Debug, Serialize, Deserialize)]
2361#[serde(rename_all = "camelCase")]
2362pub struct PriceVersion {
2363 pub id: String,
2364 pub meter: String,
2365 pub version: u32,
2366 pub cost_micros: f64,
2367 pub markup_percent: u32,
2368 pub price_micros: f64,
2369 pub effective_at: String,
2370 pub reason: String,
2371 pub created_by: String,
2372 /// When the price book took it on; absent while it waits for its date.
2373 pub applied_at: Option<String>,
2374}
2375
2376/// What a workspace cost g1t over the range, Cloudflare's costs shared
2377/// out by g1t's own meters, against what it paid.
2378#[derive(Clone, Debug, Serialize, Deserialize)]
2379#[serde(rename_all = "camelCase")]
2380pub struct WorkspaceCost {
2381 pub workspace: String,
2382 pub cost_micros: i64,
2383 pub revenue_micros: i64,
2384 /// One of g1t's own (comped) workspaces.
2385 pub internal: bool,
2386}
2387
2388/// One Cloudflare meter over the range, and the product it is a cost of.
2389#[derive(Clone, Debug, Serialize, Deserialize)]
2390#[serde(rename_all = "camelCase")]
2391pub struct CostLineSummary {
2392 pub product: String,
2393 pub meter: String,
2394 pub raw_name: String,
2395 pub unit: String,
2396 pub source: String,
2397 pub quantity: f64,
2398 pub cost_micros: i64,
2399 /// Absent when no mapping claims it.
2400 pub bucket: Option<String>,
2401}
2402
2403/// A row of the mapping from Cloudflare's meters to g1t's products.
2404#[derive(Clone, Debug, Serialize, Deserialize)]
2405#[serde(rename_all = "camelCase")]
2406pub struct CostMapping {
2407 pub product: String,
2408 pub meter: String,
2409 pub bucket: String,
2410 pub price_meter: Option<String>,
2411 pub own_meter: Option<String>,
2412 pub scale_to_own: bool,
2413 pub drift_percent: f64,
2414 pub note: String,
2415 pub updated_at: String,
2416 pub updated_by: String,
2417}
2418
2419/// The guardrails on prices and the alerts.
2420#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
2421#[serde(rename_all = "camelCase")]
2422pub struct CostSettings {
2423 /// Apply small moves without staff.
2424 pub auto_apply: bool,
2425 /// The largest move applied without staff, either way, in percent.
2426 pub auto_apply_percent: f64,
2427 /// Days between telling customers of a rise and charging it.
2428 pub notice_days: u32,
2429 /// Below this margin, in percent, for `alert_days` days in a row, alert.
2430 pub margin_floor_percent: f64,
2431 pub alert_days: u32,
2432 /// Days with less cost than this say nothing about a margin.
2433 pub min_daily_cost_micros: i64,
2434 /// A workspace costing more than its revenue times this, over 30 days,
2435 /// and at least `anomaly_floor_micros`, is flagged.
2436 pub anomaly_factor: f64,
2437 pub anomaly_floor_micros: i64,
2438}
2439
2440impl Default for CostSettings {
2441 fn default() -> Self {
2442 CostSettings {
2443 auto_apply: true,
2444 auto_apply_percent: 25.0,
2445 notice_days: 14,
2446 margin_floor_percent: 10.0,
2447 alert_days: 3,
2448 min_daily_cost_micros: 100_000,
2449 anomaly_factor: 1.0,
2450 anomaly_floor_micros: 1_000_000,
2451 }
2452 }
2453}
2454
2455/// The Costs & margin page.
2456#[derive(Clone, Debug, Default, Serialize, Deserialize)]
2457#[serde(rename_all = "camelCase")]
2458pub struct CostsReport {
2459 /// A token to read Cloudflare's bill is set.
2460 pub configured: bool,
2461 /// When Cloudflare's bill was last read.
2462 pub fetched_at: Option<String>,
2463 /// The days shown, YYYY-MM-DD.
2464 pub since: String,
2465 pub until: String,
2466 pub days: Vec<CostDay>,
2467 pub products: Vec<ProductMargin>,
2468 pub overall: OverallMargin,
2469 pub drift: Vec<CostDrift>,
2470 pub alerts: Vec<MarginAlert>,
2471 pub proposals: Vec<PriceProposal>,
2472 pub versions: Vec<PriceVersion>,
2473 pub top_workspaces: Vec<WorkspaceCost>,
2474 pub lines: Vec<CostLineSummary>,
2475 pub mappings: Vec<CostMapping>,
2476 pub settings: CostSettings,
Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays2477 /// g1t's own spend against its two caps.
2478 #[serde(default)]
2479 pub caps: SpendCaps,
2480}
2481
2482/// What g1t itself pays for, against its caps (billing's `budget`): the
2483/// daily breaker on all of it, and each comped account's monthly budget.
2484/// At cost, never at price. What sudo's Costs page and its red bar show.
2485#[derive(Clone, Debug, Default, Serialize, Deserialize)]
2486#[serde(rename_all = "camelCase")]
2487pub struct SpendCaps {
2488 /// Today (UTC), YYYY-MM-DD, and this month, YYYY-MM.
2489 pub day: String,
2490 pub month: String,
2491 /// What g1t paid for itself today across every workspace: comped work,
2492 /// the trial and open-source pools, free workspaces' overruns, and
2493 /// anything charged without real money behind it.
2494 pub today_micros: i64,
2495 /// `PLATFORM_DAILY_SPEND_CAP_MICROS`. Zero: no breaker.
2496 pub daily_cap_micros: i64,
2497 /// The breaker is open: new hosted-model agent runs that g1t would pay
2498 /// for wait until tomorrow (UTC) or until staff lift it.
2499 pub tripped: bool,
2500 pub tripped_at: Option<String>,
2501 /// Staff lifted it for the rest of the day.
2502 pub lifted_by: Option<String>,
2503 pub lifted_at: Option<String>,
2504 pub lift_note: Option<String>,
2505 /// This month so far, by what paid: `comped`, `trial`, `oss`, `given`,
2506 /// `unpaid`.
2507 pub month_buckets: Vec<SpendBucket>,
2508 /// Each comped account's monthly budget.
2509 pub comped: Vec<CompedBudget>,
2510 /// Free workspaces' share of this month's reconciled costs (git,
2511 /// storage, platform), through yesterday.
2512 pub free_tier_micros: i64,
2513 /// `CLOUDFLARE_FIXED_MONTHLY_MICROS`: Cloudflare subscriptions, an estimate.
2514 pub fixed_monthly_micros: i64,
2515 /// Money in this month, through the last reconciled day.
2516 pub revenue_micros: i64,
2517}
2518
2519#[derive(Clone, Debug, Default, Serialize, Deserialize)]
2520#[serde(rename_all = "camelCase")]
2521pub struct SpendBucket {
2522 pub bucket: String,
2523 pub title: String,
2524 pub micros: i64,
2525}
2526
2527/// A comped account's monthly budget: what its work cost g1t this month.
2528#[derive(Clone, Debug, Default, Serialize, Deserialize)]
2529#[serde(rename_all = "camelCase")]
2530pub struct CompedBudget {
2531 pub account: String,
2532 pub name: String,
2533 pub used_micros: i64,
2534 /// Zero: no budget.
2535 pub ceiling_micros: i64,
2536 /// The ceiling is `COMPED_MONTHLY_CEILING_MICROS`, not the account's own.
2537 pub default_ceiling: bool,
2538 /// 50, 75, 90, 100, or 0.
2539 pub level: u32,
2540}
2541
2542/// `admin_spend_caps`: g1t's own spend against its caps. Returns `SpendCaps`.
2543#[derive(Debug, Default, Serialize, Deserialize)]
2544pub struct AdminSpendCapsArgs {}
2545
2546/// `admin_lift_breaker`: lets hosted-model runs start again for the rest
2547/// of today (UTC), with why. Recorded in the audit log. Returns
2548/// `Outcome<SpendCaps>`.
2549#[derive(Debug, Serialize, Deserialize)]
2550pub struct AdminLiftBreakerArgs {
2551 pub note: String,
2552 pub by: String,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily2553}
2554
2555/// `admin_cost_alerts`: the open margin alerts, for sudo's banner.
2556/// Returns `Vec<MarginAlert>`.
2557#[derive(Debug, Default, Serialize, Deserialize)]
2558pub struct AdminCostAlertsArgs {}
2559
2560/// `admin_decide_proposal`: approve or reject a price proposal. An
2561/// approved rise takes effect after the notice period. Returns
2562/// `Outcome<PriceProposal>`.
2563#[derive(Debug, Serialize, Deserialize)]
2564pub struct AdminDecideProposalArgs {
2565 pub id: String,
2566 /// `approve` or `reject`.
2567 pub decision: String,
2568 #[serde(default)]
2569 pub note: String,
2570 pub by: String,
2571}
2572
2573/// `admin_set_cost_settings`. Returns `Outcome<CostSettings>`.
2574#[derive(Debug, Serialize, Deserialize)]
2575pub struct AdminSetCostSettingsArgs {
2576 pub settings: CostSettings,
2577 pub by: String,
2578}
2579
2580/// `admin_set_cost_mapping`: adds, changes or (with `remove`) removes a
2581/// mapping row. Returns `Outcome<CostMapping>`.
2582#[derive(Debug, Serialize, Deserialize)]
2583pub struct AdminSetCostMappingArgs {
2584 pub product: String,
2585 pub meter: String,
2586 #[serde(default)]
2587 pub bucket: String,
2588 #[serde(default)]
2589 pub price_meter: Option<String>,
2590 #[serde(default)]
2591 pub own_meter: Option<String>,
2592 #[serde(default)]
2593 pub scale_to_own: bool,
2594 #[serde(default)]
2595 pub drift_percent: Option<f64>,
2596 #[serde(default)]
2597 pub note: String,
2598 #[serde(default)]
2599 pub remove: bool,
2600 pub by: String,
2601}
2602
2603/// `admin_run_costs`: reads Cloudflare's bill and reconciles now, as the
2604/// daily run does. Returns `Outcome<CostsRun>`.
2605#[derive(Debug, Default, Serialize, Deserialize)]
2606pub struct AdminRunCostsArgs {
2607 #[serde(default)]
2608 pub by: String,
2609}
2610
2611#[derive(Clone, Debug, Default, Serialize, Deserialize)]
2612#[serde(rename_all = "camelCase")]
2613pub struct CostsRun {
2614 pub lines: u32,
2615 pub days: u32,
2616 pub proposals: u32,
2617 pub alerts: u32,
2618 /// What could not be read, in words.
2619 pub problems: Vec<String>,
2620}
2621
Models per workspace: several providers, routed by kind of work2622#[cfg(test)]
2623mod tests {
2624 use super::*;
2625
2626 #[test]
Prices are what g1t pays plus 20%, from the first second2627 fn an_account_carries_no_run_fee() {
2628 let account = Account {
2629 workspace: "acme".into(),
2630 balance_micros: 0,
2631 status: Status { enabled: true, live: false, free: false },
2632 margin_percent: 20,
2633 card: None,
2634 };
2635 let json = serde_json::to_value(account).unwrap();
2636 let mut keys: Vec<&str> = json.as_object().unwrap().keys().map(String::as_str).collect();
2637 keys.sort_unstable();
2638 assert_eq!(keys, ["balanceMicros", "card", "marginPercent", "status", "workspace"]);
2639 }
2640
2641 #[test]
2642 fn a_price_change_says_when_the_markup_moved() {
2643 let change = PriceChange {
2644 meter: "sandbox_second".into(),
2645 old_cost_micros: 21.0,
2646 new_cost_micros: 21.0,
2647 markup_percent: 20,
2648 old_markup_percent: Some(138),
2649 reason: "Sandbox time is now charged at cost plus 20% from the first second".into(),
2650 created_at: "2026-10-05T00:00:00Z".into(),
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily2651 effective_at: None,
Prices are what g1t pays plus 20%, from the first second2652 };
2653 assert_eq!(serde_json::to_value(&change).unwrap()["oldMarkupPercent"], 138);
2654 let cost_only = PriceChange { old_markup_percent: None, ..change };
2655 assert!(serde_json::to_value(&cost_only).unwrap().get("oldMarkupPercent").is_none());
2656 }
2657
2658 #[test]
Paid features: a workspace turns on Deployments with a monthly plan2659 fn features_are_named_as_the_site_sends_them() {
2660 assert_eq!(
2661 serde_json::to_value(Feature::Deployments).unwrap(),
2662 serde_json::json!("deployments")
2663 );
2664 assert_eq!(Feature::parse("deployments"), Some(Feature::Deployments));
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look2665 assert_eq!(serde_json::to_value(Feature::Plan).unwrap(), serde_json::json!("plan"));
2666 assert_eq!(Feature::parse("plan"), Some(Feature::Plan));
2667 // Older readers named the plan Team.
2668 assert_eq!(Feature::parse("team"), Some(Feature::Plan));
2669 assert_eq!(serde_json::from_value::<Feature>(serde_json::json!("team")).unwrap(), Feature::Plan);
2670 assert_eq!(Feature::ALL, [Feature::Plan]);
Paid features: a workspace turns on Deployments with a monthly plan2671 assert!(SubscriptionStatus::Canceling.on());
2672 assert!(!SubscriptionStatus::PastDue.on());
2673 }
2674
2675 #[test]
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look2676 fn a_reservation_is_asked_for_and_answered_in_camel_case() {
2677 let asked: ReserveArgs = serde_json::from_value(serde_json::json!({
2678 "workspace": "acme",
2679 "repo": { "namespace": "acme", "name": "web" },
2680 "public": true,
2681 "kind": "check",
2682 "estimateMicros": 2_000_000,
2683 }))
2684 .unwrap();
2685 assert_eq!(asked.kind, ComputeKind::Check);
2686 assert!(asked.kind.open_source_pool());
2687 assert!(!ComputeKind::Agent.open_source_pool());
2688 // Rust callers that write snake_case are read too.
2689 let snake: ReserveArgs = serde_json::from_value(serde_json::json!({
2690 "workspace": "acme",
2691 "repo": { "namespace": "acme", "name": "web" },
2692 "public": false,
2693 "kind": "agent",
2694 "estimate_micros": 1,
2695 }))
2696 .unwrap();
2697 assert_eq!(snake.estimate_micros, 1);
2698 let answer = Reservation { id: "rsv_1".into(), paid_by: PaidBy::OnDemand, held_micros: 5, expires_at: String::new() };
2699 assert_eq!(serde_json::to_value(&answer).unwrap()["paidBy"], "on_demand");
2700 assert_eq!(serde_json::to_value(PlanKind::Internal).unwrap(), "internal");
2701 assert!(!PlanKind::Free.on_demand() && PlanKind::Enterprise.on_demand());
2702 }
2703
2704 #[test]
2705 fn a_refusal_carries_its_own_code() {
2706 let refused: crate::Outcome<Reservation> =
2707 crate::Outcome::fail(crate::FailureCode::OssPoolEmpty, "The open-source pool is spent.");
2708 let json = serde_json::to_value(&refused).unwrap();
2709 assert_eq!(json["error"]["code"], "oss_pool_empty");
2710 assert_eq!(crate::FailureCode::NotPaid.http_status(), 402);
2711 assert_eq!(crate::FailureCode::Paused.http_status(), 409);
2712 }
2713
2714 #[test]
Models per workspace: several providers, routed by kind of work2715 fn who_pays_is_read_as_the_runner_sends_it() {
2716 let run: StartRunArgs = serde_json::from_value(serde_json::json!({
2717 "workspace": "acme",
2718 "repo": { "namespace": "acme", "name": "web" },
2719 "number": 7,
2720 "task": "implement",
2721 "model": "Claude Sonnet 5.5",
2722 "billedTo": "workspace",
2723 }))
2724 .unwrap();
2725 assert_eq!(run.billed_to, "workspace");
2726 }
2727}