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
258 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.

Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP1import 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,
11 entitlementsKeptSeconds,
12 localRefusal,
13 onBillingError,
14 readEntitlements,
15 refusalCode,
16 refusalMessage,
17 sandboxEstimateMicros,
18} from "../../../packages/contracts/src/compute.ts";
Merge branch 'main' into ask-integration19import { dockerFor, throughWorker, withPlanLimits } from "./egress.ts";
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP20
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 ceilingMicros: 100_000_000,
32 exposureMicros: 0,
33 paused: null,
34 ...change,
35 };
36}
37
38/** A billing service that answers as told, and records what it was asked. */
39function billing(answers: {
40 entitlements?: unknown;
41 reserve?: unknown;
42 fail?: Set<string>;
43}) {
44 const asked: { method: string; body: Record<string, unknown> }[] = [];
45 const binding = {
46 async fetch(url: string, init?: RequestInit): Promise<Response> {
47 const method = url.split("/rpc/")[1];
48 asked.push({ method, body: JSON.parse(String(init?.body ?? "{}")) });
49 if (answers.fail?.has(method)) return new Response("down", { status: 503 });
50 if (method === "entitlements") return Response.json(answers.entitlements ?? null);
51 if (method === "reserve") return Response.json(answers.reserve ?? { ok: true, value: { id: "rsv_1", paidBy: "credit" } });
52 if (method === "settle") return Response.json({ ok: true, value: true });
53 if (method === "prices") return Response.json({ prices: [{ meter: "sandbox_second", costMicros: 20 }] });
54 return new Response("not found", { status: 404 });
55 },
56 };
57 return { binding, asked };
58}
59
60/** A plan memory that remembers nothing unless told. */
61function memory(plan: ComputeEntitlements["plan"] | null = null): PlanMemory {
62 return { get: async () => plan, put: async () => undefined };
63}
64
65const quiet = () => undefined;
66
67function wire(e: ComputeEntitlements) {
68 // As billing serialises it: camelCase.
69 return { workspace: "acme", ...e };
70}
71
72const request = (kind: ComputeKind, isPublic = false) => ({
73 workspace: "acme",
74 repo,
75 public: isPublic,
76 kind,
77 estimateMicros: 100_000,
78});
79
80// ---- Gating decisions ---------------------------------------------------------------
81
82test("a paid workspace's start is reserved and goes ahead", async () => {
83 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.84 const admitted = await new ComputeGate(binding, memory(), quiet).admit(request("deploy"));
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP85 assert.equal(admitted.ok, true);
86 assert.deepEqual(admitted.ok && admitted.reservation, { id: "rsv_1", paidBy: "credit" });
87 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.88 assert.deepEqual(reserve.body, { workspace: "acme", repo, public: false, kind: "deploy", estimateMicros: 100_000 });
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP89});
90
91test("billing's refusal is shown in its own words", async () => {
92 for (const code of ["not_paid", "trial_used", "limit", "oss_pool_empty"] as const) {
93 const { binding } = billing({
94 entitlements: wire(ent({ plan: "free", compute: false })),
95 reserve: { ok: false, error: { code, message: `Refused: ${code}. /acme/-/billing` } },
96 });
97 const admitted = await new ComputeGate(binding, memory(), quiet).admit(request("check", true));
98 assert.equal(admitted.ok, false, code);
99 assert.equal(!admitted.ok && admitted.code, code);
100 assert.equal(!admitted.ok && admitted.message, `Refused: ${code}. /acme/-/billing`);
101 }
102});
103
104test("a refusal without words gets g1t's, with what to do and where", async () => {
105 const { binding } = billing({
106 entitlements: wire(ent({ plan: "free", compute: false })),
107 reserve: { ok: false, error: { code: "not_paid", message: "" } },
108 });
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.109 const admitted = await new ComputeGate(binding, memory(), quiet).admit(request("deploy"));
110 assert.equal(!admitted.ok && admitted.message, refusalMessage("not_paid", "acme", "deploy"));
111 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$/);
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP112});
113
114test("a paused workspace starts nothing, and billing is not asked to reserve", async () => {
115 const { binding, asked } = billing({ entitlements: wire(ent({ paused: "A spend spike is waiting for an owner." })) });
116 const admitted = await new ComputeGate(binding, memory(), quiet).admit(request("workflow"));
117 assert.equal(!admitted.ok && admitted.code, "paused");
118 assert.match(!admitted.ok ? admitted.message : "", /A spend spike is waiting for an owner/);
119 assert.equal(asked.some((call) => call.method === "reserve"), false);
120});
121
122test("an answer that holds compute back is kept seconds, one that lets it through half a minute", () => {
123 assert.equal(entitlementsKeptSeconds(ent()), 30);
124 assert.equal(entitlementsKeptSeconds(ent({ paused: "A spend spike is waiting for an owner." })), 3);
125 assert.equal(entitlementsKeptSeconds(ent({ plan: "free", compute: false })), 3);
126 // A free workspace with compute (its trial) runs, so it is kept as long.
127 assert.equal(entitlementsKeptSeconds(ent({ plan: "free", compute: true })), 30);
128});
129
130test("internal and enterprise plans pass even when billing refuses", async () => {
131 for (const plan of ["internal", "enterprise"] as const) {
132 const { binding } = billing({
133 entitlements: wire(ent({ plan })),
134 reserve: { ok: false, error: { code: "limit", message: "Over." } },
135 });
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.136 const admitted = await new ComputeGate(binding, memory(), quiet).admit(request("deploy"));
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP137 assert.equal(admitted.ok, true, plan);
138 }
139 assert.equal(alwaysPasses("internal") && alwaysPasses("enterprise") && !alwaysPasses("paid"), true);
140});
141
142test("a pause from billing holds for every plan: g1t's own budget and its daily breaker", async () => {
143 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.144 const message = "flagon-io's monthly budget is used up ($150.00 of $150.00 this month at cost), so new runs wait.";
145 const { binding } = billing({ entitlements: wire(ent({ plan })), reserve: { ok: false, error: { code: "paused", message } } });
146 const admitted = await new ComputeGate(binding, memory(), quiet).admit(request("workflow"));
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP147 assert.equal(!admitted.ok && admitted.code, "paused", plan);
148 assert.equal(!admitted.ok && admitted.message, message);
149 }
150 // Billing's pause reason reads cleanly inside g1t's sentence.
151 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.152 refusalMessage("paused", "acme", "workflow", "Its budget is used up."),
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP153 "g1t paused compute for this workspace: Its budget is used up. Contact support@g1t.sh to have it looked at.",
154 );
155});
156
157test("billing down: free workspaces fail closed, paying ones go on", async () => {
158 const down = new Set(["entitlements", "reserve"]);
159 // Nothing known about the workspace: treated as free.
160 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.161 let admitted = await gate.admit(request("deploy"));
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP162 assert.equal(!admitted.ok && admitted.code, "billing_unavailable");
163 // Last seen paid: goes ahead, without a reservation.
164 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.165 admitted = await gate.admit(request("deploy"));
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP166 assert.deepEqual(admitted, { ok: true, reservation: null, entitlements: null });
167 // Entitlements answer, reserve errors: the plan decides.
168 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.169 admitted = await gate.admit(request("deploy"));
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP170 assert.equal(admitted.ok, false);
171 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.172 assert.equal((await gate.admit(request("deploy"))).ok, true);
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP173 assert.equal(onBillingError("free"), "refuse");
174 assert.equal(onBillingError(null), "refuse");
175 assert.equal(onBillingError("paid"), "allow");
176});
177
178test("an answer that is not a refusal counts as billing being down", async () => {
179 const { binding } = billing({
180 entitlements: wire(ent({ plan: "free", compute: false })),
181 reserve: { ok: false, error: { code: "invalid", message: "Unknown method" } },
182 });
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.183 const admitted = await new ComputeGate(binding, memory(), quiet).admit(request("deploy"));
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP184 assert.equal(!admitted.ok && admitted.code, "billing_unavailable");
185 assert.equal(refusalCode({ code: "payment_required" }), "not_paid");
186 assert.equal(refusalCode({ code: "conflict", reason: "trial_used" }), "trial_used");
187 assert.equal(refusalCode({ code: "invalid" }), null);
188});
189
190test("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.191 const snake = readEntitlements({ plan: "free", trial_micros_left: 5, trial_verified: true, max_run_minutes: 30, paused: "" });
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP192 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.193 assert.equal(snake?.maxRunMinutes, 30);
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP194 assert.equal(snake?.paused, null);
195 assert.equal(readEntitlements({ team: true })?.plan, undefined);
196 assert.equal(readEntitlements({ ok: true, value: { plan: "paid" } })?.plan, "paid");
197});
198
199test("before they try: what a free workspace can start, by kind and repository", () => {
200 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.201 assert.equal(localRefusal(free, "deploy", false), "not_paid");
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP202 assert.equal(localRefusal(free, "check", true), "not_paid", "the pool needs a card check");
203 const verified = ent({ plan: "free", compute: false, trialVerified: true, trialMicrosLeft: 0 });
204 // The open-source path: public repositories' checks, workflows and queue.
205 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.206 assert.equal(localRefusal(verified, "deploy", true), "trial_used");
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP207 assert.equal(localRefusal(verified, "check", false), "trial_used");
208 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.209 assert.equal(localRefusal(trial, "deploy", false), null);
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP210 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.211 assert.equal(localRefusal(ent({ paused: "Held." }), "deploy", false), "paused");
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP212});
213
214// ---- Estimates ------------------------------------------------------------------------
215
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.216test("an estimate is the sandbox for its whole time cap", () => {
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP217 assert.equal(sandboxEstimateMicros(60, 25), 90_000);
218 assert.equal(sandboxEstimateMicros(-5, 25), 0);
219});
220
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.221test("what work cost: its seconds at the sandbox price", () => {
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP222 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.223 assert.equal(actualMicros(-10, 20), 0);
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP224});
225
226test("the gate reads the sandbox price from the price book", async () => {
227 const gate = new ComputeGate(billing({}).binding, memory(), quiet);
228 assert.equal(await gate.microsPerSecond(), 20);
229 const down = new ComputeGate(billing({ fail: new Set(["prices"]) }).binding, memory(), quiet);
Merge branch 'worktree-agent-a56293a6f836cd343'230 assert.equal(await down.microsPerSecond(), 63);
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP231});
232
233// ---- Caps -------------------------------------------------------------------------------
234
The spend caps of g1t's former agents are gone end to end. Billing no longer serves set_caps (the owners' run and issue caps, which nothing called), reads the owners' caps from limits, or puts a run cap and an issue cap in a workspace's entitlements; ISSUE_CAP_MICROS and RUN_CAP_MICROS are no longer read, and a staff override no longer carries either, so sudo stops sending them. The runner no longer turns a run cap into a cost cap, and the GUARDRAILS it hands a sandbox says the run's time cap and default branch only, without the command rules, deny patterns and cost cap nothing reads. A run's guardrails no longer carry restrictNetwork and hosts, sent only for runners from before network rules were removed. Stored data still reads: the limits and billing_accounts columns stay, no migration is touched, and a run recorded with a budget still shows it. Checked: cargo test for g1t-billing (243) and g1t-contracts (307), cargo check of the workspace, the TypeScript typecheck, the runner's 88 tests, sudo's 153, the contracts' 48 and the deploy tool's 92.235test("the time cap is the lower of the guardrails' and the plan's", () => {
236 const guard = { policy: { minutes: {} }, minutes: 90 };
237 assert.equal(withPlanLimits(guard, { minutes: 60 }).minutes, 60);
238 assert.equal(withPlanLimits(guard, { minutes: null }).minutes, 90);
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP239});
240
Merge branch 'main' into ask-integration241// ---- Sandboxes' network ------------------------------------------------------------------
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP242
Merge branch 'main' into ask-integration243test("only a job that may be given brokered secrets passes the Worker", () => {
244 assert.equal(throughWorker({ brokered: true }, undefined), true);
245 assert.equal(throughWorker({ brokered: true }, "on"), true);
246 // Everything else goes straight out.
247 assert.equal(throughWorker({ brokered: false }, undefined), false);
248 assert.equal(throughWorker({}, undefined), false);
249 assert.equal(throughWorker(null, undefined), false);
250 // The operator's switch.
251 assert.equal(throughWorker({ brokered: true }, " OFF "), false);
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP252});
253
Merge branch 'main' into actions-toolkit-oidc-artifacts254test("workflow jobs get Docker unless the operator turns it off", () => {
255 assert.equal(dockerFor(undefined), "on");
256 assert.equal(dockerFor("on"), "on");
257 assert.equal(dockerFor(" OFF "), "off");
Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP258});

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