flagon-io/g1t

public

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t/crates/contracts/src/billing.rs

1,322 lines44,336 bytesCodeBlame
1//! 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,
30 /// 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,
35}
36
37/// `trial`: the free allowance on g1t's hosted models for a workspace that
38/// is not otherwise open to them, so people can try g1t's agents without a
39/// key of their own. Each workspace gets a few dollars of model cost, out
40/// of one pool, until an end date. Returns `Trial`.
41#[derive(Debug, Serialize, Deserialize)]
42#[serde(rename_all = "camelCase")]
43pub struct TrialArgs {
44 pub workspace: String,
45 /// Workspaces open to hosted models anyway, whose use is not counted
46 /// against the pool.
47 #[serde(default)]
48 pub exempt: Vec<String>,
49}
50
51#[derive(Clone, Debug, Serialize, Deserialize)]
52#[serde(rename_all = "camelCase")]
53pub struct Trial {
54 /// Whether its agents may use g1t's hosted models on the allowance now.
55 pub open: bool,
56 /// What its runs on g1t's models have cost, in millionths of a dollar.
57 pub used_micros: i64,
58 pub limit_micros: i64,
59 /// RFC 3339; when the allowance ends for everyone.
60 pub ends_at: Option<String>,
61 /// Why it is closed: `off` (no allowance), `ended`, `used` (this
62 /// workspace's is spent) or `pool` (everyone's is).
63 pub reason: Option<String>,
64}
65
66/// A workspace's standing.
67#[derive(Clone, Debug, Serialize, Deserialize)]
68#[serde(rename_all = "camelCase")]
69pub struct Account {
70 pub workspace: String,
71 /// Credit left, in millionths of a dollar. Can dip below zero by the
72 /// cost of the runs that were under way when it ran out.
73 pub balance_micros: i64,
74 pub status: Status,
75 /// What is added to a run's cost, in percent.
76 pub margin_percent: u32,
77 /// What a run on the workspace's own model provider is charged: g1t's
78 /// sandbox and orchestration, with the model paid for elsewhere.
79 pub orchestration_fee_micros: i64,
80 /// The card g1t charges as the workspace nears its limit and when a
81 /// month closes, if one is on file.
82 #[serde(default)]
83 pub card: Option<Card>,
84}
85
86/// A saved card, as far as it is safe to show.
87#[derive(Clone, Debug, Serialize, Deserialize)]
88#[serde(rename_all = "camelCase")]
89pub struct Card {
90 /// `visa`, `mastercard`, ...
91 pub brand: String,
92 pub last4: String,
93 pub exp_month: u32,
94 pub exp_year: u32,
95}
96
97/// `billing_portal`: Stripe's hosted billing page for the workspace, where
98/// an owner adds or replaces the card, sees invoices and receipts, and sets
99/// the billing email and address. g1t never handles card numbers. Owners
100/// only. Returns `Outcome<Checkout>` (its `url`); Stripe sends them back
101/// to `return_url`.
102#[derive(Debug, Serialize, Deserialize)]
103pub struct BillingPortalArgs {
104 pub actor: User,
105 pub workspace: String,
106 pub return_url: String,
107}
108
109/// `admin_billing_link`: for staff to send a customer: their Stripe billing
110/// page. Returns `Outcome<BillingLink>`.
111#[derive(Debug, Serialize, Deserialize)]
112pub struct AdminBillingLinkArgs {
113 pub workspace: String,
114 pub by: String,
115}
116
117#[derive(Clone, Debug, Serialize, Deserialize)]
118#[serde(rename_all = "camelCase")]
119pub struct BillingLink {
120 /// A one-time session on Stripe's billing page, signed in already.
121 pub portal_url: String,
122 /// The billing page's sign-in page, which does not expire: the
123 /// customer signs in with the email Stripe has for them.
124 pub login_url: Option<String>,
125 pub customer_email: Option<String>,
126 pub expires_note: String,
127}
128
129#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
130#[serde(rename_all = "snake_case")]
131pub enum EntryKind {
132 /// Credit bought with a card.
133 TopUp,
134 /// An agent's run, or a paid feature's usage past its allowance.
135 Usage,
136}
137
138/// One line of a workspace's statement.
139#[derive(Clone, Debug, Serialize, Deserialize)]
140#[serde(rename_all = "camelCase")]
141pub struct LedgerEntry {
142 pub id: String,
143 pub kind: EntryKind,
144 /// Positive for credit added, negative for usage.
145 pub amount_micros: i64,
146 pub description: String,
147 /// For usage: the repository and pull request the agent worked on.
148 pub repo: Option<String>,
149 pub number: Option<u32>,
150 /// For usage: `implement`, `review` or `update`.
151 pub task: Option<String>,
152 /// For usage: the model, by its public name.
153 pub model: Option<String>,
154 /// For usage: `g1t` when g1t paid the model provider, `workspace` when
155 /// the workspace's own account did and only orchestration is charged.
156 #[serde(default = "g1t")]
157 pub billed_to: String,
158 /// For a top-up: the username of whoever paid.
159 pub created_by: Option<String>,
160 /// RFC 3339.
161 pub created_at: String,
162 /// The workspace the line belongs to, which tells an enterprise's
163 /// lines apart.
164 #[serde(default, skip_serializing_if = "Option::is_none")]
165 pub workspace: Option<String>,
166}
167
168fn g1t() -> String {
169 "g1t".to_owned()
170}
171
172/// `account` (`Outcome<Account>`) and `ledger` (`Outcome<Vec<LedgerEntry>>`,
173/// newest first). Members of the workspace only.
174#[derive(Debug, Serialize, Deserialize)]
175pub struct AccountArgs {
176 pub workspace: String,
177 pub viewer: Viewer,
178}
179
180/// `checkout`: starts a card payment for credit. Owners of the workspace
181/// only. Returns `Outcome<Checkout>`.
182#[derive(Debug, Serialize, Deserialize)]
183#[serde(rename_all = "camelCase")]
184pub struct CheckoutArgs {
185 pub actor: User,
186 pub workspace: String,
187 /// How much credit to buy, in cents.
188 pub amount_cents: u32,
189 /// Where the payment page sends the person afterwards. The payment's
190 /// id is appended as `session`.
191 pub return_url: String,
192}
193
194#[derive(Debug, Serialize, Deserialize)]
195pub struct Checkout {
196 /// The payment page to send the person to.
197 pub url: String,
198}
199
200/// `confirm`: credits a payment once the provider says it was made. Safe
201/// to call any number of times. Returns `Outcome<Account>`.
202#[derive(Debug, Serialize, Deserialize)]
203pub struct ConfirmArgs {
204 pub workspace: String,
205 pub viewer: Viewer,
206 /// The payment's id, as returned to `return_url`.
207 pub session: String,
208}
209
210/// `can_start`: whether a workspace may start an agent now, asked before
211/// anything is opened for it. Returns `Outcome<bool>`: a failure, with the
212/// reason to show, when it has no credit.
213#[derive(Debug, Serialize, Deserialize)]
214pub struct CanStartArgs {
215 pub workspace: String,
216}
217
218/// `start_run`: asks whether a workspace may start an agent, and opens the
219/// run it will be charged for. Called by the runner service. Returns
220/// `Outcome<Option<RunTicket>>`: no ticket when billing is off, a failure
221/// when the workspace has no credit.
222#[derive(Debug, Serialize, Deserialize)]
223pub struct StartRunArgs {
224 pub workspace: String,
225 pub repo: RepoPath,
226 pub number: u32,
227 /// `implement`, `review` or `update`.
228 pub task: String,
229 /// The model, by its public name.
230 pub model: String,
231 /// `workspace` when the run uses the workspace's own model provider.
232 /// The runner, which is TypeScript, sends it as `billedTo`.
233 #[serde(default = "g1t", alias = "billedTo")]
234 pub billed_to: String,
235 /// The model session's id, when its requests go through g1t's AI
236 /// Gateway: settling charges the run what the gateway priced them at.
237 #[serde(default)]
238 pub session: Option<String>,
239}
240
241#[derive(Clone, Debug, Serialize, Deserialize)]
242#[serde(rename_all = "camelCase")]
243pub struct RunTicket {
244 pub run_id: String,
245 /// Lets the sandbox, and nothing else, report what this run cost.
246 pub token: String,
247}
248
249/// `finish_run`: what a run cost, as its sandbox reports it. Charged once.
250/// Returns `Outcome<bool>`.
251#[derive(Debug, Serialize, Deserialize)]
252#[serde(rename_all = "camelCase")]
253pub struct FinishRunArgs {
254 pub run_id: String,
255 pub token: String,
256 /// What the model provider charged, in US dollars.
257 pub cost_usd: f64,
258 #[serde(default)]
259 pub turns: u32,
260}
261
262
263/// `usage`: what a workspace's agents cost over a period, broken down.
264/// Members only. Returns `Outcome<Usage>`.
265#[derive(Debug, Serialize, Deserialize)]
266pub struct UsageArgs {
267 pub workspace: String,
268 pub viewer: Viewer,
269 /// RFC 3339: the start of the period. The period runs to now.
270 pub since: String,
271}
272
273/// One slice of usage: what it was for, what it cost, how many runs.
274#[derive(Clone, Debug, Serialize, Deserialize)]
275#[serde(rename_all = "camelCase")]
276pub struct UsageSlice {
277 pub key: String,
278 pub micros: i64,
279 pub runs: u32,
280}
281
282/// What a workspace's agents cost over a period.
283#[derive(Clone, Debug, Serialize, Deserialize)]
284#[serde(rename_all = "camelCase")]
285pub struct Usage {
286 pub since: String,
287 /// Charged, including g1t's margin.
288 pub spent_micros: i64,
289 /// What g1t's model provider charged, before the margin.
290 pub cost_micros: i64,
291 /// What runs on the workspace's own provider cost there, as the harness
292 /// estimated it. Not charged by g1t.
293 pub provider_micros: i64,
294 /// What the runs used, at cost: g1t's models and the workspace's own
295 /// provider together, whatever was charged for them.
296 pub used_micros: i64,
297 /// g1t charges nothing for now. The slices then measure usage at cost,
298 /// since every charge is zero.
299 pub free: bool,
300 pub runs: u32,
301 /// Spend per day (`YYYY-MM-DD`) and task, as `day/task` keys.
302 pub by_day: Vec<UsageSlice>,
303 /// Per task: implement, review, revise, update, plan.
304 pub by_task: Vec<UsageSlice>,
305 /// Per repository, `namespace/name`.
306 pub by_repo: Vec<UsageSlice>,
307 /// The pull requests that cost most, as `namespace/name#number`.
308 pub by_pull: Vec<UsageSlice>,
309 /// Per model, by its public name.
310 pub by_model: Vec<UsageSlice>,
311 /// Credit bought in the period.
312 pub added_micros: i64,
313}
314
315/// A paid feature a workspace turns on with a monthly plan, the way
316/// Cloudflare's Workers for Platforms or Vercel's Pro are bought. Never
317/// free: `FREE_WHILE_BUILDING` and the free model allowance do not cover
318/// it.
319#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
320#[serde(rename_all = "snake_case")]
321pub enum Feature {
322 /// Previews per pull request and production on g1t.page.
323 Deployments,
324}
325
326impl Feature {
327 pub const ALL: [Feature; 1] = [Feature::Deployments];
328
329 pub fn as_str(self) -> &'static str {
330 match self {
331 Feature::Deployments => "deployments",
332 }
333 }
334
335 pub fn parse(name: &str) -> Option<Feature> {
336 Feature::ALL.into_iter().find(|feature| feature.as_str() == name)
337 }
338
339 pub fn title(self) -> &'static str {
340 match self {
341 Feature::Deployments => "Deployments",
342 }
343 }
344}
345
346/// What the Deployments plan includes each month; usage past it is charged
347/// at cost plus the margin. The billing service describes the plan with
348/// these and the deployments service meters against them.
349pub mod deployments_allowance {
350 /// Apps deployed at once: production and previews together.
351 pub const APPS: u32 = 10;
352 pub const REQUESTS: u64 = 1_000_000;
353 pub const CPU_MS: u64 = 3_000_000;
354 /// What Cloudflare charges g1t past that, in millionths of a dollar.
355 pub const MICROS_PER_APP_MONTH: i64 = 20_000;
356 pub const MICROS_PER_MILLION_REQUESTS: i64 = 300_000;
357 pub const MICROS_PER_MILLION_CPU_MS: i64 = 20_000;
358 /// What one second of a build's sandbox costs g1t (Cloudflare
359 /// Containers, standard-1: half a vCPU, 4 GiB, 8 GB disk), rounded up.
360 /// Builds are not in the allowance: each is charged at this plus the
361 /// margin.
362 pub const MICROS_PER_BUILD_SECOND: i64 = 21;
363 /// Custom domains across the workspace (Cloudflare for SaaS custom
364 /// hostnames); each one past these is charged by the month.
365 pub const CUSTOM_DOMAINS: u32 = 3;
366 /// What one custom hostname costs g1t a month: $0.10.
367 pub const MICROS_PER_DOMAIN_MONTH: i64 = 100_000;
368}
369
370/// Sandbox time: every sandbox g1t starts for a workspace (agents,
371/// reviews, checks, the merge queue, workflow jobs) is metered by the
372/// second. Deploy builds are charged by the Deployments plan instead.
373pub mod sandbox_allowance {
374 /// Free each calendar month (UTC): 500 minutes.
375 pub const FREE_SECONDS: i64 = 30_000;
376 /// What one second costs g1t (Cloudflare Containers, standard-1),
377 /// rounded up. Recorded with every entry.
378 pub const COST_MICROS_PER_SECOND: i64 = super::deployments_allowance::MICROS_PER_BUILD_SECOND;
379 /// What one second past the free minutes is charged: $0.003 a minute.
380 pub const MICROS_PER_SECOND: i64 = 50;
381}
382
383/// `record_sandbox`: how long one sandbox ran for a workspace, reported by
384/// the runner when it stops. Recorded once per `reference`, with what it
385/// cost g1t; seconds past the month's free minutes are charged at
386/// `sandbox_allowance::MICROS_PER_SECOND`, unless `FREE_WHILE_BUILDING`.
387/// Returns `Outcome<bool>`: false if that reference was recorded before.
388#[derive(Debug, Serialize, Deserialize)]
389#[serde(rename_all = "camelCase")]
390pub struct RecordSandboxArgs {
391 pub workspace: String,
392 pub seconds: u32,
393 /// What ran, e.g. `Checks on acme/api#12`.
394 pub description: String,
395 /// `namespace/name`.
396 pub repo: Option<String>,
397 /// Unique to the run.
398 pub reference: String,
399}
400
401/// How much a workspace has earned g1t's trust with money, which sets how
402/// far its unpaid usage can go before its work stops.
403#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
404#[serde(rename_all = "snake_case")]
405pub enum Trust {
406 /// No live payment yet: only a little past the free allowances.
407 New,
408 /// Has paid g1t real money: the ceiling grows with what it has paid.
409 Paid,
410 /// Has paid steadily for months, with nothing disputed or declined:
411 /// the ceiling follows its monthly spend, up to $10,000, by itself.
412 Established,
413 /// A ceiling g1t set by hand, after talking to the workspace.
414 Reviewed,
415 /// g1t's own workspaces: no ceiling.
416 Internal,
417}
418
419#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
420#[serde(rename_all = "snake_case")]
421pub enum LimitState {
422 Ok,
423 /// Past 80% of the ceiling.
424 Warning,
425 /// At or past it: no new sandboxes, builds or app requests.
426 Stopped,
427}
428
429/// How far a workspace's unpaid usage has gone this month, and where its
430/// work stops: like Fly's or Cloudflare's limits for new accounts, so no
431/// one runs up costs g1t cannot collect. Usage counts at what it cost g1t
432/// or what it is charged, whichever is more, so it counts while g1t is
433/// free too.
434#[derive(Clone, Debug, Serialize, Deserialize)]
435#[serde(rename_all = "camelCase")]
436pub struct Limit {
437 pub workspace: String,
438 /// The account that pays, whose usage and payments the limit counts:
439 /// the workspace's own, or its enterprise's.
440 #[serde(default)]
441 pub account: String,
442 #[serde(default)]
443 pub account_name: String,
444 pub trust: Trust,
445 /// Usage this month (UTC) less what was paid this month.
446 pub exposure_micros: i64,
447 /// Where work stops: the lower of g1t's ceiling and the owner's own
448 /// spend limit. None for g1t's own workspaces.
449 pub ceiling_micros: Option<i64>,
450 /// The ceiling g1t sets from `trust`.
451 pub trust_ceiling_micros: Option<i64>,
452 /// The owner's own monthly limit, if they set one.
453 pub spend_limit_micros: Option<i64>,
454 pub state: LimitState,
455 /// What to tell people when work is stopped or close to it.
456 pub message: Option<String>,
457 /// Charged this month, which the spend limit is measured against.
458 #[serde(default)]
459 pub spent_micros: i64,
460 /// True while the owners have not chosen a spend limit of their own, so
461 /// the automatic one applies: $200, or twice last month's spend.
462 #[serde(default)]
463 pub default_spend_limit: bool,
464 /// The most the owners may set their own limit to: g1t's ceiling. To
465 /// go past it, they contact g1t.
466 #[serde(default)]
467 pub available_micros: Option<i64>,
468 /// How the ceiling grows from here, in a sentence.
469 #[serde(default)]
470 pub growth: Option<String>,
471}
472
473/// `limit`: a workspace's limit, for its members. Returns `Outcome<Limit>`.
474#[derive(Debug, Serialize, Deserialize)]
475pub struct LimitArgs {
476 pub workspace: String,
477 pub viewer: Viewer,
478}
479
480/// `check_limit`: the same, for the services that enforce it. Returns
481/// `Outcome<Limit>`.
482#[derive(Debug, Serialize, Deserialize)]
483pub struct CheckLimitArgs {
484 pub workspace: String,
485}
486
487/// `note_pending`: usage this month that will be charged later, such as
488/// app traffic past a plan, so the workspace's limit counts it now. Each
489/// report replaces the last for that workspace, source and month. Called
490/// by the service that meters it. Returns `bool`.
491#[derive(Debug, Serialize, Deserialize)]
492#[serde(rename_all = "camelCase")]
493pub struct NotePendingArgs {
494 pub workspace: String,
495 /// `deployments`.
496 pub source: String,
497 /// What it cost g1t so far this month, before the margin.
498 pub cost_micros: i64,
499}
500
501/// `set_spend_limit`: the owner's own monthly ceiling, under g1t's; None
502/// removes it. Owners only. Returns `Outcome<Limit>`.
503#[derive(Debug, Serialize, Deserialize)]
504#[serde(rename_all = "camelCase")]
505pub struct SetSpendLimitArgs {
506 pub actor: User,
507 pub workspace: String,
508 /// A monthly limit, at most what is available; None goes back to the
509 /// default.
510 pub spend_limit_micros: Option<i64>,
511 /// Use everything available, with no limit of their own.
512 #[serde(default)]
513 pub use_full_limit: bool,
514}
515
516/// One metered unit: what it costs g1t, and what it is sold at. The price
517/// is always `cost × (100 + markup) / 100`, so it follows the cost.
518#[derive(Clone, Debug, Serialize, Deserialize)]
519#[serde(rename_all = "camelCase")]
520pub struct Price {
521 /// `sandbox_second`, `build_second`, `app_requests`, `app_cpu`, `app_month`.
522 pub meter: String,
523 pub title: String,
524 pub unit: String,
525 /// Millionths of a dollar per unit; may have a fraction.
526 pub cost_micros: f64,
527 pub markup_percent: u32,
528 pub price_micros: f64,
529 /// `list`: Cloudflare's published price. `cloudflare`: what Cloudflare
530 /// actually billed g1t, measured.
531 pub source: String,
532 /// When it was last checked against Cloudflare's bill.
533 pub checked_at: Option<String>,
534 pub updated_at: String,
535}
536
537impl Price {
538 pub fn price_for(cost_micros: f64, markup_percent: u32) -> f64 {
539 cost_micros * f64::from(100 + markup_percent) / 100.0
540 }
541}
542
543/// A cost that moved.
544#[derive(Clone, Debug, Serialize, Deserialize)]
545#[serde(rename_all = "camelCase")]
546pub struct PriceChange {
547 pub meter: String,
548 pub old_cost_micros: f64,
549 pub new_cost_micros: f64,
550 pub markup_percent: u32,
551 pub reason: String,
552 pub created_at: String,
553}
554
555/// `prices`: every metered price and the recent changes. Public. Returns
556/// `PriceBook`.
557#[derive(Clone, Debug, Serialize, Deserialize)]
558#[serde(rename_all = "camelCase")]
559pub struct PriceBook {
560 pub prices: Vec<Price>,
561 pub changes: Vec<PriceChange>,
562 /// The margin on model usage, which is charged at what AI Gateway
563 /// priced each request at.
564 pub model_margin_percent: u32,
565}
566
567/// Who pays: a billing account. Every workspace has one; by default its
568/// own. An enterprise account pays for several workspaces at once, as
569/// GitHub Enterprise does: one bill, one limit, one set of terms.
570#[derive(Clone, Debug, Serialize, Deserialize)]
571#[serde(rename_all = "camelCase")]
572pub struct BillingAccount {
573 /// `ws_<slug>` for a workspace's own account; `ent_…` for an enterprise.
574 pub id: String,
575 pub kind: AccountKind,
576 pub name: String,
577 pub terms: Terms,
578 /// The workspaces it pays for.
579 pub workspaces: Vec<String>,
580 /// Where an enterprise's invoices go.
581 #[serde(default)]
582 pub billing_email: Option<String>,
583 /// An enterprise's invoices, newest first. Empty for a workspace's own.
584 #[serde(default)]
585 pub invoices: Vec<EnterpriseInvoice>,
586 pub created_at: String,
587}
588
589#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
590#[serde(rename_all = "snake_case")]
591pub enum AccountKind {
592 Workspace,
593 Enterprise,
594}
595
596/// How an account is charged. Standard unless g1t set otherwise in sudo.
597#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
598#[serde(rename_all = "camelCase")]
599pub struct Terms {
600 pub kind: TermsKind,
601 /// Off every usage charge, in percent. Custom terms only.
602 #[serde(default)]
603 pub discount_percent: u32,
604 /// A ceiling on unpaid usage that replaces the one trust would give.
605 #[serde(default)]
606 pub ceiling_micros: Option<i64>,
607 /// Why, for whoever looks next.
608 #[serde(default)]
609 pub note: String,
610 /// When the terms end and the account goes back to standard.
611 #[serde(default)]
612 pub until: Option<String>,
613 #[serde(default)]
614 pub set_by: Option<String>,
615 #[serde(default)]
616 pub set_at: Option<String>,
617}
618
619impl Terms {
620 pub fn standard() -> Self {
621 Terms {
622 kind: TermsKind::Standard,
623 discount_percent: 0,
624 ceiling_micros: None,
625 note: String::new(),
626 until: None,
627 set_by: None,
628 set_at: None,
629 }
630 }
631
632 /// What a charge becomes under these terms.
633 pub fn apply(&self, charge_micros: i64) -> i64 {
634 match self.kind {
635 TermsKind::Comped => 0,
636 TermsKind::Custom => charge_micros * i64::from(100 - self.discount_percent.min(100)) / 100,
637 TermsKind::Standard => charge_micros,
638 }
639 }
640}
641
642#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
643#[serde(rename_all = "snake_case")]
644pub enum TermsKind {
645 /// Prices as published, limits by trust.
646 Standard,
647 /// Nothing charged; usage still recorded with its cost. Paid features
648 /// are on without a plan. For g1t's own workspaces, partners, and the
649 /// like.
650 Comped,
651 /// A discount, a ceiling, or both.
652 Custom,
653}
654
655/// `stripe_webhook`: an event from Stripe, as the API received it: the raw
656/// body and its `Stripe-Signature` header. Billing checks the signature
657/// against the secret of the endpoint it registered, and handles each
658/// event once. Returns `Outcome<bool>`: false for one already handled.
659#[derive(Debug, Serialize, Deserialize)]
660pub struct StripeWebhookArgs {
661 pub payload: String,
662 pub signature: String,
663}
664
665/// `admin_stripe`: where billing stands with Stripe. Staff only. Returns
666/// `StripeStatus`. With `setup: true`, registers (or replaces) the webhook
667/// endpoint for the current mode first.
668#[derive(Debug, Default, Serialize, Deserialize)]
669pub struct AdminStripeArgs {
670 #[serde(default)]
671 pub setup: bool,
672 #[serde(default)]
673 pub by: Option<String>,
674}
675
676#[derive(Clone, Debug, Serialize, Deserialize)]
677#[serde(rename_all = "camelCase")]
678pub struct StripeStatus {
679 /// `test` or `live`, from the key; `off` without one.
680 pub mode: String,
681 pub webhook: Option<StripeWebhook>,
682 /// The latest events handled, newest first.
683 pub recent_events: Vec<StripeEventSummary>,
684 /// What went wrong setting up, if it did.
685 pub error: Option<String>,
686}
687
688#[derive(Clone, Debug, Serialize, Deserialize)]
689#[serde(rename_all = "camelCase")]
690pub struct StripeWebhook {
691 pub url: String,
692 pub endpoint_id: String,
693 pub events: Vec<String>,
694 pub created_by: String,
695 pub created_at: String,
696}
697
698#[derive(Clone, Debug, Serialize, Deserialize)]
699#[serde(rename_all = "camelCase")]
700pub struct StripeEventSummary {
701 pub id: String,
702 pub kind: String,
703 pub outcome: String,
704 pub received_at: String,
705}
706
707/// `admin_enterprise_billing`: where an enterprise's invoices go. Creates
708/// or updates its Stripe customer. Returns `Outcome<BillingAccount>`.
709#[derive(Debug, Serialize, Deserialize)]
710pub struct AdminEnterpriseBillingArgs {
711 pub id: String,
712 pub email: String,
713 pub by: String,
714}
715
716/// `admin_invoice_enterprise`: sends an enterprise its invoice now, for
717/// what its workspaces owe, rather than waiting for the month to close.
718/// Returns `Outcome<EnterpriseInvoice>`.
719#[derive(Debug, Serialize, Deserialize)]
720pub struct AdminInvoiceEnterpriseArgs {
721 pub id: String,
722 pub by: String,
723}
724
725/// An enterprise's invoice: one line per workspace, paid on Stripe.
726#[derive(Clone, Debug, Serialize, Deserialize)]
727#[serde(rename_all = "camelCase")]
728pub struct EnterpriseInvoice {
729 pub invoice_id: String,
730 /// Stripe's page for it, where it is paid.
731 pub hosted_url: Option<String>,
732 pub amount_micros: i64,
733 /// `open`, `paid`, `overdue` or `void`.
734 pub status: String,
735 pub period: String,
736 pub lines: Vec<InvoiceLine>,
737 pub created_at: String,
738}
739
740#[derive(Clone, Debug, Serialize, Deserialize)]
741#[serde(rename_all = "camelCase")]
742pub struct InvoiceLine {
743 pub workspace: String,
744 pub amount_micros: i64,
745}
746
747/// A workspace's invoice from g1t: one per month, and one each time it is
748/// charged near its limit. Itemised, charged to the card on file, and kept
749/// in Stripe's billing page with its PDF.
750#[derive(Clone, Debug, Serialize, Deserialize)]
751#[serde(rename_all = "camelCase")]
752pub struct WorkspaceInvoice {
753 pub invoice_id: String,
754 pub workspace: String,
755 /// `month` (2026-10) or `threshold`.
756 pub reason: String,
757 pub period: String,
758 pub amount_micros: i64,
759 /// `paid`, `open`, `failed` or `void`.
760 pub status: String,
761 pub hosted_url: Option<String>,
762 pub pdf_url: Option<String>,
763 pub lines: Vec<InvoiceItem>,
764 pub created_at: String,
765}
766
767#[derive(Clone, Debug, Serialize, Deserialize)]
768#[serde(rename_all = "camelCase")]
769pub struct InvoiceItem {
770 pub description: String,
771 pub amount_micros: i64,
772}
773
774/// `invoices`: a workspace's invoices from g1t, newest first. Members
775/// only. Returns `Outcome<Vec<WorkspaceInvoice>>`.
776#[derive(Debug, Serialize, Deserialize)]
777pub struct InvoicesArgs {
778 pub workspace: String,
779 pub viewer: Viewer,
780}
781
782/// `admin_workspace_invoices`: the same, for staff. Returns
783/// `Vec<WorkspaceInvoice>`.
784#[derive(Debug, Serialize, Deserialize)]
785pub struct AdminWorkspaceInvoicesArgs {
786 pub workspace: String,
787}
788
789/// `statement`: a month of a workspace's ledger, grouped by day (or by
790/// project) with a line per kind of charge. Members only. Returns
791/// `Outcome<Statement>`.
792#[derive(Debug, Serialize, Deserialize)]
793pub struct StatementArgs {
794 pub workspace: String,
795 pub viewer: Viewer,
796 /// YYYY-MM; this month when absent.
797 #[serde(default)]
798 pub month: Option<String>,
799 /// `day` (the default) or `project`.
800 #[serde(default)]
801 pub group: Option<String>,
802}
803
804#[derive(Clone, Debug, Serialize, Deserialize)]
805#[serde(rename_all = "camelCase")]
806pub struct Statement {
807 pub month: String,
808 /// Months with any entries, newest first.
809 pub months: Vec<String>,
810 pub groups: Vec<StatementGroup>,
811 pub totals: StatementTotals,
812}
813
814#[derive(Clone, Debug, Serialize, Deserialize)]
815#[serde(rename_all = "camelCase")]
816pub struct StatementGroup {
817 /// The day (YYYY-MM-DD) or the project (`owner/name`, or empty).
818 pub key: String,
819 pub label: String,
820 pub lines: Vec<StatementLine>,
821 /// What the group's charges come to.
822 pub charged_micros: i64,
823}
824
825#[derive(Clone, Debug, Serialize, Deserialize)]
826#[serde(rename_all = "camelCase")]
827pub struct StatementLine {
828 /// Agent runs, Sandbox time, Deployments, Payments, Credits from g1t,
829 /// Refunds, Runs on your own model provider.
830 pub kind: String,
831 pub count: u32,
832 /// Charges positive; money in (payments, credits) negative.
833 pub charged_micros: i64,
834 pub cost_micros: i64,
835}
836
837#[derive(Clone, Debug, Serialize, Deserialize)]
838#[serde(rename_all = "camelCase")]
839pub struct StatementTotals {
840 pub charged_micros: i64,
841 pub paid_micros: i64,
842 pub cost_micros: i64,
843 pub entries: u32,
844}
845
846/// `statement_entries`: one statement line's entries, newest first, 50 at
847/// a time (`before` = the last id seen). Returns `Outcome<Vec<LedgerEntry>>`.
848#[derive(Debug, Serialize, Deserialize)]
849pub struct StatementEntriesArgs {
850 pub workspace: String,
851 pub viewer: Viewer,
852 pub month: String,
853 pub kind: String,
854 #[serde(default)]
855 pub day: Option<String>,
856 #[serde(default)]
857 pub project: Option<String>,
858 #[serde(default)]
859 pub before: Option<String>,
860}
861
862// --- Sales (sudo.g1t.sh) ------------------------------------------------------
863//
864// What staff need to know to reach out: who is growing, who is close to
865// their limit, who was declined, who has become a steady customer. And what
866// was done about it: a stage, an owner on g1t's side, a next step, notes.
867
868/// Why a workspace is worth a look.
869#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
870#[serde(rename_all = "snake_case")]
871pub enum SignalKind {
872 /// At its limit, or its own spend limit: work is stopped.
873 AtLimit,
874 /// Past 80% of what is available to it: about to need more.
875 NearCeiling,
876 /// Its card was declined or a payment disputed.
877 Declined,
878 /// This month is well ahead of last month.
879 Growing,
880 /// Became Established: the ceiling now follows its spend.
881 Established,
882 /// Paid g1t for the first time.
883 FirstPayment,
884 /// Spending enough that custom terms or an enterprise may suit it.
885 HighSpend,
886}
887
888#[derive(Clone, Debug, Serialize, Deserialize)]
889#[serde(rename_all = "camelCase")]
890pub struct Signal {
891 pub workspace: String,
892 pub kind: SignalKind,
893 /// One sentence, with the figures.
894 pub detail: String,
895 /// The figure that matters, such as this month's spend.
896 pub value_micros: i64,
897 /// Its sales stage, if staff gave it one.
898 pub stage: Option<String>,
899 pub owner: Option<String>,
900 #[serde(default)]
901 pub next_step: Option<String>,
902 /// When the next step is due, `YYYY-MM-DD`.
903 #[serde(default)]
904 pub next_at: Option<String>,
905}
906
907/// `admin_invoices`: every invoice g1t has sent, workspaces' and
908/// enterprises', newest first. Returns `Vec<InvoiceSummary>`.
909#[derive(Debug, Default, Serialize, Deserialize)]
910pub struct AdminInvoicesArgs {
911 /// `paid`, `open`, `failed`, `overdue` or `void`.
912 #[serde(default)]
913 pub status: Option<String>,
914 /// YYYY-MM, by when it was sent.
915 #[serde(default)]
916 pub month: Option<String>,
917}
918
919#[derive(Clone, Debug, Serialize, Deserialize)]
920#[serde(rename_all = "camelCase")]
921pub struct InvoiceSummary {
922 pub invoice_id: String,
923 /// `workspace` or `enterprise`.
924 pub kind: String,
925 /// The workspace's slug, or the enterprise's account id.
926 pub account: String,
927 /// What to call it: the workspace, or the enterprise's name.
928 pub name: String,
929 pub reason: String,
930 pub period: String,
931 pub amount_micros: i64,
932 pub status: String,
933 pub hosted_url: Option<String>,
934 pub created_at: String,
935 pub paid_at: Option<String>,
936}
937
938/// `admin_audit`: every change made in sudo, and by Stripe, newest first.
939/// Returns `Vec<AdminAction>`.
940#[derive(Debug, Default, Serialize, Deserialize)]
941pub struct AdminAuditArgs {
942 #[serde(default)]
943 pub by: Option<String>,
944 #[serde(default)]
945 pub action: Option<String>,
946 /// Only those before this time, for paging.
947 #[serde(default)]
948 pub before: Option<String>,
949}
950
951/// `admin_signals`: every workspace worth reaching out to, most urgent
952/// first. Returns `Vec<Signal>`.
953#[derive(Debug, Default, Serialize, Deserialize)]
954pub struct AdminSignalsArgs {}
955
956/// What staff are doing about a workspace.
957#[derive(Clone, Debug, Serialize, Deserialize)]
958#[serde(rename_all = "camelCase")]
959pub struct SalesRecord {
960 pub workspace: String,
961 /// `none`, `lead`, `contacted`, `negotiating`, `won`, `lost` or `churn_risk`.
962 pub stage: String,
963 /// The staff member looking after it.
964 pub owner: Option<String>,
965 pub next_step: Option<String>,
966 /// RFC 3339 date.
967 pub next_at: Option<String>,
968 pub notes: Vec<SalesNote>,
969 pub updated_at: Option<String>,
970}
971
972#[derive(Clone, Debug, Serialize, Deserialize)]
973#[serde(rename_all = "camelCase")]
974pub struct SalesNote {
975 pub id: String,
976 pub text: String,
977 pub by: String,
978 pub created_at: String,
979}
980
981/// `admin_sales`: a workspace's sales record. Returns `SalesRecord`.
982#[derive(Debug, Serialize, Deserialize)]
983pub struct AdminSalesArgs {
984 pub workspace: String,
985}
986
987/// `admin_set_sales`: its stage, owner and next step. Returns `Outcome<SalesRecord>`.
988#[derive(Debug, Serialize, Deserialize)]
989pub struct AdminSetSalesArgs {
990 pub workspace: String,
991 pub stage: String,
992 #[serde(default)]
993 pub owner: Option<String>,
994 #[serde(default)]
995 pub next_step: Option<String>,
996 #[serde(default)]
997 pub next_at: Option<String>,
998 pub by: String,
999}
1000
1001/// `admin_add_note`. Returns `Outcome<SalesRecord>`.
1002#[derive(Debug, Serialize, Deserialize)]
1003pub struct AdminAddNoteArgs {
1004 pub workspace: String,
1005 pub text: String,
1006 pub by: String,
1007}
1008
1009/// `admin_overview`: the business at a glance. Returns `Overview`.
1010#[derive(Debug, Default, Serialize, Deserialize)]
1011pub struct AdminOverviewArgs {}
1012
1013#[derive(Clone, Debug, Serialize, Deserialize)]
1014#[serde(rename_all = "camelCase")]
1015pub struct Overview {
1016 /// YYYY-MM.
1017 pub month: String,
1018 /// The last six months, oldest first, all workspaces together.
1019 pub months: Vec<MonthFigures>,
1020 /// This month by kind of usage: models, sandbox, deployments, plans.
1021 pub by_kind: Vec<KindFigures>,
1022 pub paying_workspaces: u32,
1023 pub stopped: u32,
1024 pub near_ceiling: u32,
1025 pub declined: u32,
1026 /// Sent and not yet paid, workspaces and enterprises.
1027 pub open_invoices_micros: i64,
1028 /// Follow-ups due today or earlier.
1029 pub follow_ups_due: u32,
1030}
1031
1032#[derive(Clone, Debug, Serialize, Deserialize)]
1033#[serde(rename_all = "camelCase")]
1034pub struct KindFigures {
1035 pub kind: String,
1036 pub charged_micros: i64,
1037 pub cost_micros: i64,
1038}
1039
1040// --- Staff (sudo.g1t.sh) ------------------------------------------------------
1041//
1042// Called only by the sudo app, which only g1t staff can reach (behind
1043// Cloudflare Access). Each change names who made it, and is kept in the
1044// audit log.
1045
1046/// `admin_accounts`: every billing account, with where each stands this
1047/// month. Returns `Vec<AccountSummary>`.
1048#[derive(Debug, Default, Serialize, Deserialize)]
1049pub struct AdminAccountsArgs {
1050 #[serde(default)]
1051 pub query: Option<String>,
1052 /// Exactly these workspaces' accounts, such as one page of sudo's
1053 /// list; every account with activity when absent.
1054 #[serde(default)]
1055 pub workspaces: Option<Vec<String>>,
1056}
1057
1058#[derive(Clone, Debug, Serialize, Deserialize)]
1059#[serde(rename_all = "camelCase")]
1060pub struct AccountSummary {
1061 pub account: BillingAccount,
1062 pub limit: Limit,
1063 /// Charged this month, after terms.
1064 pub charged_micros: i64,
1065 /// What this month's usage cost g1t.
1066 pub cost_micros: i64,
1067 /// Paid, ever.
1068 pub paid_micros: i64,
1069 /// The same figures for each of the account's workspaces that has
1070 /// any, so staff can see what one member of an enterprise used.
1071 #[serde(default)]
1072 pub by_workspace: Vec<WorkspaceFigures>,
1073 /// The last six months, oldest first, for trends.
1074 #[serde(default)]
1075 pub months: Vec<MonthFigures>,
1076}
1077
1078/// One month of an account's billing.
1079#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1080#[serde(rename_all = "camelCase")]
1081pub struct MonthFigures {
1082 /// YYYY-MM.
1083 pub month: String,
1084 pub charged_micros: i64,
1085 pub cost_micros: i64,
1086 pub paid_micros: i64,
1087}
1088
1089/// One workspace's share of an [`AccountSummary`].
1090#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1091#[serde(rename_all = "camelCase")]
1092pub struct WorkspaceFigures {
1093 pub workspace: String,
1094 pub charged_micros: i64,
1095 pub cost_micros: i64,
1096 pub paid_micros: i64,
1097}
1098
1099/// `admin_account`: one account in full. Returns `Outcome<AccountDetail>`.
1100#[derive(Debug, Serialize, Deserialize)]
1101pub struct AdminAccountArgs {
1102 /// An account id, or a workspace slug.
1103 pub id: String,
1104}
1105
1106#[derive(Clone, Debug, Serialize, Deserialize)]
1107#[serde(rename_all = "camelCase")]
1108pub struct AccountDetail {
1109 pub summary: AccountSummary,
1110 /// Each workspace's limit, for an enterprise.
1111 pub workspaces: Vec<Limit>,
1112 pub ledger: Vec<LedgerEntry>,
1113 pub audit: Vec<AdminAction>,
1114}
1115
1116/// `admin_set_terms`. Returns `Outcome<BillingAccount>`.
1117#[derive(Debug, Serialize, Deserialize)]
1118pub struct AdminSetTermsArgs {
1119 pub id: String,
1120 pub terms: Terms,
1121 pub by: String,
1122}
1123
1124/// `admin_create_enterprise`. Returns `Outcome<BillingAccount>`.
1125#[derive(Debug, Serialize, Deserialize)]
1126pub struct AdminCreateEnterpriseArgs {
1127 pub name: String,
1128 pub workspaces: Vec<String>,
1129 pub by: String,
1130}
1131
1132/// `admin_attach`: moves a workspace onto an enterprise account, or back
1133/// onto its own with `account: None`. Returns `Outcome<BillingAccount>`.
1134#[derive(Debug, Serialize, Deserialize)]
1135pub struct AdminAttachArgs {
1136 pub workspace: String,
1137 pub account: Option<String>,
1138 pub by: String,
1139}
1140
1141/// `admin_credit`: money g1t gives a workspace, such as a refund or a
1142/// goodwill credit. Returns `Outcome<LedgerEntry>`.
1143#[derive(Debug, Serialize, Deserialize)]
1144pub struct AdminCreditArgs {
1145 pub workspace: String,
1146 pub amount_micros: i64,
1147 pub note: String,
1148 pub by: String,
1149}
1150
1151/// One change made in sudo.
1152#[derive(Clone, Debug, Serialize, Deserialize)]
1153#[serde(rename_all = "camelCase")]
1154pub struct AdminAction {
1155 pub id: String,
1156 pub account: String,
1157 pub action: String,
1158 pub detail: String,
1159 pub by: String,
1160 pub created_at: String,
1161}
1162
1163/// What a feature's plan costs and includes.
1164#[derive(Clone, Debug, Serialize, Deserialize)]
1165#[serde(rename_all = "camelCase")]
1166pub struct Plan {
1167 pub feature: Feature,
1168 pub title: String,
1169 /// Charged every month while the plan is on, in cents.
1170 pub monthly_cents: u32,
1171 /// What the monthly price includes, one line each, for people to read.
1172 pub includes: Vec<String>,
1173 /// How usage past the allowance is charged, for people to read.
1174 pub overage: String,
1175}
1176
1177#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1178#[serde(rename_all = "snake_case")]
1179pub enum SubscriptionStatus {
1180 /// Paid up; the feature works.
1181 Active,
1182 /// Paid up to the end of the period, and ends then.
1183 Canceling,
1184 /// The last payment failed; the feature is off until it is paid.
1185 PastDue,
1186 /// Ended.
1187 Canceled,
1188}
1189
1190impl SubscriptionStatus {
1191 /// Whether the feature works in this state.
1192 pub fn on(self) -> bool {
1193 matches!(self, SubscriptionStatus::Active | SubscriptionStatus::Canceling)
1194 }
1195}
1196
1197/// A workspace's plan for one feature.
1198#[derive(Clone, Debug, Serialize, Deserialize)]
1199#[serde(rename_all = "camelCase")]
1200pub struct Subscription {
1201 pub feature: Feature,
1202 pub status: SubscriptionStatus,
1203 /// RFC 3339: when the period paid for ends, and the plan renews or
1204 /// ends.
1205 pub period_end: Option<String>,
1206 /// Username of whoever turned it on.
1207 pub started_by: String,
1208 /// RFC 3339.
1209 pub started_at: String,
1210}
1211
1212/// A feature as a workspace sees it: what it costs, and its plan if it has
1213/// one.
1214#[derive(Clone, Debug, Serialize, Deserialize)]
1215#[serde(rename_all = "camelCase")]
1216pub struct FeatureState {
1217 pub plan: Plan,
1218 pub subscription: Option<Subscription>,
1219 /// Whether the feature works for the workspace now.
1220 pub on: bool,
1221}
1222
1223/// `features`: every paid feature and the workspace's plan for each.
1224/// Members only. Returns `Outcome<Vec<FeatureState>>`.
1225#[derive(Debug, Serialize, Deserialize)]
1226pub struct FeaturesArgs {
1227 pub workspace: String,
1228 pub viewer: Viewer,
1229}
1230
1231/// `subscribe`: starts the card page for a feature's monthly plan. Owners
1232/// only. Returns `Outcome<Checkout>`; the page's id comes back to
1233/// `return_url` as `session`, for `confirm_subscription`.
1234#[derive(Debug, Serialize, Deserialize)]
1235#[serde(rename_all = "camelCase")]
1236pub struct SubscribeArgs {
1237 pub actor: User,
1238 pub workspace: String,
1239 pub feature: Feature,
1240 pub return_url: String,
1241}
1242
1243/// `confirm_subscription`: turns the feature on once the processor says
1244/// the plan was paid for. Safe to call any number of times. Returns
1245/// `Outcome<FeatureState>`.
1246#[derive(Debug, Serialize, Deserialize)]
1247pub struct ConfirmSubscriptionArgs {
1248 pub workspace: String,
1249 pub viewer: Viewer,
1250 pub session: String,
1251}
1252
1253/// `cancel_subscription` (`resume` false) ends a plan at the end of the
1254/// period paid for; with `resume` true, takes that back. Owners only.
1255/// Returns `Outcome<FeatureState>`.
1256#[derive(Debug, Serialize, Deserialize)]
1257pub struct CancelSubscriptionArgs {
1258 pub actor: User,
1259 pub workspace: String,
1260 pub feature: Feature,
1261 #[serde(default)]
1262 pub resume: bool,
1263}
1264
1265/// `has_feature`: whether a feature works for a workspace now, asked by the
1266/// service that provides it before doing paid work. Returns
1267/// `Outcome<bool>`: a failure, with the reason to show, when it does not.
1268/// True everywhere when no card processor is configured.
1269#[derive(Debug, Serialize, Deserialize)]
1270pub struct HasFeatureArgs {
1271 pub workspace: String,
1272 pub feature: Feature,
1273}
1274
1275/// `charge_feature`: usage of a feature past its plan's allowance, charged
1276/// from the workspace's credit at cost plus the margin, whatever
1277/// `FREE_WHILE_BUILDING` says. Called by the service that provides it.
1278/// Charged once per `reference`. Returns `Outcome<bool>`: false if that
1279/// reference was charged before.
1280#[derive(Debug, Serialize, Deserialize)]
1281#[serde(rename_all = "camelCase")]
1282pub struct ChargeFeatureArgs {
1283 pub workspace: String,
1284 pub feature: Feature,
1285 /// What it cost g1t, in millionths of a dollar, before the margin.
1286 pub cost_micros: i64,
1287 pub description: String,
1288 /// `namespace/name`, when the usage was one repository's.
1289 pub repo: Option<String>,
1290 /// Unique to this charge, e.g. `deployments/acme/2026-10`.
1291 pub reference: String,
1292}
1293
1294#[cfg(test)]
1295mod tests {
1296 use super::*;
1297
1298 #[test]
1299 fn features_are_named_as_the_site_sends_them() {
1300 assert_eq!(
1301 serde_json::to_value(Feature::Deployments).unwrap(),
1302 serde_json::json!("deployments")
1303 );
1304 assert_eq!(Feature::parse("deployments"), Some(Feature::Deployments));
1305 assert!(SubscriptionStatus::Canceling.on());
1306 assert!(!SubscriptionStatus::PastDue.on());
1307 }
1308
1309 #[test]
1310 fn who_pays_is_read_as_the_runner_sends_it() {
1311 let run: StartRunArgs = serde_json::from_value(serde_json::json!({
1312 "workspace": "acme",
1313 "repo": { "namespace": "acme", "name": "web" },
1314 "number": 7,
1315 "task": "implement",
1316 "model": "Claude Sonnet 5.5",
1317 "billedTo": "workspace",
1318 }))
1319 .unwrap();
1320 assert_eq!(run.billed_to, "workspace");
1321 }
1322}