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.
| Merge branch 'worktree-agent-a6a121745e81f639f' | 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, |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 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"; |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 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")); |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 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 }); |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 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$/); | |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 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 | ||
| Merge branch 'worktree-agent-a6a121745e81f639f' | 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")); |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 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")); | |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 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."), |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 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")); |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 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")); |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 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")); |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 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); |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 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")); |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 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: "" }); |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 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); |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 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"); |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 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"); |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 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); |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 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"); |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 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", () => { |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 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", () => { |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 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); |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 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); | |
| 231 | assert.equal(await down.microsPerSecond(), 25); | |
| 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 | } | |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 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")); | |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 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"); | |
| Merge branch 'worktree-agent-a6a121745e81f639f' | 270 | }); |
This file's history is long; its oldest lines are credited to the oldest commit read.