Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| The Packages page names every registry it has | 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; | |
| 3 | ||
| 4 | import { | |
| 5 | type ComputeEntitlements, | |
| 6 | type ComputeKind, | |
| 7 | type PlanMemory, | |
| 8 | ComputeGate, | |
| 9 | actualMicros, | |
| 10 | alwaysPasses, | |
| Merge branch 'worktree-agent-a985c01f43538af01' | 11 | entitlementsKeptSeconds, |
| The Packages page names every registry it has | 12 | localRefusal, |
| 13 | onBillingError, | |
| 14 | readEntitlements, | |
| 15 | refusalCode, | |
| 16 | refusalMessage, | |
| 17 | sandboxEstimateMicros, | |
| 18 | } from "../../../packages/contracts/src/compute.ts"; | |
| Merge branch 'main' into actions-toolkit-oidc-artifacts | 19 | import { BUILD_HOSTS, buildHosts, dockerFor, withPlanLimits } from "./egress.ts"; |
| The Packages page names every registry it has | 20 | |
| 21 | const repo = { namespace: "acme", name: "web" }; | |
| 22 | ||
| 23 | function ent(change: Partial<ComputeEntitlements> = {}): ComputeEntitlements { | |
| 24 | return { | |
| 25 | plan: "paid", | |
| 26 | compute: true, | |
| 27 | trialMicrosLeft: 0, | |
| 28 | trialVerified: true, | |
| 29 | firstMonth: false, | |
| 30 | maxRunMinutes: 0, | |
| 31 | runCapMicros: 2_000_000, | |
| 32 | ceilingMicros: 100_000_000, | |
| 33 | exposureMicros: 0, | |
| 34 | paused: null, | |
| 35 | ...change, | |
| 36 | }; | |
| 37 | } | |
| 38 | ||
| 39 | /** A billing service that answers as told, and records what it was asked. */ | |
| 40 | function billing(answers: { | |
| 41 | entitlements?: unknown; | |
| 42 | reserve?: unknown; | |
| 43 | fail?: Set<string>; | |
| 44 | }) { | |
| 45 | const asked: { method: string; body: Record<string, unknown> }[] = []; | |
| 46 | const binding = { | |
| 47 | async fetch(url: string, init?: RequestInit): Promise<Response> { | |
| 48 | const method = url.split("/rpc/")[1]; | |
| 49 | asked.push({ method, body: JSON.parse(String(init?.body ?? "{}")) }); | |
| 50 | if (answers.fail?.has(method)) return new Response("down", { status: 503 }); | |
| 51 | if (method === "entitlements") return Response.json(answers.entitlements ?? null); | |
| 52 | if (method === "reserve") return Response.json(answers.reserve ?? { ok: true, value: { id: "rsv_1", paidBy: "credit" } }); | |
| 53 | if (method === "settle") return Response.json({ ok: true, value: true }); | |
| 54 | if (method === "prices") return Response.json({ prices: [{ meter: "sandbox_second", costMicros: 20 }] }); | |
| 55 | return new Response("not found", { status: 404 }); | |
| 56 | }, | |
| 57 | }; | |
| 58 | return { binding, asked }; | |
| 59 | } | |
| 60 | ||
| 61 | /** A plan memory that remembers nothing unless told. */ | |
| 62 | function memory(plan: ComputeEntitlements["plan"] | null = null): PlanMemory { | |
| 63 | return { get: async () => plan, put: async () => undefined }; | |
| 64 | } | |
| 65 | ||
| 66 | const quiet = () => undefined; | |
| 67 | ||
| 68 | function wire(e: ComputeEntitlements) { | |
| 69 | // As billing serialises it: camelCase. | |
| 70 | return { workspace: "acme", ...e }; | |
| 71 | } | |
| 72 | ||
| 73 | const request = (kind: ComputeKind, isPublic = false) => ({ | |
| 74 | workspace: "acme", | |
| 75 | repo, | |
| 76 | public: isPublic, | |
| 77 | kind, | |
| 78 | estimateMicros: 100_000, | |
| 79 | }); | |
| 80 | ||
| 81 | // ---- Gating decisions --------------------------------------------------------------- | |
| 82 | ||
| 83 | test("a paid workspace's start is reserved and goes ahead", async () => { | |
| 84 | const { binding, asked } = billing({ entitlements: wire(ent()) }); | |
| The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens. | 85 | const admitted = await new ComputeGate(binding, memory(), quiet).admit(request("deploy")); |
| The Packages page names every registry it has | 86 | assert.equal(admitted.ok, true); |
| 87 | assert.deepEqual(admitted.ok && admitted.reservation, { id: "rsv_1", paidBy: "credit" }); | |
| 88 | const reserve = asked.find((call) => call.method === "reserve")!; | |
| The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens. | 89 | assert.deepEqual(reserve.body, { workspace: "acme", repo, public: false, kind: "deploy", estimateMicros: 100_000 }); |
| The Packages page names every registry it has | 90 | }); |
| 91 | ||
| 92 | test("billing's refusal is shown in its own words", async () => { | |
| 93 | for (const code of ["not_paid", "trial_used", "limit", "oss_pool_empty"] as const) { | |
| 94 | const { binding } = billing({ | |
| 95 | entitlements: wire(ent({ plan: "free", compute: false })), | |
| 96 | reserve: { ok: false, error: { code, message: `Refused: ${code}. /acme/-/billing` } }, | |
| 97 | }); | |
| 98 | const admitted = await new ComputeGate(binding, memory(), quiet).admit(request("check", true)); | |
| 99 | assert.equal(admitted.ok, false, code); | |
| 100 | assert.equal(!admitted.ok && admitted.code, code); | |
| 101 | assert.equal(!admitted.ok && admitted.message, `Refused: ${code}. /acme/-/billing`); | |
| 102 | } | |
| 103 | }); | |
| 104 | ||
| 105 | test("a refusal without words gets g1t's, with what to do and where", async () => { | |
| 106 | const { binding } = billing({ | |
| 107 | entitlements: wire(ent({ plan: "free", compute: false })), | |
| 108 | reserve: { ok: false, error: { code: "not_paid", message: "" } }, | |
| 109 | }); | |
| The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens. | 110 | const admitted = await new ComputeGate(binding, memory(), quiet).admit(request("deploy")); |
| 111 | assert.equal(!admitted.ok && admitted.message, refusalMessage("not_paid", "acme", "deploy")); | |
| 112 | assert.match(refusalMessage("not_paid", "acme", "deploy"), /^Deployments need a paid workspace\. Start the \$20 plan or try it with \$5 of free usage after a card check: \/acme\/-\/billing$/); | |
| The Packages page names every registry it has | 113 | }); |
| 114 | ||
| 115 | test("a paused workspace starts nothing, and billing is not asked to reserve", async () => { | |
| 116 | const { binding, asked } = billing({ entitlements: wire(ent({ paused: "A spend spike is waiting for an owner." })) }); | |
| 117 | const admitted = await new ComputeGate(binding, memory(), quiet).admit(request("workflow")); | |
| 118 | assert.equal(!admitted.ok && admitted.code, "paused"); | |
| 119 | assert.match(!admitted.ok ? admitted.message : "", /A spend spike is waiting for an owner/); | |
| 120 | assert.equal(asked.some((call) => call.method === "reserve"), false); | |
| 121 | }); | |
| 122 | ||
| Merge branch 'worktree-agent-a985c01f43538af01' | 123 | test("an answer that holds compute back is kept seconds, one that lets it through half a minute", () => { |
| 124 | assert.equal(entitlementsKeptSeconds(ent()), 30); | |
| 125 | assert.equal(entitlementsKeptSeconds(ent({ paused: "A spend spike is waiting for an owner." })), 3); | |
| 126 | assert.equal(entitlementsKeptSeconds(ent({ plan: "free", compute: false })), 3); | |
| 127 | // A free workspace with compute (its trial) runs, so it is kept as long. | |
| 128 | assert.equal(entitlementsKeptSeconds(ent({ plan: "free", compute: true })), 30); | |
| 129 | }); | |
| 130 | ||
| The Packages page names every registry it has | 131 | test("internal and enterprise plans pass even when billing refuses", async () => { |
| 132 | for (const plan of ["internal", "enterprise"] as const) { | |
| 133 | const { binding } = billing({ | |
| 134 | entitlements: wire(ent({ plan })), | |
| 135 | reserve: { ok: false, error: { code: "limit", message: "Over." } }, | |
| 136 | }); | |
| The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens. | 137 | const admitted = await new ComputeGate(binding, memory(), quiet).admit(request("deploy")); |
| The Packages page names every registry it has | 138 | assert.equal(admitted.ok, true, plan); |
| 139 | } | |
| 140 | assert.equal(alwaysPasses("internal") && alwaysPasses("enterprise") && !alwaysPasses("paid"), true); | |
| 141 | }); | |
| 142 | ||
| 143 | test("a pause from billing holds for every plan: g1t's own budget and its daily breaker", async () => { | |
| 144 | for (const plan of ["internal", "enterprise", "paid"] as const) { | |
| The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens. | 145 | const message = "flagon-io's monthly budget is used up ($150.00 of $150.00 this month at cost), so new runs wait."; |
| 146 | const { binding } = billing({ entitlements: wire(ent({ plan })), reserve: { ok: false, error: { code: "paused", message } } }); | |
| 147 | const admitted = await new ComputeGate(binding, memory(), quiet).admit(request("workflow")); | |
| The Packages page names every registry it has | 148 | assert.equal(!admitted.ok && admitted.code, "paused", plan); |
| 149 | assert.equal(!admitted.ok && admitted.message, message); | |
| 150 | } | |
| 151 | // Billing's pause reason reads cleanly inside g1t's sentence. | |
| 152 | assert.equal( | |
| The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens. | 153 | refusalMessage("paused", "acme", "workflow", "Its budget is used up."), |
| The Packages page names every registry it has | 154 | "g1t paused compute for this workspace: Its budget is used up. Contact support@g1t.sh to have it looked at.", |
| 155 | ); | |
| 156 | }); | |
| 157 | ||
| 158 | test("billing down: free workspaces fail closed, paying ones go on", async () => { | |
| 159 | const down = new Set(["entitlements", "reserve"]); | |
| 160 | // Nothing known about the workspace: treated as free. | |
| 161 | let gate = new ComputeGate(billing({ fail: down }).binding, memory(null), quiet); | |
| The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens. | 162 | let admitted = await gate.admit(request("deploy")); |
| The Packages page names every registry it has | 163 | assert.equal(!admitted.ok && admitted.code, "billing_unavailable"); |
| 164 | // Last seen paid: goes ahead, without a reservation. | |
| 165 | gate = new ComputeGate(billing({ fail: down }).binding, memory("paid"), quiet); | |
| The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens. | 166 | admitted = await gate.admit(request("deploy")); |
| The Packages page names every registry it has | 167 | assert.deepEqual(admitted, { ok: true, reservation: null, entitlements: null }); |
| 168 | // Entitlements answer, reserve errors: the plan decides. | |
| 169 | gate = new ComputeGate(billing({ entitlements: wire(ent({ plan: "free", compute: false, trialMicrosLeft: 5_000_000 })), fail: new Set(["reserve"]) }).binding, memory(), quiet); | |
| The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens. | 170 | admitted = await gate.admit(request("deploy")); |
| The Packages page names every registry it has | 171 | assert.equal(admitted.ok, false); |
| 172 | gate = new ComputeGate(billing({ entitlements: wire(ent()), fail: new Set(["reserve"]) }).binding, memory(), quiet); | |
| The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens. | 173 | assert.equal((await gate.admit(request("deploy"))).ok, true); |
| The Packages page names every registry it has | 174 | assert.equal(onBillingError("free"), "refuse"); |
| 175 | assert.equal(onBillingError(null), "refuse"); | |
| 176 | assert.equal(onBillingError("paid"), "allow"); | |
| 177 | }); | |
| 178 | ||
| 179 | test("an answer that is not a refusal counts as billing being down", async () => { | |
| 180 | const { binding } = billing({ | |
| 181 | entitlements: wire(ent({ plan: "free", compute: false })), | |
| 182 | reserve: { ok: false, error: { code: "invalid", message: "Unknown method" } }, | |
| 183 | }); | |
| The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens. | 184 | const admitted = await new ComputeGate(binding, memory(), quiet).admit(request("deploy")); |
| The Packages page names every registry it has | 185 | assert.equal(!admitted.ok && admitted.code, "billing_unavailable"); |
| 186 | assert.equal(refusalCode({ code: "payment_required" }), "not_paid"); | |
| 187 | assert.equal(refusalCode({ code: "conflict", reason: "trial_used" }), "trial_used"); | |
| 188 | assert.equal(refusalCode({ code: "invalid" }), null); | |
| 189 | }); | |
| 190 | ||
| 191 | test("entitlements read in either case, and not at all without a plan", () => { | |
| The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens. | 192 | const snake = readEntitlements({ plan: "free", trial_micros_left: 5, trial_verified: true, max_run_minutes: 30, paused: "" }); |
| The Packages page names every registry it has | 193 | assert.equal(snake?.trialMicrosLeft, 5); |
| The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens. | 194 | assert.equal(snake?.maxRunMinutes, 30); |
| The Packages page names every registry it has | 195 | assert.equal(snake?.paused, null); |
| 196 | assert.equal(readEntitlements({ team: true })?.plan, undefined); | |
| 197 | assert.equal(readEntitlements({ ok: true, value: { plan: "paid" } })?.plan, "paid"); | |
| 198 | }); | |
| 199 | ||
| 200 | test("before they try: what a free workspace can start, by kind and repository", () => { | |
| 201 | const free = ent({ plan: "free", compute: false, trialVerified: false }); | |
| The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens. | 202 | assert.equal(localRefusal(free, "deploy", false), "not_paid"); |
| The Packages page names every registry it has | 203 | assert.equal(localRefusal(free, "check", true), "not_paid", "the pool needs a card check"); |
| 204 | const verified = ent({ plan: "free", compute: false, trialVerified: true, trialMicrosLeft: 0 }); | |
| 205 | // The open-source path: public repositories' checks, workflows and queue. | |
| 206 | for (const kind of ["check", "workflow", "queue"] as const) assert.equal(localRefusal(verified, kind, true), null, kind); | |
| The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens. | 207 | assert.equal(localRefusal(verified, "deploy", true), "trial_used"); |
| The Packages page names every registry it has | 208 | assert.equal(localRefusal(verified, "check", false), "trial_used"); |
| 209 | const trial = ent({ plan: "free", compute: true, trialVerified: true, trialMicrosLeft: 4_000_000 }); | |
| The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens. | 210 | assert.equal(localRefusal(trial, "deploy", false), null); |
| The Packages page names every registry it has | 211 | assert.equal(localRefusal(ent(), "deploy", false), null); |
| The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens. | 212 | assert.equal(localRefusal(ent({ paused: "Held." }), "deploy", false), "paused"); |
| The Packages page names every registry it has | 213 | }); |
| 214 | ||
| 215 | // ---- Estimates ------------------------------------------------------------------------ | |
| 216 | ||
| The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens. | 217 | test("an estimate is the sandbox for its whole time cap", () => { |
| The Packages page names every registry it has | 218 | assert.equal(sandboxEstimateMicros(60, 25), 90_000); |
| 219 | assert.equal(sandboxEstimateMicros(-5, 25), 0); | |
| 220 | }); | |
| 221 | ||
| The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens. | 222 | test("what work cost: its seconds at the sandbox price", () => { |
| The Packages page names every registry it has | 223 | assert.equal(actualMicros(600, 20), 12_000); |
| The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens. | 224 | assert.equal(actualMicros(-10, 20), 0); |
| The Packages page names every registry it has | 225 | }); |
| 226 | ||
| 227 | test("the gate reads the sandbox price from the price book", async () => { | |
| 228 | const gate = new ComputeGate(billing({}).binding, memory(), quiet); | |
| 229 | assert.equal(await gate.microsPerSecond(), 20); | |
| 230 | const down = new ComputeGate(billing({ fail: new Set(["prices"]) }).binding, memory(), quiet); | |
| Every sandbox on g1t's runners has 20 GB of disk, the most Cloudflare Containers give one, as much or more than a GitHub-hosted ubuntu-latest's 14 GB: the standard machine (ubuntu-latest and every other hosted label, and all other work) becomes a custom instance type of 1 vCPU, 4 GiB and 20 GB, where standard-1 had half a vCPU and 8 GB, and g1t-2core a custom 2 vCPU, 8 GiB and 20 GB, where standard-3 had 16 GB; g1t-4core stays standard-4, which already had 20 GB. 1 vCPU is the smallest a custom type may have, and Cloudflare bills CPU only as it is used, so the doubled vCPU costs only what a job uses of it. The contracts' instance table, its TypeScript mirror and the billing keeper's standard sandbox say the same sizes; the price scales, worked out as before from Cloudflare's list prices with every vCPU busy and the Durable Object behind each, are now 1.9 for g1t-2core and 3.4 for g1t-4core, against the larger standard machine, and the fallbacks for a sandbox second (33 millionths of a dollar with its vCPU busy) and a build second (16) follow it. A test holds wrangler.jsonc's sizes to the contracts', and another the scales to the list prices. The docs' machine tables, the larger machines' prices and the Cloudflare deploy guide say so. | 231 | assert.equal(await down.microsPerSecond(), 33); |
| The Packages page names every registry it has | 232 | }); |
| 233 | ||
| 234 | // ---- Caps ------------------------------------------------------------------------------- | |
| 235 | ||
| 236 | test("caps are the lower of the guardrails' and the plan's", () => { | |
| 237 | const guard = { policy: { budgetUsd: 5 } as never, minutes: 90 }; | |
| 238 | const limited = withPlanLimits(guard as never, { minutes: 60, budgetUsd: 2 }); | |
| 239 | assert.equal(limited.minutes, 60); | |
| 240 | assert.equal((limited.policy as { budgetUsd: number }).budgetUsd, 2); | |
| 241 | const unlimited = withPlanLimits(guard as never, { minutes: null, budgetUsd: null }); | |
| 242 | assert.equal(unlimited.minutes, 90); | |
| 243 | assert.equal((unlimited.policy as { budgetUsd: number }).budgetUsd, 5); | |
| 244 | }); | |
| 245 | ||
| 246 | // ---- Builds' network --------------------------------------------------------------------- | |
| 247 | ||
| 248 | test("builds reach registries and git hosts, and no mining pool", () => { | |
| 249 | for (const host of ["registry.npmjs.org", "codeload.github.com", "nodejs.org", "crates.io"]) { | |
| 250 | assert.ok(BUILD_HOSTS.includes(host), host); | |
| 251 | } | |
| Merge branch 'main' into actions-toolkit-oidc-artifacts | 252 | // A job's Docker Engine pulls from Docker Hub (through its mirror) and Quay. |
| 253 | for (const host of ["registry-1.docker.io", "auth.docker.io", "production.cloudflare.docker.com", "mirror.gcr.io", "ghcr.io", "quay.io"]) { | |
| 254 | assert.ok(BUILD_HOSTS.includes(host), host); | |
| 255 | } | |
| The Packages page names every registry it has | 256 | assert.ok(buildHosts("deploy").includes("api.cloudflare.com")); |
| Merge GitHub pull request #8 (claude/cool-newton-7nm8r9): Docker deployments on g1t.page behind a switch that stays off until Cloudflare's container APIs are checked live, workflows a workspace manages in its repositories, tarball and zipball downloads, refs/pull/* kept to g1t alone, framework detection for g1t.page builds, and a workflow's event payload matching GitHub's. | 257 | // A container build pushes its image to Cloudflare's registry, from a sandbox or a job. |
| 258 | assert.ok(buildHosts("deploy").includes("registry.cloudflare.com")); | |
| 259 | assert.ok(buildHosts("actions").includes("registry.cloudflare.com")); | |
| The Packages page names every registry it has | 260 | assert.ok(!buildHosts("actions").includes("api.cloudflare.com")); |
| 261 | for (const host of buildHosts("deploy")) { | |
| 262 | assert.doesNotMatch(host, /pool|xmr|monero|nicehash|^\*/, host); | |
| 263 | } | |
| 264 | }); | |
| 265 | ||
| Merge branch 'main' into actions-toolkit-oidc-artifacts | 266 | test("workflow jobs get Docker unless the operator turns it off", () => { |
| 267 | assert.equal(dockerFor(undefined), "on"); | |
| 268 | assert.equal(dockerFor("on"), "on"); | |
| 269 | assert.equal(dockerFor(" OFF "), "off"); | |
| The Packages page names every registry it has | 270 | }); |
This file's history is long; its oldest lines are credited to the oldest commit read.