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.
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 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"; |
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 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")); |
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 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 }); |
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 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")); | |
| Billing is ready to take real money, and stays on Stripe's test key until the switch. The g1t plan is $21 a month, all-in, with $10 of usage included: the card processing fee is part of the price, and no payment carries a fee line any more (old fee rows still read as they were). Card, billing address, tax IDs, invoices and cancelling are on Stripe's billing page (Manage billing), whose configuration billing keeps in step; Checkout asks for the billing address and a tax ID; g1t's Billing page keeps usage, the spend limit, budget alerts and prepaying. Members other than owners and billing managers no longer see billing details or invoices, from the page or the API. | 111 | assert.match(refusalMessage("not_paid", "acme", "deploy"), /^Deployments need a paid workspace\. Start the \$21 plan or try it with \$5 of free usage after a card check: \/acme\/-\/billing$/); |
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 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")); |
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 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")); | |
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 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."), |
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 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")); |
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 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")); |
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 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")); |
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 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); |
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 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")); |
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 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: "" }); |
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 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); |
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 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"); |
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 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"); |
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 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); |
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 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"); |
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 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", () => { |
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 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", () => { |
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 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); |
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 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); |
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 231 | }); |
| 232 | ||
| 233 | // ---- Caps ------------------------------------------------------------------------------- | |
| 234 | ||
| Merge branch 'worktree-agent-ac950b9c891220ad3' | 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); | |
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 239 | }); |
| 240 | ||
| Merge branch 'main' into ask-integration | 241 | // ---- Sandboxes' network ------------------------------------------------------------------ |
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 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); | |
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 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"); | |
| Mission control: a place to ask g1t, shown and not on yet, with the actions under it | 258 | }); |
This file's history is long; its oldest lines are credited to the oldest commit read.