Costs & margin: one total for who g1t paid, own spend explained, rates and replaced rises said plainly
Who g1t paid is now All in's cost: Cloudflare's usage, its subscriptions over the range and the model providers ($32.49, not $2.49 beside an All in of $32.49). g1t's own spend says what it is and why it differs from the statement: the calendar month so far at cost as each charge settled (the price book's cost for sandboxes, where the statement takes Cloudflare's bill, $0 inside included usage), the whole month's subscriptions, and spend on workspaces a testing reset later wiped. Billing returns that last part (SpendCaps.reset_micros, reset_workspaces): syntaqx's $7.41 of 10-02 to 10-05 is most of the $7.61 "charged without real money", which is at cost, not at price; its note now says so. Margins are labelled as a share of the price (cost plus 20% is 16.7%). A free workspace's net is red only for what was neither paid nor given. Price versions carry a basis: the agent rate and security activation are rates g1t sets, so no cost is shown for them, and the token weights show as multipliers. Version and in-force dates show their time. A rise replaced by a newer measurement before its date marks its proposal superseded (it never took effect), and migration 0049 marks the two from 2026-10-07 that showed as applied with no in-force date. No rise skipped its notice: v2 (due 10-21) was replaced by v3 (due 10-22) and sandbox seconds are still charged at v1.
| 6 | 6 | import { | |
| 7 | 7 | daySeries, | |
| 8 | 8 | daysBetween, | |
| 9 | + | marginOnPrice, | |
| 9 | 10 | marginPercent, | |
| 10 | 11 | marginTone, | |
| 11 | 12 | parseBucket, | |
| ⋯ | |||
| 13 | 14 | parseMapping, | |
| 14 | 15 | parseRange, | |
| 15 | 16 | percentLabel, | |
| 17 | + | proposalOutcome, | |
| 16 | 18 | spendBanner, | |
| 17 | 19 | spendRows, | |
| 20 | + | subscriptionsOver, | |
| 18 | 21 | unitDollars, | |
| 22 | + | versionCells, | |
| 23 | + | whoPaid, | |
| 19 | 24 | } from "./costs.ts"; | |
| 20 | 25 | ||
| 21 | 26 | const day = (d: string, bucket: string, cf: number, own: number, value: number, cash: number): CostDay => ({ | |
| ⋯ | |||
| 150 | 155 | assert.deepEqual(rows.map((r) => r.key), ["comped", "trial", "free", "fixed"]); | |
| 151 | 156 | assert.equal(totalMicros, 76_000_000); | |
| 152 | 157 | }); | |
| 158 | + | ||
| 159 | + | test("charged without real money is what it cost g1t, never its price", () => { | |
| 160 | + | const { rows } = spendRows(caps({ monthBuckets: [{ bucket: "unpaid", title: "Charged without real money", micros: 7_610_000 }] })); | |
| 161 | + | assert.match(rows[0]!.note, /what it cost g1t, not what was charged/); | |
| 162 | + | }); | |
| 163 | + | ||
| 164 | + | test("margins are a share of the price: cost plus 20% is 16.7%", () => { | |
| 165 | + | assert.equal(percentLabel(marginOnPrice(20)), "16.7%"); | |
| 166 | + | assert.equal(marginOnPrice(0), 0); | |
| 167 | + | // As By product showed it: $2.99 charged for models that cost $2.49. | |
| 168 | + | assert.equal(percentLabel(marginPercent(2_988_000, 2_490_000)), "16.7%"); | |
| 169 | + | }); | |
| 170 | + | ||
| 171 | + | test("who g1t paid is the statement's all-in cost, subscriptions included", () => { | |
| 172 | + | const subscriptions = subscriptionsOver(30_000_000, 30); | |
| 173 | + | const paid = whoPaid({ costMicros: 2_490_000, cloudflareCostMicros: 0, modelsCostMicros: 2_490_000 }, subscriptions); | |
| 174 | + | assert.deepEqual(paid, { totalMicros: 32_490_000, cloudflareMicros: 0, subscriptionsMicros: 30_000_000, modelsMicros: 2_490_000 }); | |
| 175 | + | assert.equal(subscriptionsOver(30_000_000, 7), 7_000_000); | |
| 176 | + | // From older billing, Cloudflare's part is the rest. | |
| 177 | + | assert.equal(whoPaid({ costMicros: 3_000_000, modelsCostMicros: 1_000_000 }, 0).cloudflareMicros, 2_000_000); | |
| 178 | + | }); | |
| 179 | + | ||
| 180 | + | test("a rate g1t sets shows no cost, and a weight is no money", () => { | |
| 181 | + | assert.deepEqual(versionCells({ costMicros: 250_000, priceMicros: 250_000, basis: "rate" }), { | |
| 182 | + | cost: "—", | |
| 183 | + | price: "$0.250", | |
| 184 | + | note: "g1t's own rate: no cost behind it", | |
| 185 | + | }); | |
| 186 | + | assert.equal(versionCells({ costMicros: 100_000, priceMicros: 100_000, basis: "weight" }).price, "×0.1"); | |
| 187 | + | assert.deepEqual(versionCells({ costMicros: 16.44, priceMicros: 19.73, basis: "cost" }), { cost: unitDollars(16.44), price: unitDollars(19.73), note: null }); | |
| 188 | + | // Older billing sends no basis: a cost. | |
| 189 | + | assert.equal(versionCells({ costMicros: 1_000_000, priceMicros: 1_200_000 }).cost, "$1.00"); | |
| 190 | + | }); | |
| 191 | + | ||
| 192 | + | test("a rise replaced before its date says so, rather than missing its date", () => { | |
| 193 | + | assert.equal( | |
| 194 | + | proposalOutcome({ status: "superseded", decidedBy: "guardrail", effectiveAt: null }), | |
| 195 | + | "applied by guardrail, then replaced by a later measurement before it took effect; nothing was charged at it", | |
| 196 | + | ); | |
| 197 | + | // From before billing marked them superseded. | |
| 198 | + | assert.match(proposalOutcome({ status: "applied", decidedBy: "guardrail", effectiveAt: null }) ?? "", /replaced by a later measurement/); | |
| 199 | + | assert.equal(proposalOutcome({ status: "applied", decidedBy: "guardrail", effectiveAt: "2026-10-22T04:18:12.571Z" }), "applied by guardrail"); | |
| 200 | + | assert.equal(proposalOutcome({ status: "superseded", decidedBy: null, effectiveAt: null }), "replaced by a later measurement"); | |
| 201 | + | assert.equal(proposalOutcome({ status: "open", decidedBy: null, effectiveAt: null }), null); | |
| 202 | + | }); | |
| 44 | 44 | return [...totals.values()]; | |
| 45 | 45 | } | |
| 46 | 46 | ||
| 47 | + | /** | |
| 48 | + | * The margin on the price a markup gives, in percent: cost plus 20% is a | |
| 49 | + | * 16.7% margin, since the 20% is of the cost and the margin of the price. | |
| 50 | + | */ | |
| 51 | + | export function marginOnPrice(markupPercent: number): number { | |
| 52 | + | return markupPercent > -100 ? (markupPercent / (100 + markupPercent)) * 100 : 0; | |
| 53 | + | } | |
| 54 | + | ||
| 55 | + | /** | |
| 56 | + | * Who g1t paid over the range: Cloudflare's usage bill, its subscriptions | |
| 57 | + | * (a month's, over the range) and the model providers. The same total as | |
| 58 | + | * the statement's All in. | |
| 59 | + | */ | |
| 60 | + | export function whoPaid( | |
| 61 | + | overall: { costMicros: number; cloudflareCostMicros?: number; modelsCostMicros?: number }, | |
| 62 | + | subscriptionsMicros: number, | |
| 63 | + | ): { totalMicros: number; cloudflareMicros: number; subscriptionsMicros: number; modelsMicros: number } { | |
| 64 | + | const modelsMicros = overall.modelsCostMicros ?? 0; | |
| 65 | + | const cloudflareMicros = overall.cloudflareCostMicros ?? overall.costMicros - modelsMicros; | |
| 66 | + | return { totalMicros: overall.costMicros + subscriptionsMicros, cloudflareMicros, subscriptionsMicros, modelsMicros }; | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | /** Cloudflare's subscriptions over a range of days: a month's, pro rata. */ | |
| 70 | + | export function subscriptionsOver(monthlyMicros: number, days: number): number { | |
| 71 | + | return Math.round((monthlyMicros * days) / 30); | |
| 72 | + | } | |
| 73 | + | ||
| 74 | + | /** | |
| 75 | + | * A price version's cost and price as the table shows them. A rate g1t | |
| 76 | + | * sets has no cost behind it; a weight is a multiplier, not money. | |
| 77 | + | */ | |
| 78 | + | export function versionCells(v: { costMicros: number; priceMicros: number; basis?: string }): { cost: string; price: string; note: string | null } { | |
| 79 | + | if (v.basis === "weight") { | |
| 80 | + | const weight = Number((v.costMicros / 1_000_000).toFixed(6)); | |
| 81 | + | return { cost: "—", price: `×${weight}`, note: "A weight on the agent rate's tokens, not money" }; | |
| 82 | + | } | |
| 83 | + | if (v.basis === "rate") return { cost: "—", price: unitDollars(v.priceMicros), note: "g1t's own rate: no cost behind it" }; | |
| 84 | + | return { cost: unitDollars(v.costMicros), price: unitDollars(v.priceMicros), note: null }; | |
| 85 | + | } | |
| 86 | + | ||
| 87 | + | /** | |
| 88 | + | * What became of a proposal, in words, for its line: who decided it, and | |
| 89 | + | * when one never took effect because a later measurement replaced it. | |
| 90 | + | */ | |
| 91 | + | export function proposalOutcome(p: { status: string; decidedBy: string | null; effectiveAt: string | null }): string | null { | |
| 92 | + | const by = p.decidedBy ? `${p.status === "superseded" ? "applied" : p.status} by ${p.decidedBy}` : null; | |
| 93 | + | const replaced = "replaced by a later measurement before it took effect; nothing was charged at it"; | |
| 94 | + | if (p.status === "superseded") return by ? `${by}, then ${replaced}` : "replaced by a later measurement"; | |
| 95 | + | if ((p.status === "applied" || p.status === "approved") && !p.effectiveAt) return by ? `${by}, then ${replaced}` : replaced; | |
| 96 | + | return by; | |
| 97 | + | } | |
| 98 | + | ||
| 47 | 99 | /** Margin as a whole percent of revenue; none when there was none. */ | |
| 48 | 100 | export function marginPercent(revenueMicros: number, costMicros: number): number | null { | |
| 49 | 101 | return revenueMicros > 0 ? ((revenueMicros - costMicros) / revenueMicros) * 100 : null; | |
| ⋯ | |||
| 192 | 244 | /** What g1t paid this month, by bucket, with the free tier and Cloudflare's subscriptions; and the total. */ | |
| 193 | 245 | export function spendRows(caps: SpendCaps): { rows: { key: string; title: string; micros: number; note: string }[]; totalMicros: number } { | |
| 194 | 246 | const notes: Record<string, string> = { | |
| 195 | − | comped: "Work on accounts with a 100% discount, at cost", | |
| 196 | − | trial: "Trial credit, at cost", | |
| 197 | − | oss: "Checks and workflows on public repositories, at cost", | |
| 198 | − | given: "Free workspaces' overruns past their trial", | |
| 199 | − | unpaid: "Charged, but no real money yet (test-mode payments)", | |
| 247 | + | comped: "Work on accounts with a 100% discount: what it cost g1t, not its price", | |
| 248 | + | trial: "Trial credit, at what it cost g1t", | |
| 249 | + | oss: "Checks and workflows on public repositories, at what they cost g1t", | |
| 250 | + | given: "Free workspaces' overruns past their trial, at what they cost g1t", | |
| 251 | + | unpaid: "Usage charged while payments are in Stripe's test mode: what it cost g1t, not what was charged", | |
| 200 | 252 | }; | |
| 201 | 253 | const rows = caps.monthBuckets.map((b) => ({ key: b.bucket, title: b.title, micros: b.micros, note: notes[b.bucket] ?? "" })); | |
| 202 | − | rows.push({ key: "free", title: "Free tier", micros: caps.freeTierMicros, note: "Free workspaces' share of git, storage and platform, reconciled through yesterday" }); | |
| 254 | + | rows.push({ key: "free", title: "Free tier", micros: caps.freeTierMicros, note: "Free workspaces' share of git, storage and platform, as last reconciled" }); | |
| 203 | 255 | rows.push({ | |
| 204 | 256 | key: "fixed", | |
| 205 | 257 | title: "Cloudflare subscriptions", | |
| 206 | 258 | micros: caps.fixedMonthlyMicros, | |
| 207 | − | note: caps.fixedSource === "cloudflare" ? "A month, as Cloudflare lists them" : "A month, estimated (CLOUDFLARE_FIXED_MONTHLY_MICROS)", | |
| 259 | + | note: caps.fixedSource === "cloudflare" ? "The whole month, as Cloudflare lists them" : "The whole month, estimated (CLOUDFLARE_FIXED_MONTHLY_MICROS)", | |
| 208 | 260 | }); | |
| 209 | 261 | return { rows, totalMicros: rows.reduce((sum, row) => sum + row.micros, 0) }; | |
| 210 | 262 | } | |
| 5 | 5 | import type { Route } from "./+types/costs-bill"; | |
| 6 | 6 | import { CostsHeader } from "~/components/costs-header"; | |
| 7 | 7 | import { Badge, Button, EmptyState, Field, Input, Notice, Section, When } from "~/components/ui"; | |
| 8 | − | import { countLabel, driftLabel, percentLabel, unitDollars } from "~/lib/costs"; | |
| 8 | + | import { countLabel, driftLabel, percentLabel, proposalOutcome, unitDollars, versionCells } from "~/lib/costs"; | |
| 9 | 9 | import { type CostsActionResult, costsAction, costsLoader } from "~/lib/costs-route.server"; | |
| 10 | 10 | import { dollarsField, usd } from "~/lib/money"; | |
| 11 | 11 | ||
| ⋯ | |||
| 111 | 111 | </tr> | |
| 112 | 112 | </thead> | |
| 113 | 113 | <tbody> | |
| 114 | − | {report.versions.map((v) => ( | |
| 115 | − | <tr key={v.id} className="border-b border-line align-top last:border-0"> | |
| 116 | − | <td className="px-4 py-2.5 sm:px-5"> | |
| 117 | − | <span className="font-mono text-xs">{v.meter}</span> <span className="text-faint">v{v.version}</span> | |
| 118 | − | {!v.appliedAt && ( | |
| 119 | − | <span className="ml-2"> | |
| 120 | − | <Badge tone="info">Coming</Badge> | |
| 121 | − | </span> | |
| 122 | − | )} | |
| 123 | − | </td> | |
| 124 | − | <td className="tabular px-4 py-2.5 text-right">{unitDollars(v.costMicros)}</td> | |
| 125 | − | <td className="tabular px-4 py-2.5 text-right">{unitDollars(v.priceMicros)}</td> | |
| 126 | − | <td className="px-4 py-2.5 text-xs text-muted"> | |
| 127 | − | <When at={v.effectiveAt} /> | |
| 128 | − | </td> | |
| 129 | − | <td className="px-4 py-2.5 text-xs text-faint sm:pr-5"> | |
| 130 | − | {v.reason} · {v.createdBy} | |
| 131 | − | </td> | |
| 132 | − | </tr> | |
| 133 | − | ))} | |
| 114 | + | {report.versions.map((v) => { | |
| 115 | + | const cells = versionCells(v); | |
| 116 | + | return ( | |
| 117 | + | <tr key={v.id} className="border-b border-line align-top last:border-0"> | |
| 118 | + | <td className="px-4 py-2.5 sm:px-5"> | |
| 119 | + | <span className="font-mono text-xs">{v.meter}</span> <span className="text-faint">v{v.version}</span> | |
| 120 | + | {!v.appliedAt && ( | |
| 121 | + | <span className="ml-2"> | |
| 122 | + | <Badge tone="info">Coming</Badge> | |
| 123 | + | </span> | |
| 124 | + | )} | |
| 125 | + | {cells.note && <span className="block text-xs text-faint">{cells.note}</span>} | |
| 126 | + | </td> | |
| 127 | + | <td className="tabular px-4 py-2.5 text-right">{cells.cost === "—" ? <span className="text-faint">—</span> : cells.cost}</td> | |
| 128 | + | <td className="tabular px-4 py-2.5 text-right">{cells.price}</td> | |
| 129 | + | <td className="px-4 py-2.5 text-xs text-muted"> | |
| 130 | + | <When at={v.effectiveAt} time /> | |
| 131 | + | </td> | |
| 132 | + | <td className="px-4 py-2.5 text-xs text-faint sm:pr-5"> | |
| 133 | + | {v.reason} · {v.createdBy} | |
| 134 | + | </td> | |
| 135 | + | </tr> | |
| 136 | + | ); | |
| 137 | + | })} | |
| 134 | 138 | </tbody> | |
| 135 | 139 | </table> | |
| 136 | 140 | </div> | |
| ⋯ | |||
| 278 | 282 | ||
| 279 | 283 | function ProposalRow({ proposal: p, error }: { proposal: PriceProposal; error: string | null }) { | |
| 280 | 284 | const rise = p.proposedCostMicros > p.currentCostMicros; | |
| 285 | + | const outcome = proposalOutcome(p); | |
| 281 | 286 | const statusTone = p.status === "open" ? "warn" : p.status === "rejected" || p.status === "superseded" ? "plain" : "mint"; | |
| 282 | 287 | return ( | |
| 283 | 288 | <li className="rounded-md border border-line px-4 py-3"> | |
| ⋯ | |||
| 297 | 302 | <p className="mt-1 text-xs text-muted">{p.reason}</p> | |
| 298 | 303 | <p className="mt-1 text-xs text-faint"> | |
| 299 | 304 | From the {p.source}, <When at={p.createdAt} time /> | |
| 300 | − | {p.decidedBy ? ` · ${p.status} by ${p.decidedBy}` : ""} | |
| 301 | − | {p.effectiveAt ? ( | |
| 305 | + | {outcome ? ` · ${outcome}` : ""} | |
| 306 | + | {p.effectiveAt && p.status !== "superseded" ? ( | |
| 302 | 307 | <> | |
| 303 | 308 | {" "} | |
| 304 | − | · in force from <When at={p.effectiveAt} /> | |
| 309 | + | · in force from <When at={p.effectiveAt} time /> | |
| 305 | 310 | </> | |
| 306 | 311 | ) : null} | |
| 307 | 312 | {p.note ? ` · “${p.note}”` : ""} | |
| 7 | 7 | import { DaysChart } from "~/components/costs"; | |
| 8 | 8 | import { CostsHeader, chip, costsHref } from "~/components/costs-header"; | |
| 9 | 9 | import { Badge, Button, Field, Input, Notice, Section, Stat, When } from "~/components/ui"; | |
| 10 | − | import { capPercent, daySeries, marginTone, parseBucket, percentLabel, spendRows } from "~/lib/costs"; | |
| 10 | + | import { capPercent, daySeries, marginOnPrice, marginTone, parseBucket, percentLabel, spendRows, subscriptionsOver, whoPaid } from "~/lib/costs"; | |
| 11 | 11 | import { type CostsActionResult, costsAction, costsLoader } from "~/lib/costs-route.server"; | |
| 12 | 12 | import { usd } from "~/lib/money"; | |
| 13 | 13 | ||
| 14 | 14 | export const meta: Route.MetaFunction = () => [{ title: "Costs & margin · sudo" }, { name: "robots", content: "noindex, nofollow" }]; | |
| 15 | 15 | ||
| 16 | + | /** Billing's MARGIN_PERCENT: what is added to cost. */ | |
| 17 | + | const MARKUP_PERCENT = 20; | |
| 18 | + | ||
| 16 | 19 | export const loader = ({ request, context }: Route.LoaderArgs) => costsLoader(request, context); | |
| 17 | 20 | export const action = ({ request, context }: Route.ActionArgs) => costsAction(request, context); | |
| 18 | 21 | ||
| ⋯ | |||
| 23 | 26 | export default function Costs({ loaderData, actionData }: Route.ComponentProps) { | |
| 24 | 27 | const { range, report, error, done } = loaderData; | |
| 25 | 28 | const failed = actionData && "error" in actionData ? (actionData as CostsActionResult) : null; | |
| 26 | − | const description = | |
| 27 | − | "What g1t cost to run (Cloudflare's bill and the model providers') against what workspaces paid, with what g1t gave away on purpose kept apart. Prices are cost plus 20%; the bill itself, drift and the price book are on Bill & pricing."; | |
| 29 | + | const description = `What g1t cost to run (Cloudflare's bill and the model providers') against what workspaces paid, with what g1t gave away on purpose kept apart. Prices are cost plus ${MARKUP_PERCENT}%, a ${percentLabel(marginOnPrice(MARKUP_PERCENT))} margin: every margin here is a share of the price, not of the cost. The bill itself, drift and the price book are on Bill & pricing.`; | |
| 28 | 30 | if (!report) { | |
| 29 | 31 | return ( | |
| 30 | 32 | <main className="mx-auto max-w-6xl px-4 py-8 sm:py-10"> | |
| ⋯ | |||
| 46 | 48 | ||
| 47 | 49 | <Statement report={report} floor={floor} range={range} proposals={open.length} /> | |
| 48 | 50 | ||
| 49 | − | <SpendSection caps={report.caps} error={failed?.section === "lift" ? failed.error : null} /> | |
| 51 | + | <SpendSection caps={report.caps} range={range} error={failed?.section === "lift" ? failed.error : null} /> | |
| 50 | 52 | ||
| 51 | 53 | <Section | |
| 52 | 54 | className="mt-6" | |
| ⋯ | |||
| 75 | 77 | /> | |
| 76 | 78 | </Section> | |
| 77 | 79 | ||
| 78 | − | <Section className="mt-6" title="By product" description={`Each of g1t's products over the last ${range} days. The floor is ${floor}%.`}> | |
| 80 | + | <Section | |
| 81 | + | className="mt-6" | |
| 82 | + | title="By product" | |
| 83 | + | description={`Each of g1t's products over the last ${range} days: what customers were charged at price, given away or not, against what it cost. The margin is the charge less the cost, as a share of the charge; at cost plus ${MARKUP_PERCENT}% it is ${percentLabel(marginOnPrice(MARKUP_PERCENT))}. The floor is ${floor}%.`} | |
| 84 | + | > | |
| 79 | 85 | <div className="-mx-4 overflow-x-auto sm:-mx-5"> | |
| 80 | 86 | <table className="w-full min-w-[44rem] text-sm"> | |
| 81 | 87 | <thead> | |
| ⋯ | |||
| 85 | 91 | <th className="px-4 py-2 text-right font-medium">Cost</th> | |
| 86 | 92 | <th className="px-4 py-2 text-right font-medium">Price book said</th> | |
| 87 | 93 | <th className="px-4 py-2 text-right font-medium">Margin</th> | |
| 88 | − | <th className="px-4 py-2 text-right font-medium sm:pr-5">%</th> | |
| 94 | + | <th className="px-4 py-2 text-right font-medium sm:pr-5">% of price</th> | |
| 89 | 95 | </tr> | |
| 90 | 96 | </thead> | |
| 91 | 97 | <tbody> | |
| ⋯ | |||
| 142 | 148 | <tbody> | |
| 143 | 149 | {report.topWorkspaces.map((w) => { | |
| 144 | 150 | const net = w.revenueMicros - w.costMicros; | |
| 151 | + | // What was given away on purpose is a budget, not a loss: | |
| 152 | + | // red only for what neither paid nor was given. | |
| 153 | + | const lost = net + (w.givenMicros ?? 0) < 0; | |
| 145 | 154 | return ( | |
| 146 | 155 | <tr key={w.workspace} className="border-b border-line last:border-0"> | |
| 147 | 156 | <td className="px-4 py-2.5 sm:px-5"> | |
| ⋯ | |||
| 153 | 162 | <td className="tabular px-4 py-2.5 text-right">{usd(w.costMicros)}</td> | |
| 154 | 163 | <td className="tabular px-4 py-2.5 text-right text-fg-soft">{usd(w.givenMicros ?? 0)}</td> | |
| 155 | 164 | <td className="tabular px-4 py-2.5 text-right">{usd(w.revenueMicros)}</td> | |
| 156 | − | <td className={`tabular px-4 py-2.5 text-right sm:pr-5 ${net < 0 && !w.internal ? "text-danger" : "text-fg-soft"}`}>{usd(net)}</td> | |
| 165 | + | <td className={`tabular px-4 py-2.5 text-right sm:pr-5 ${lost && !w.internal ? "text-danger" : "text-fg-soft"}`}>{usd(net)}</td> | |
| 157 | 166 | </tr> | |
| 158 | 167 | ); | |
| 159 | 168 | })} | |
| ⋯ | |||
| 204 | 213 | const usagePercent = soldSomething && o.usageMarginPercent !== undefined ? o.usageMarginPercent : null; | |
| 205 | 214 | const running = o.runningCostMicros ?? 0; | |
| 206 | 215 | const unmapped = o.unmappedCostMicros ?? 0; | |
| 207 | − | // Cloudflare's subscriptions are not on the usage bill: the estimate, over the range. | |
| 208 | − | const subscriptions = Math.round((report.caps.fixedMonthlyMicros * range) / 30); | |
| 216 | + | // Cloudflare's subscriptions are not on the usage bill: a month's, over the range. | |
| 217 | + | const subscriptions = subscriptionsOver(report.caps.fixedMonthlyMicros, range); | |
| 218 | + | const paid = whoPaid(o, subscriptions); | |
| 209 | 219 | const moneyIn = o.usageMicros + o.plansMicros; | |
| 210 | − | const spent = o.costMicros + subscriptions; | |
| 220 | + | const spent = paid.totalMicros; | |
| 211 | 221 | const net = moneyIn - spent; | |
| 212 | 222 | const givenParts = [ | |
| 213 | 223 | ["100% discounts", o.givenCompedMicros ?? 0], | |
| ⋯ | |||
| 272 | 282 | <Stat | |
| 273 | 283 | label="Margin on usage sold" | |
| 274 | 284 | value={percentLabel(usagePercent)} | |
| 275 | − | hint={soldSomething ? `${usd(usageIn, { cents: true })} paid for usage that cost ${usd(usageCost, { cents: true })}` : "No usage sold in this range"} | |
| 285 | + | hint={ | |
| 286 | + | soldSomething | |
| 287 | + | ? `Of the price: ${usd(usageIn, { cents: true })} paid for usage that cost ${usd(usageCost, { cents: true })}` | |
| 288 | + | : "No usage sold in this range" | |
| 289 | + | } | |
| 276 | 290 | tone={marginTone(usagePercent, floor)} | |
| 277 | 291 | /> | |
| 278 | 292 | <Stat | |
| ⋯ | |||
| 282 | 296 | /> | |
| 283 | 297 | <Stat | |
| 284 | 298 | label="Who g1t paid" | |
| 285 | − | value={usd(o.costMicros, { cents: true })} | |
| 286 | − | hint={`Cloudflare ${usd(o.cloudflareCostMicros ?? o.costMicros - (o.modelsCostMicros ?? 0), { cents: true })}, model providers ${usd(o.modelsCostMicros ?? 0, { cents: true })}`} | |
| 299 | + | value={usd(paid.totalMicros, { cents: true })} | |
| 300 | + | hint={`Cloudflare's usage ${usd(paid.cloudflareMicros, { cents: true })} and subscriptions ${usd(paid.subscriptionsMicros, { cents: true })}, model providers ${usd(paid.modelsMicros, { cents: true })}; the cost in All in`} | |
| 287 | 301 | /> | |
| 288 | 302 | <Stat | |
| 289 | 303 | label="Proposals waiting" | |
| ⋯ | |||
| 370 | 384 | ); | |
| 371 | 385 | } | |
| 372 | 386 | ||
| 373 | − | function SpendSection({ caps, error }: { caps: CostsReport["caps"]; error: string | null }) { | |
| 387 | + | function SpendSection({ caps, range, error }: { caps: CostsReport["caps"]; range: number; error: string | null }) { | |
| 374 | 388 | const { rows, totalMicros } = spendRows(caps); | |
| 375 | 389 | const net = caps.revenueMicros - totalMicros; | |
| 376 | 390 | return ( | |
| ⋯ | |||
| 378 | 392 | className="mt-6" | |
| 379 | 393 | id="spend" | |
| 380 | 394 | title="g1t's own spend" | |
| 381 | − | description="What g1t pays for itself, at cost: accounts on a 100% discount, the trial and open-source pools, free workspaces' overruns, and anything charged without real money behind it. Two caps hold it: each 100%-discount account's monthly budget, and a daily breaker on all of it that pauses new hosted-model agent runs g1t would pay for." | |
| 395 | + | description="What g1t pays for itself this calendar month, today included: accounts on a 100% discount, the trial and open-source pools, free workspaces' overruns, and usage charged without real money behind it. Each is what the work cost g1t, never its price, counted as each charge settled: the model providers' cost, and the price book's cost of sandboxes and builds. Two caps hold it: each 100%-discount account's monthly budget, and a daily breaker on all of it that pauses new hosted-model agent runs g1t would pay for." | |
| 382 | 396 | > | |
| 383 | 397 | <div className="grid gap-3 lg:grid-cols-2"> | |
| 384 | 398 | <CapMeter | |
| ⋯ | |||
| 462 | 476 | <tr className="border-b border-line"> | |
| 463 | 477 | <td className="px-4 py-2.5 sm:px-5"> | |
| 464 | 478 | Money in | |
| 465 | − | <span className="block text-xs text-faint">Usage paid for and the plan, reconciled through yesterday</span> | |
| 479 | + | <span className="block text-xs text-faint">Usage paid for with real money and the plan, as last reconciled</span> | |
| 466 | 480 | </td> | |
| 467 | 481 | <td className="tabular px-4 py-2.5 text-right sm:pr-5">{usd(caps.revenueMicros)}</td> | |
| 468 | 482 | </tr> | |
| ⋯ | |||
| 473 | 487 | </tbody> | |
| 474 | 488 | </table> | |
| 475 | 489 | </div> | |
| 490 | + | <p className="mt-3 text-xs text-muted"> | |
| 491 | + | Why this differs from the statement above: this is {caps.month} so far, the statement the last {range} days. The statement takes Cloudflare's bill as the | |
| 492 | + | cost of what Cloudflare runs, so sandboxes and builds inside Cloudflare's included usage cost nothing there, and here what the price book says they | |
| 493 | + | cost. Here are Cloudflare's subscriptions for the whole month, there for the range. | |
| 494 | + | {(caps.resetMicros ?? 0) > 0 && ( | |
| 495 | + | <> | |
| 496 | + | {" "} | |
| 497 | + | And {usd(caps.resetMicros ?? 0, { cents: true })} of this was spent on {(caps.resetWorkspaces ?? []).join(", ")} before a testing reset wiped | |
| 498 | + | {(caps.resetWorkspaces ?? []).length === 1 ? " its" : " their"} billing: still g1t's spend, but the statement has it only where the reset kept | |
| 499 | + | it, as given away. | |
| 500 | + | </> | |
| 501 | + | )} | |
| 502 | + | </p> | |
| 476 | 503 | </Section> | |
| 477 | 504 | ); | |
| 478 | 505 | } | |
| 3419 | 3419 | pub created_by: String, | |
| 3420 | 3420 | /// When the price book took it on; absent while it waits for its date. | |
| 3421 | 3421 | pub applied_at: Option<String>, | |
| 3422 | + | /// What `cost_micros` is: `cost`, what g1t pays for a unit (a | |
| 3423 | + | /// provider's dollar passed on at cost is one); `rate`, a price g1t | |
| 3424 | + | /// sets with no cost behind it (the agent rate, security activation), | |
| 3425 | + | /// so its cost column is its price; `weight`, a multiplier in | |
| 3426 | + | /// millionths, not money (the agent rate's token weights). | |
| 3427 | + | #[serde(default)] | |
| 3428 | + | pub basis: String, | |
| 3422 | 3429 | } | |
| 3423 | 3430 | ||
| 3424 | 3431 | /// What a workspace cost g1t over the range, Cloudflare's costs shared | |
| ⋯ | |||
| 3590 | 3597 | pub fixed_items: Vec<FixedCost>, | |
| 3591 | 3598 | /// Money in this month, through the last reconciled day. | |
| 3592 | 3599 | pub revenue_micros: i64, | |
| 3600 | + | /// Of this month's buckets, what was spent on workspaces whose billing | |
| 3601 | + | /// a testing reset later wiped: still g1t's spend, but no longer on | |
| 3602 | + | /// their ledger, so the reconciled figures have it only where the | |
| 3603 | + | /// reset kept it (as given away, testing resets). | |
| 3604 | + | #[serde(default)] | |
| 3605 | + | pub reset_micros: i64, | |
| 3606 | + | /// Those workspaces. | |
| 3607 | + | #[serde(default)] | |
| 3608 | + | pub reset_workspaces: Vec<String>, | |
| 3593 | 3609 | } | |
| 3594 | 3610 | ||
| 3595 | 3611 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 21 | 21 | | --- | --- | --- | | |
| 22 | 22 | | Billable usage, `GET /accounts/{account}/billable-usage?from=&to=` | One row per service per day in FOCUS columns: `ServiceFamilyName`, `ServiceName`, `ChargePeriodStart`, `PricingQuantity`, `ContractedCost` / `BilledCost` / `ListCost`. Every product g1t uses appears once it is used: Workers, Workers for Platforms, D1, KV, R2, Queues, Containers, Durable Objects, Artifacts, Browser Rendering, Workers AI, Vectorize, Cloudflare for SaaS, Email. Inside an included amount the cost is 0. | `cost_lines`, source `billable_usage` | | |
| 23 | 23 | | GraphQL `artifactsEventsAdaptiveGroups` | Artifacts' own count by `date`, `eventType` and `repositoryName`. Operations are `create`, `fork`, `push`, `pull`, `delete`; errors (`rateLimited`, `serverError`, …) are kept but not counted. | `cost_lines`, source `artifacts_events`; per workspace (from the store key `<workspace>--<repo>`; a pull request's working copy, `pulls--<id>`, is its repository's workspace's, from repos' `pull_owners`) in `own_counts` as `cloudflare_git` | | |
| 24 | − | | GraphQL `aiGatewayRequestsAdaptiveGroups`, filtered to `AI_GATEWAY_ID` | What AI Gateway priced g1t's own provider traffic at, by `date`, `provider`, `model` and `wholesale`: `count`, `sum.cost` (dollars), `sum.tokensIn`/`tokensOut`/`cacheReadTokens`/`cacheWriteTokens`. Field names checked against Cloudflare's schema (introspection of `AccountAiGatewayRequestsAdaptiveGroups{Sum,Dimensions,Filter_InputObject}`). An adaptive (sampled) dataset: an estimate, close at g1t's volumes. Only g1t's hosted models go through this gateway: a workspace's own provider is called at its own address, never here. | `cost_lines`, source `ai_gateway`, product `ai_gateway_requests`: per day and model a line `<provider>_<model>` (requests, at the gateway's cost), and at no cost `…__tokens`, `…__cache_read_tokens`, `…__cache_write_tokens`; Cloudflare-billed (unified billing) requests are prefixed `wholesale__`. Mapped to `models` (migration 0036). A re-read day replaces all its gateway lines. | | |
| 24 | + | | GraphQL `aiGatewayRequestsAdaptiveGroups`, filtered to `AI_GATEWAY_ID` | What AI Gateway priced g1t's own provider traffic at, by `date`, `provider` and `model`: `count`, `sum.cost` (dollars), `sum.tokensIn`/`tokensOut`/`cacheReadTokens`/`cacheWriteTokens`; asked twice in one query, filtered `wholesale: 0` and `wholesale: 1`. `wholesale` is never a dimension: grouped by it, Cloudflare answers no rows and no error (until 2026-10-08 that left the gateway's side empty while it had logged $11.11). Read over its own window: the last 31 days until it has answered with a line, then the last few. Field names checked against Cloudflare's schema (introspection of `AccountAiGatewayRequestsAdaptiveGroups{Sum,Dimensions,Filter_InputObject}`). An adaptive (sampled) dataset: an estimate, close at g1t's volumes. Only g1t's hosted models go through this gateway: a workspace's own provider is called at its own address, never here. | `cost_lines`, source `ai_gateway`, product `ai_gateway_requests`: per day and model a line `<provider>_<model>` (requests, at the gateway's cost), and at no cost `…__tokens`, `…__cache_read_tokens`, `…__cache_write_tokens`; Cloudflare-billed (unified billing) requests are prefixed `wholesale__`. Mapped to `models` (migration 0036). A re-read day replaces all its gateway lines. | | |
| 25 | 25 | | The ledger | Every charge: its cost at the price book's cost, what it was charged at price, what paid for it. | read, never written | | |
| 26 | 26 | | `pending_usage` | Month-end meters (git, storage, scans, embeddings, the cache) as they stand. | snapshotted daily into `pending_days` | | |
| 27 | 27 | | `plan_payments` | The plan's $20. | read | | |
| ⋯ | |||
| 40 | 40 | | Secret on g1t-billing | Permissions | Used for | | |
| 41 | 41 | | --- | --- | --- | | |
| 42 | 42 | | `CLOUDFLARE_BILLING_TOKEN` (optional) | Account: **Billing Read**, Account: **Account Analytics Read**, for the g1t account only | Reading the bill, the Artifacts events and the subscriptions | | |
| 43 | − | | `CLOUDFLARE_USAGE_TOKEN` (exists) | Billing Read, Account Analytics Read, AI Gateway Read | The keeper (settling runs from the gateway's logs); also the bill when `CLOUDFLARE_BILLING_TOKEN` is not set. AI Gateway's analytics are read with this token first and, if that is refused, with the bill's: Cloudflare answers a token without AI Gateway Read with no rows rather than an error, so the bill's token would read as a gateway that priced nothing | | |
| 43 | + | | `CLOUDFLARE_USAGE_TOKEN` (exists) | Billing Read, Account Analytics Read, AI Gateway Read | The keeper (settling runs from the gateway's logs); also the bill when `CLOUDFLARE_BILLING_TOKEN` is not set. AI Gateway's analytics are read with this token first and, if that is refused, with the bill's (a token without Account Analytics Read gets an error). When the answer has no rows, billing asks the REST API for the gateway with the same token: 403 means the token lacks AI Gateway Read, 404 that `AI_GATEWAY_ID` names no gateway, 200 that nothing went through it; the `models` drift says which | | |
| 44 | 44 | ||
| 45 | 45 | With neither, the daily run reconciles only what g1t counted itself, and | |
| 46 | 46 | the page says the bill cannot be read. Nothing fails. To set the scoped one: | |
| ⋯ | |||
| 98 | 98 | | `embeddings` | Workers AI, Vectorize | `context` | | |
| 99 | 99 | | `security` | (Workers CPU, under `platform`) | `security` | | |
| 100 | 100 | | `domains` | Cloudflare for SaaS | `domains` | | |
| 101 | − | | `models` | not Cloudflare: AI Gateway's settled cost on the ledger; AI Gateway's own daily total (`ai_gateway_requests`) beside it, to check it | every other task (agent runs) | | |
| 101 | + | | `models` | not Cloudflare: AI Gateway's settled cost on the ledger; AI Gateway's own daily total (`ai_gateway_requests`) beside it, to check it | every other task (agent runs). A planning run's ledger task is `plan`, the same as the plan's payments' key, so it is reconciled as `planning` (`margin::PLANNING_KEY`), which has no `revenue_map` row: models. Before 2026-10-08 its model cost landed in `platform`, where Cloudflare's bill is the cost, and was lost | | |
| 102 | 102 | | `platform` | Workers, D1, KV, Queues, Email, Browser Rendering, other Durable Objects | the plan's price | | |
| 103 | 103 | ||
| 104 | 104 | For each day and bucket: | |
| ⋯ | |||
| 152 | 152 | each why by name, testing resets included (`givenResetMicros`). | |
| 153 | 153 | - **Month-end meters**: a day's figure is that day's `pending_days` | |
| 154 | 154 | snapshot less the day before's, within a month. Their month-end ledger | |
| 155 | − | entries are left out, so nothing is counted twice. | |
| 156 | − | - **Product margin** = (value − cost) / value. | |
| 155 | + | entries are left out, so nothing is counted twice. On a 100%-discount | |
| 156 | + | workspace the snapshot's charge is valued and given (comped), never cash: | |
| 157 | + | the month's close takes all of it off (`margin::comped_meter`). | |
| 158 | + | - **Product margin** = (value − cost) / value: a share of the price, so | |
| 159 | + | cost plus 20% is a 16.7% margin. Sudo labels every margin "of price". | |
| 157 | 160 | - **Sudo's statement** keeps apart: | |
| 158 | 161 | - **Usage sold**: cash for usage against the cost of the usage buckets | |
| 159 | 162 | less what was given. Its margin is the headline; at cost plus 20% it | |
| ⋯ | |||
| 167 | 170 | - **Given away**: by why. A budget, watched under g1t's own spend, never | |
| 168 | 171 | shown as a loss. | |
| 169 | 172 | - **All in**: money in against all of it, with the figure without what | |
| 170 | − | was given beside it. **Who g1t paid** splits the cost into Cloudflare | |
| 171 | − | and the model providers. | |
| 173 | + | was given beside it. **Who g1t paid** is All in's cost, split into | |
| 174 | + | Cloudflare's usage, Cloudflare's subscriptions over the range, and the | |
| 175 | + | model providers. | |
| 172 | 176 | ||
| 173 | 177 | The overall alert is (Σ cash − (Σ cost − Σ given)) / Σ cash. | |
| 174 | 178 | - **Quantities**: where a mapping names an `own_meter`, Cloudflare's | |
| ⋯ | |||
| 190 | 194 | | --- | --- | --- | | |
| 191 | 195 | | Count | g1t's count and Cloudflare's differ by more than the mapping's `drift_percent` (10%) | Find out what Cloudflare counts: compare its events with `own_counts` `artifacts_*` and `cost_operations`. If it counts more (binding reads, `ls-refs`), either change repos' `operation_mapping` so customers are charged for what Cloudflare counts, or leave it and let the per-unit cost rise (below). | | |
| 192 | 196 | | Cost | Cloudflare charged more than `drift_percent` away from the price book's cost of the same usage, with at least `min_daily_cost` | A price is stale: check the proposals. | | |
| 193 | − | | Cost, on `models` | What AI Gateway priced g1t's own provider traffic at over the 7 days, against the ledger's model cost for the same days (billed to g1t: comped, free and trial use included, a workspace's own provider not) plus the model cost testing resets kept for those days (`reset_costs`), more than the `ai_gateway_requests` mapping's `drift_percent` (10%) apart, with at least `min_daily_cost`. A ledger with none of the gateway's cost is drift too, and so is a gateway that priced nothing against a ledger with at least `min_daily_cost` of model cost (no percentage): that is not agreement, it is a token that cannot see AI Gateway, or calls that went around it | The gateway higher: model calls g1t paid for and charged no one: runs not settled yet (they catch up within the hour), runs with no session, a run started without a billing ticket, or something else on g1t's gateway. The ledger higher: runs that reached a provider without the gateway. The detail adds why the gateway's own figure may be off: prompt-cache read and write tokens (the gateway prices them at its rates for cache tokens, which can lag the provider's; check against the provider's invoice), requests Cloudflare billed itself (unified billing: on Cloudflare's bill, not a provider's), and models with no price. A testing reset in the window is named in the detail: one that kept its cost says how much of the ledger's side it is; one from before resets kept their cost (the audit log has it, `reset_costs` does not) says the gateway's figure includes usage the ledger no longer has, so that part is not a leak, and the day it leaves the 7 days; while such a reset is in the window the `models` leak is not raised. Days are UTC by when a request ran (gateway) and when a charge was entered (ledger), so a run across midnight shifts a little between days; the 7-day sum absorbs it. | | |
| 197 | + | | Cost, on `models` | What AI Gateway priced g1t's own provider traffic at over the 7 days, against the ledger's model cost for the same days (billed to g1t: comped, free and trial use included, a workspace's own provider not) plus the model cost testing resets kept for those days (`reset_costs`), more than the `ai_gateway_requests` mapping's `drift_percent` (10%) apart, with at least `min_daily_cost`. A ledger with none of the gateway's cost is drift too, and so is a gateway that priced nothing against a ledger with at least `min_daily_cost` of model cost (no percentage): that is not agreement. Its detail says why as far as the run could tell: requests logged with no price (add the models' prices), a token Cloudflare refuses for the gateway (give it AI Gateway Read, or fix `AI_GATEWAY_ID`), a gateway the token sees with no requests (calls went around it), or a read that failed | The gateway higher: model calls g1t paid for and charged no one: runs not settled yet (they catch up within the hour), runs with no session, a run started without a billing ticket, or something else on g1t's gateway. The ledger higher: runs that reached a provider without the gateway. The detail adds why the gateway's own figure may be off: prompt-cache read and write tokens (the gateway prices them at its rates for cache tokens, which can lag the provider's; check against the provider's invoice), requests Cloudflare billed itself (unified billing: on Cloudflare's bill, not a provider's), and models with no price. A testing reset in the window is named in the detail: one that kept its cost says how much of the ledger's side it is; one from before resets kept their cost (the audit log has it, `reset_costs` does not) says the gateway's figure includes usage the ledger no longer has, so that part is not a leak, and the day it leaves the 7 days; while such a reset is in the window the `models` leak is not raised. Days are UTC by when a request ran (gateway) and when a charge was entered (ledger), so a run across midnight shifts a little between days; the 7-day sum absorbs it. | | |
| 194 | 198 | | Unpriced | Over the 7 days, a model in AI Gateway's analytics with tokens and $0 cost, or runs settled with `runs.gateway_note` (the gateway could not price all of a run) | The gateway has no price for a model g1t runs: add it in the gateway (custom cost) or route away from it. Until then those runs are charged no less than the sandbox reported (Claude Code's own price table), never $0 silently. | | |
| 195 | 199 | | Leak | Cost of at least `min_daily_cost` and nothing charged for it (never for `platform`), or a meter in `unmapped` | Map the meter (below), or decide it is overhead (`platform`). | | |
| 196 | 200 | ||
| ⋯ | |||
| 218 | 222 | once per rise (`price_notices`), and the pricing page lists it with | |
| 219 | 223 | "takes effect". Rises are never retroactive; margin protection is for | |
| 220 | 224 | new usage once notice has run. | |
| 225 | + | - A newer measurement before a rise's date replaces its version, with | |
| 226 | + | the new rise's own full notice; the proposal behind the replaced version | |
| 227 | + | is marked `superseded` (it never took effect, and nothing was charged at | |
| 228 | + | it). Sudo says "applied by guardrail, then replaced by a later | |
| 229 | + | measurement". Migration 0049 marks the ones from before: sandbox and | |
| 230 | + | build seconds' rises of 2026-10-07, replaced on 10-08. | |
| 221 | 231 | - Staff approve or reject in sudo. A rejection needs a note. | |
| 232 | + | - **Price versions** in sudo show each version's cost and price, except | |
| 233 | + | where the cost column is not a cost (`PriceVersion.basis`, | |
| 234 | + | `pricing::basis_of`): the agent rate and security activation are rates | |
| 235 | + | g1t sets (`rate`, no cost shown), and the agent rate's token weights are | |
| 236 | + | multipliers (`weight`, shown as ×0.1). Dates show with their time: a | |
| 237 | + | version from 00:00 UTC is the evening before in the Americas. | |
| 222 | 238 | ||
| 223 | 239 | ## Changing a mapping | |
| 224 | 240 | ||
| ⋯ | |||
| 798 | 814 | | `unpaid` | Charged, but with no real money behind it: Stripe's test key, or `FREE_WHILE_BUILDING` | | |
| 799 | 815 | ||
| 800 | 816 | The plan's included usage and on-demand charges count as revenue only | |
| 801 | − | with live payments; in test mode they are `unpaid`. A workspace's own | |
| 817 | + | with live payments; in test mode they are `unpaid`, at what the usage cost | |
| 818 | + | g1t, never what was charged. A workspace's own | |
| 802 | 819 | model provider costs g1t nothing and is not counted. Month-end meters | |
| 803 | 820 | (git, storage, scans, embeddings, the cache) are not counted here; the | |
| 804 | 821 | daily reconciliation covers them. Migration `0024_spend_caps.sql` | |
| 805 | 822 | backfills the current month from the ledger. | |
| 806 | 823 | ||
| 824 | + | **Why it differs from the statement.** The section is the calendar month | |
| 825 | + | so far, today included, counted as each charge settled: the model | |
| 826 | + | providers' cost and the price book's cost of sandboxes and builds. The | |
| 827 | + | statement is the range, reconciled against Cloudflare's bill, where | |
| 828 | + | sandboxes inside Cloudflare's included usage cost $0. The section shows | |
| 829 | + | Cloudflare's subscriptions for the whole month, the statement over the | |
| 830 | + | range. And `g1t_spend` is never wiped: spend on a workspace a testing reset | |
| 831 | + | wiped later stays here (sudo names it, `SpendCaps.reset_micros`), while the | |
| 832 | + | statement has it only where the reset kept it (`reset_costs`, given away as | |
| 833 | + | testing resets). syntaqx's $7.41 of 2026-10-02 to 10-05, reset on 10-07 | |
| 834 | + | before resets kept their cost, is that case. | |
| 835 | + | ||
| 807 | 836 | ### Caps | |
| 808 | 837 | ||
| 809 | 838 | | Cap | Variable (g1t-billing) | Default | At the cap | | |
| 1593 | 1593 | reason: string; | |
| 1594 | 1594 | createdBy: string; | |
| 1595 | 1595 | appliedAt: string | null; | |
| 1596 | + | /** | |
| 1597 | + | * What `costMicros` is: `cost`, what g1t pays for a unit; `rate`, a price | |
| 1598 | + | * g1t sets with no cost behind it (the agent rate), so it is no cost; | |
| 1599 | + | * `weight`, a multiplier in millionths, not money. Absent from older | |
| 1600 | + | * billing: read as `cost`. | |
| 1601 | + | */ | |
| 1602 | + | basis?: "cost" | "rate" | "weight" | ""; | |
| 1596 | 1603 | }; | |
| 1597 | 1604 | ||
| 1598 | 1605 | /** What `resetBilling` removed. */ | |
| ⋯ | |||
| 1698 | 1705 | fixedItems?: { name: string; monthlyMicros: number }[]; | |
| 1699 | 1706 | /** Money in this month, through the last reconciled day. */ | |
| 1700 | 1707 | revenueMicros: number; | |
| 1708 | + | /** | |
| 1709 | + | * Of this month's buckets, what was spent on workspaces whose billing a | |
| 1710 | + | * testing reset later wiped (still g1t's spend; the reconciled figures | |
| 1711 | + | * have it only where the reset kept it), and those workspaces. | |
| 1712 | + | */ | |
| 1713 | + | resetMicros?: number; | |
| 1714 | + | resetWorkspaces?: string[]; | |
| 1701 | 1715 | }; | |
| 1702 | 1716 | ||
| 1703 | 1717 | /** A comped account's monthly budget, at cost. */ | |
| 1 | + | -- A proposal the guardrail applied as a rise waits for its notice as a | |
| 2 | + | -- price version; a newer measurement before that date replaces the | |
| 3 | + | -- version (src/pricing.rs, schedule_version). The proposal behind the | |
| 4 | + | -- replaced version never took effect, but stayed "applied" with no date it | |
| 5 | + | -- is in force from: sandbox_second and build_second's rises of 2026-10-07 | |
| 6 | + | -- (v2, due 2026-10-21) were replaced by v3 (due 2026-10-22) on 2026-10-08. | |
| 7 | + | -- Nothing was charged at v2. From now on the replacement marks them | |
| 8 | + | -- superseded; these are the ones from before. | |
| 9 | + | UPDATE price_proposals SET status = 'superseded' | |
| 10 | + | WHERE status = 'applied' AND version_id IS NOT NULL | |
| 11 | + | AND version_id NOT IN (SELECT id FROM price_versions); |
| 503 | 503 | .await? | |
| 504 | 504 | .and_then(|s| s.micros) | |
| 505 | 505 | .unwrap_or(0); | |
| 506 | + | // What a testing reset wiped from the ledger is still here. | |
| 507 | + | #[derive(Deserialize)] | |
| 508 | + | struct Reset { | |
| 509 | + | account: String, | |
| 510 | + | micros: Option<i64>, | |
| 511 | + | } | |
| 512 | + | let reset = self | |
| 513 | + | .db | |
| 514 | + | .prepare(RESET_SPEND_SQL) | |
| 515 | + | .bind(&[month_start.as_str().into()])? | |
| 516 | + | .all() | |
| 517 | + | .await? | |
| 518 | + | .results::<Reset>()?; | |
| 519 | + | let reset_micros = reset.iter().filter_map(|r| r.micros).sum(); | |
| 520 | + | let reset_workspaces = reset.into_iter().map(|r| r.account.strip_prefix("ws_").unwrap_or(&r.account).to_owned()).collect(); | |
| 506 | 521 | let fixed = self.fixed_monthly(self.caps.fixed_monthly).await?; | |
| 507 | 522 | Ok(SpendCaps { | |
| 523 | + | reset_micros, | |
| 524 | + | reset_workspaces, | |
| 508 | 525 | day, | |
| 509 | 526 | month, | |
| 510 | 527 | today_micros, | |
| ⋯ | |||
| 550 | 567 | } | |
| 551 | 568 | } | |
| 552 | 569 | ||
| 570 | + | /// g1t's own spend since `?1` on accounts a testing reset wiped later than | |
| 571 | + | /// the day it was spent (`admin_actions`, action `reset`), by account. | |
| 572 | + | pub(crate) const RESET_SPEND_SQL: &str = "SELECT s.account, SUM(s.micros) AS micros FROM g1t_spend s | |
| 573 | + | WHERE s.day >= ?1 AND EXISTS (SELECT 1 FROM admin_actions a WHERE a.action = 'reset' AND a.account = s.account AND substr(a.created_at, 1, 10) >= s.day) | |
| 574 | + | GROUP BY s.account HAVING SUM(s.micros) > 0 ORDER BY s.account"; | |
| 575 | + | ||
| 553 | 576 | /// How sudo names a bucket of g1t's own spend. | |
| 554 | 577 | pub(crate) fn bucket_title(bucket: &str) -> &'static str { | |
| 555 | 578 | match bucket { | |
| ⋯ | |||
| 675 | 698 | } | |
| 676 | 699 | ||
| 677 | 700 | #[test] | |
| 701 | + | fn spend_a_testing_reset_wiped_is_found_by_the_reset_after_it() { | |
| 702 | + | // syntaqx's $7.41 of 2026-10-02 to 10-05, reset on 10-07: still | |
| 703 | + | // g1t's spend, gone from its ledger. | |
| 704 | + | assert_eq!(crate::rename::parameters(RESET_SPEND_SQL), 1); | |
| 705 | + | assert!(RESET_SPEND_SQL.contains("a.action = 'reset'") && RESET_SPEND_SQL.contains("substr(a.created_at, 1, 10) >= s.day")); | |
| 706 | + | } | |
| 707 | + | ||
| 708 | + | #[test] | |
| 678 | 709 | fn amounts_read_in_cents() { | |
| 679 | 710 | assert_eq!(cents(150_000_000), "$150.00"); | |
| 680 | 711 | assert_eq!(cents(1_234_567), "$1.23"); | |
| 62 | 62 | /// percent or two at a time, and an email for each would be noise. | |
| 63 | 63 | const EMAIL_RISE_PERCENT: f64 = 5.0; | |
| 64 | 64 | ||
| 65 | + | /// What a meter's `cost_micros` is (`PriceVersion::basis`): a weight of the | |
| 66 | + | /// agent rate's tokens is a multiplier; the agent rate and security | |
| 67 | + | /// activation are prices g1t sets, with no per-unit cost behind them (the | |
| 68 | + | /// models a run uses are charged apart, at cost); everything else is a | |
| 69 | + | /// cost g1t pays. | |
| 70 | + | pub(crate) fn basis_of(meter: &str) -> &'static str { | |
| 71 | + | if meter.starts_with("agent_token_weight_") { | |
| 72 | + | "weight" | |
| 73 | + | } else if matches!(meter, "agent_tokens" | "agent_tokens_own") || meter == crate::features::SECURITY_METER { | |
| 74 | + | "rate" | |
| 75 | + | } else { | |
| 76 | + | "cost" | |
| 77 | + | } | |
| 78 | + | } | |
| 79 | + | ||
| 80 | + | /// Marks the proposals behind a meter's versions still waiting for their | |
| 81 | + | /// date as superseded, before a newer version replaces them; who decided | |
| 82 | + | /// it, and when, stay. Parameter: the meter. | |
| 83 | + | pub(crate) const SUPERSEDE_WAITING_SQL: &str = "UPDATE price_proposals SET status = 'superseded' | |
| 84 | + | WHERE version_id IN (SELECT id FROM price_versions WHERE meter = ?1 AND applied_at IS NULL)"; | |
| 85 | + | ||
| 65 | 86 | /// Owners told of rises per run, at most; the rest on the next run. | |
| 66 | 87 | const NOTICES_PER_RUN: usize = 40; | |
| 67 | 88 | ||
| ⋯ | |||
| 403 | 424 | + 1; | |
| 404 | 425 | let id = format!("pv_{meter}_{next}"); | |
| 405 | 426 | let now = rfc3339(now_ms()); | |
| 406 | − | // A newer decision replaces a rise still waiting for its date. | |
| 427 | + | // A newer decision replaces a rise still waiting for its date. The | |
| 428 | + | // proposal behind it never took effect: superseded, not "applied" | |
| 429 | + | // with no date it is in force from. | |
| 407 | 430 | self.db | |
| 408 | − | .prepare("DELETE FROM price_versions WHERE meter = ? AND applied_at IS NULL") | |
| 409 | − | .bind(&[meter.into()])? | |
| 410 | − | .run() | |
| 431 | + | .batch(vec![ | |
| 432 | + | self.db.prepare(SUPERSEDE_WAITING_SQL).bind(&[meter.into()])?, | |
| 433 | + | self.db.prepare("DELETE FROM price_versions WHERE meter = ? AND applied_at IS NULL").bind(&[meter.into()])?, | |
| 434 | + | ]) | |
| 411 | 435 | .await?; | |
| 412 | 436 | self.db | |
| 413 | 437 | .prepare( | |
| ⋯ | |||
| 524 | 548 | .into_iter() | |
| 525 | 549 | .map(|v| PriceVersion { | |
| 526 | 550 | price_micros: Price::price_for(v.cost_micros, v.markup_percent), | |
| 551 | + | basis: basis_of(&v.meter).to_owned(), | |
| 527 | 552 | id: v.id, | |
| 528 | 553 | meter: v.meter, | |
| 529 | 554 | version: v.version, | |
| ⋯ | |||
| 757 | 782 | } | |
| 758 | 783 | ||
| 759 | 784 | #[test] | |
| 785 | + | fn a_versions_cost_says_what_it_is() { | |
| 786 | + | // The agent rate's $0.25 a million tokens is g1t's price, not what | |
| 787 | + | // tokens cost g1t; the weights are multipliers. | |
| 788 | + | assert_eq!(basis_of("agent_tokens"), "rate"); | |
| 789 | + | assert_eq!(basis_of("agent_tokens_own"), "rate"); | |
| 790 | + | assert_eq!(basis_of("security_activation"), "rate"); | |
| 791 | + | assert_eq!(basis_of("agent_token_weight_cache_read"), "weight"); | |
| 792 | + | // A provider's dollar passed on at cost, and Cloudflare's units, are costs. | |
| 793 | + | for meter in ["agent_models", "gateway_models", "sandbox_second", "git_operations", "card_fee_percent"] { | |
| 794 | + | assert_eq!(basis_of(meter), "cost", "{meter}"); | |
| 795 | + | } | |
| 796 | + | } | |
| 797 | + | ||
| 798 | + | #[test] | |
| 799 | + | fn a_rise_replaced_before_its_date_is_superseded_not_applied() { | |
| 800 | + | // 2026-10-07's sandbox rise (v2, due 10-21) was replaced by | |
| 801 | + | // 10-08's (v3, due 10-22): v2's proposal never took effect. | |
| 802 | + | let sql = SUPERSEDE_WAITING_SQL; | |
| 803 | + | assert_eq!(crate::rename::parameters(sql), 1); | |
| 804 | + | assert!(sql.contains("status = 'superseded'") && sql.contains("applied_at IS NULL") && !sql.contains("decided_"), "{sql}"); | |
| 805 | + | // A rise still waits its notice: the replacement is no shortcut. | |
| 806 | + | let Decision::Auto { effective_ms } = decide(18.1817, 19.5181, guard(), now(), false) else { panic!() }; | |
| 807 | + | assert_eq!(rfc3339(effective_ms), "2026-10-20T04:17:00.000Z"); | |
| 808 | + | } | |
| 809 | + | ||
| 810 | + | #[test] | |
| 760 | 811 | fn past_the_guardrail_or_with_auto_off_staff_decide() { | |
| 761 | 812 | // Up 50%: staff. | |
| 762 | 813 | assert_eq!(decide(150_000.0, 225_000.0, guard(), now(), true), Decision::Approve { suspect: false }); | |