Skip to content

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

g1t.sh
270 linesCodeBlameRaw

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 docs' Copy page menu keeps its own spacing, with an icon per item1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import {
5 type ComputeEntitlements,
6 type ComputeKind,
7 type PlanMemory,
8 ComputeGate,
9 actualMicros,
10 alwaysPasses,
Merge branch 'worktree-agent-a985c01f43538af01'11 entitlementsKeptSeconds,
The docs' Copy page menu keeps its own spacing, with an icon per item12 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-artifacts19import { BUILD_HOSTS, buildHosts, dockerFor, withPlanLimits } from "./egress.ts";
The docs' Copy page menu keeps its own spacing, with an icon per item20
21const repo = { namespace: "acme", name: "web" };
22
23function 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. */
40function 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. */
62function memory(plan: ComputeEntitlements["plan"] | null = null): PlanMemory {
63 return { get: async () => plan, put: async () => undefined };
64}
65
66const quiet = () => undefined;
67
68function wire(e: ComputeEntitlements) {
69 // As billing serialises it: camelCase.
70 return { workspace: "acme", ...e };
71}
72
73const 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
83test("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 docs' Copy page menu keeps its own spacing, with an icon per item86 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 docs' Copy page menu keeps its own spacing, with an icon per item90});
91
92test("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
105test("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 docs' Copy page menu keeps its own spacing, with an icon per item113});
114
115test("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'123test("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 docs' Copy page menu keeps its own spacing, with an icon per item131test("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 docs' Copy page menu keeps its own spacing, with an icon per item138 assert.equal(admitted.ok, true, plan);
139 }
140 assert.equal(alwaysPasses("internal") && alwaysPasses("enterprise") && !alwaysPasses("paid"), true);
141});
142
143test("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 docs' Copy page menu keeps its own spacing, with an icon per item148 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 docs' Copy page menu keeps its own spacing, with an icon per item154 "g1t paused compute for this workspace: Its budget is used up. Contact support@g1t.sh to have it looked at.",
155 );
156});
157
158test("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 docs' Copy page menu keeps its own spacing, with an icon per item163 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 docs' Copy page menu keeps its own spacing, with an icon per item167 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 docs' Copy page menu keeps its own spacing, with an icon per item171 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 docs' Copy page menu keeps its own spacing, with an icon per item174 assert.equal(onBillingError("free"), "refuse");
175 assert.equal(onBillingError(null), "refuse");
176 assert.equal(onBillingError("paid"), "allow");
177});
178
179test("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 docs' Copy page menu keeps its own spacing, with an icon per item185 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
191test("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 docs' Copy page menu keeps its own spacing, with an icon per item193 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 docs' Copy page menu keeps its own spacing, with an icon per item195 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
200test("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 docs' Copy page menu keeps its own spacing, with an icon per item203 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 docs' Copy page menu keeps its own spacing, with an icon per item208 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 docs' Copy page menu keeps its own spacing, with an icon per item211 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 docs' Copy page menu keeps its own spacing, with an icon per item213});
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.217test("an estimate is the sandbox for its whole time cap", () => {
The docs' Copy page menu keeps its own spacing, with an icon per item218 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.222test("what work cost: its seconds at the sandbox price", () => {
The docs' Copy page menu keeps its own spacing, with an icon per item223 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 docs' Copy page menu keeps its own spacing, with an icon per item225});
226
227test("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
236test("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
248test("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-artifacts252 // 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 docs' Copy page menu keeps its own spacing, with an icon per item256 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 docs' Copy page menu keeps its own spacing, with an icon per item260 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-artifacts266test("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 docs' Copy page menu keeps its own spacing, with an icon per item270});

This file's history is long; its oldest lines are credited to the oldest commit read.