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 MCP | 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, | |
| 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-integration | 19 | import { dockerFor, throughWorker, withPlanLimits } from "./egress.ts"; |
| Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP | 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 | 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. */ | |
| 39 | function 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. */ | |
| 61 | function memory(plan: ComputeEntitlements["plan"] | null = null): PlanMemory { | |
| 62 | return { get: async () => plan, put: async () => undefined }; | |
| 63 | } | |
| 64 | ||
| 65 | const quiet = () => undefined; | |
| 66 | ||
| 67 | function wire(e: ComputeEntitlements) { | |
| 68 | // As billing serialises it: camelCase. | |
| 69 | return { workspace: "acme", ...e }; | |
| 70 | } | |
| 71 | ||
| 72 | const 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 | ||
| 82 | test("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 MCP | 85 | 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 MCP | 89 | }); |
| 90 | ||
| 91 | test("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 | ||
| 104 | test("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 MCP | 112 | }); |
| 113 | ||
| 114 | test("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 | ||
| 122 | test("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 | ||
| 130 | test("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 MCP | 137 | assert.equal(admitted.ok, true, plan); |
| 138 | } | |
| 139 | assert.equal(alwaysPasses("internal") && alwaysPasses("enterprise") && !alwaysPasses("paid"), true); | |
| 140 | }); | |
| 141 | ||
| 142 | test("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 MCP | 147 | 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 MCP | 153 | "g1t paused compute for this workspace: Its budget is used up. Contact support@g1t.sh to have it looked at.", |
| 154 | ); | |
| 155 | }); | |
| 156 | ||
| 157 | test("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 MCP | 162 | 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 MCP | 166 | 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 MCP | 170 | 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 MCP | 173 | assert.equal(onBillingError("free"), "refuse"); |
| 174 | assert.equal(onBillingError(null), "refuse"); | |
| 175 | assert.equal(onBillingError("paid"), "allow"); | |
| 176 | }); | |
| 177 | ||
| 178 | test("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 MCP | 184 | 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 | ||
| 190 | 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. | 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 MCP | 192 | 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 MCP | 194 | 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 | ||
| 199 | test("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 MCP | 202 | 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 MCP | 207 | 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 MCP | 210 | 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 MCP | 212 | }); |
| 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. | 216 | test("an estimate is the sandbox for its whole time cap", () => { |
| Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP | 217 | 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. | 221 | test("what work cost: its seconds at the sandbox price", () => { |
| Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP | 222 | 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 MCP | 224 | }); |
| 225 | ||
| 226 | test("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 MCP | 231 | }); |
| 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. | 235 | test("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 MCP | 239 | }); |
| 240 | ||
| Merge branch 'main' into ask-integration | 241 | // ---- Sandboxes' network ------------------------------------------------------------------ |
| Docs: inbox threads, reasons, subscriptions, watching, email, API and MCP | 242 | |
| Merge branch 'main' into ask-integration | 243 | test("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 MCP | 252 | }); |
| 253 | ||
| Merge branch 'main' into actions-toolkit-oidc-artifacts | 254 | test("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 MCP | 258 | }); |
This file's history is long; its oldest lines are credited to the oldest commit read.