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

Merge branch 'worktree-agent-a8752162fea25f63f' into spend-guardrails1import assert from "node:assert/strict";
2import { readFileSync } from "node:fs";
3import { test } from "node:test";
4
5import {
6 LIMIT_PERIOD_SECONDS,
7 RATE_LIMITS,
8 type RateLimitBinding,
9 checkLimit,
10 isLimited,
11 secretKey,
12} from "@g1t/contracts/rate-limits";
13
Merge GitHub pull request #8 (claude/cool-newton-7nm8r9): Docker deployments on g1t.page behind a switch that stays off until Cloudflare's container APIs are checked live, workflows a workspace manages in its repositories, tarball and zipball downloads, refs/pull/* kept to g1t alone, framework detection for g1t.page builds, and a workflow's event payload matching GitHub's.14import { archiveLink, gitLimited, heavy, pageLimited, sessionCookie, unlimited, usercontentLimited } from "./front-door-limits.ts";
Merge branch 'worktree-agent-a8752162fea25f63f' into spend-guardrails15
16/** A binding that lets `allow` requests through per key, recording each key it was asked. */
17function binding(allow: number): RateLimitBinding & { keys: string[] } {
18 const counts = new Map<string, number>();
19 const keys: string[] = [];
20 return {
21 keys,
22 async limit({ key }) {
23 keys.push(key);
24 const count = (counts.get(key) ?? 0) + 1;
25 counts.set(key, count);
26 return { success: count <= allow };
27 },
28 };
29}
30
31const broken: RateLimitBinding = {
32 async limit() {
33 throw new Error("the binding is down");
34 },
35};
36
37function request(path: string, headers: Record<string, string> = {}): Request {
38 return new Request(`https://g1t.sh${path}`, { headers: { "cf-connecting-ip": "203.0.113.9", ...headers } });
39}
40
41test("a limit lets requests through until it is reached", async () => {
42 const limit = binding(2);
43 assert.equal(await checkLimit(limit, "ip:1"), "allowed");
44 assert.equal(await checkLimit(limit, "ip:1"), "allowed");
45 assert.equal(await checkLimit(limit, "ip:1"), "limited");
46 assert.equal(await checkLimit(limit, "ip:2"), "allowed", "each key counts apart");
47});
48
49test("a missing or failing binding fails open", async () => {
50 const logged = console.error;
51 console.error = () => {};
52 try {
53 assert.equal(await checkLimit(undefined, "ip:1"), "unavailable");
54 assert.equal(await checkLimit(broken, "ip:1"), "unavailable");
55 assert.equal(await isLimited(broken, "ip:1"), false);
56 assert.equal(await gitLimited({ GIT_ANONYMOUS_LIMIT: broken }, request("/acme/rocket.git/info/refs")), null);
57 assert.equal(await pageLimited({ WEB_ADDRESS_LIMIT: broken, WEB_ANONYMOUS_LIMIT: broken }, request("/acme/rocket"), "/acme/rocket"), null);
58 } finally {
59 console.error = logged;
60 }
61});
62
63test("secrets are keyed by a short hash, never as they are", async () => {
64 const key = await secretKey("session", "s3cret-session");
65 assert.match(key, /^session:[0-9a-f]{16}$/);
66 assert.equal(await secretKey("session", "s3cret-session"), key);
67 assert.notEqual(await secretKey("session", "another"), key);
68});
69
70test("git without credentials is limited by address, with a message git shows", async () => {
71 const env = { GIT_ANONYMOUS_LIMIT: binding(1), GIT_SIGNED_LIMIT: binding(1) };
72 const clone = () => request("/acme/rocket.git/info/refs");
73 assert.equal(await gitLimited(env, clone()), null);
74 const refused = await gitLimited(env, clone());
75 assert.equal(refused?.status, 429);
76 assert.equal(refused?.headers.get("retry-after"), String(LIMIT_PERIOD_SECONDS));
77 assert.match(refused?.headers.get("content-type") ?? "", /^text\/plain/);
78 assert.match((await refused?.text()) ?? "", /Too many git requests/);
79 assert.deepEqual(env.GIT_ANONYMOUS_LIMIT.keys, ["ip:203.0.113.9", "ip:203.0.113.9"]);
80});
81
82test("git with credentials counts by a hash of them, apart from the address", async () => {
83 const env = { GIT_ANONYMOUS_LIMIT: binding(0), GIT_SIGNED_LIMIT: binding(5) };
84 const authorization = `Basic ${btoa("ada:g1t_token")}`;
85 assert.equal(await gitLimited(env, request("/acme/rocket.git/git-upload-pack", { authorization })), null);
86 assert.equal(env.GIT_ANONYMOUS_LIMIT.keys.length, 0);
87 assert.match(env.GIT_SIGNED_LIMIT.keys[0]!, /^git:[0-9a-f]{16}$/);
88 assert.ok(!env.GIT_SIGNED_LIMIT.keys[0]!.includes("g1t_token"));
89});
90
91test("signed-out pages count by address, and costly ones against a tighter limit too", async () => {
92 const env = { WEB_ADDRESS_LIMIT: binding(100), WEB_ANONYMOUS_LIMIT: binding(100), WEB_HEAVY_LIMIT: binding(1) };
93 assert.equal(await pageLimited(env, request("/acme/rocket"), "/acme/rocket"), null);
94 assert.equal(env.WEB_HEAVY_LIMIT.keys.length, 0);
95 const archive = "/acme/rocket/archive/main.zip";
96 assert.equal(await pageLimited(env, request(archive), archive), null);
97 const refused = await pageLimited(env, request(archive), archive);
98 assert.equal(refused?.status, 429);
99 assert.match((await refused?.text()) ?? "", /Signed-in accounts have a higher limit/);
100 assert.equal(await pageLimited(env, request("/acme/rocket/issues"), "/acme/rocket/issues"), null, "other pages go on");
101});
102
103test("signed-in requests count by session, and every request by address", async () => {
104 const env = { WEB_ADDRESS_LIMIT: binding(1), WEB_SESSION_LIMIT: binding(100), WEB_ANONYMOUS_LIMIT: binding(0) };
105 const signedIn = () => request("/acme/rocket/archive/main.zip", { cookie: "theme=dark; g1t_session=abc123" });
106 assert.equal(await pageLimited(env, signedIn(), "/acme/rocket/archive/main.zip"), null);
107 assert.equal(env.WEB_ANONYMOUS_LIMIT.keys.length, 0);
108 assert.match(env.WEB_SESSION_LIMIT.keys[0]!, /^session:[0-9a-f]{16}$/);
109 // Made-up cookies still meet the ceiling per address.
110 const refused = await pageLimited(env, request("/", { cookie: "g1t_session=made-up" }), "/");
111 assert.equal(refused?.status, 429);
112});
113
Merge main into Artifacts Phase 2114test("requests with an access token count by a hash of the token, at the API's limit", async () => {
115 const env = { WEB_ADDRESS_LIMIT: binding(100), WEB_TOKEN_LIMIT: binding(1), WEB_SESSION_LIMIT: binding(100), WEB_ANONYMOUS_LIMIT: binding(0) };
116 const withToken = () => request("/acme/rocket", { authorization: "Bearer g1t_secret", cookie: "g1t_session=abc123" });
117 assert.equal(await pageLimited(env, withToken(), "/acme/rocket"), null);
118 assert.equal(env.WEB_SESSION_LIMIT.keys.length, 0, "the token counts, not a cookie beside it");
119 assert.equal(env.WEB_ANONYMOUS_LIMIT.keys.length, 0);
120 assert.match(env.WEB_TOKEN_LIMIT.keys[0]!, /^token:[0-9a-f]{16}$/);
121 assert.ok(!env.WEB_TOKEN_LIMIT.keys[0]!.includes("g1t_secret"));
122 const refused = await pageLimited(env, withToken(), "/acme/rocket");
123 assert.equal(refused?.status, 429);
124 assert.match((await refused?.text()) ?? "", /this access token/);
125 assert.equal(RATE_LIMITS.WEB_TOKEN_LIMIT.limit, RATE_LIMITS.API_TOKEN_LIMIT.limit);
126});
127
Merge branch 'worktree-agent-a8752162fea25f63f' into spend-guardrails128test("files the Worker serves itself are never limited", async () => {
129 const env = { WEB_ADDRESS_LIMIT: binding(0), WEB_ANONYMOUS_LIMIT: binding(0) };
130 for (const path of ["/assets/app-1a2b.js", "/fonts/hanken.woff2", "/favicon.ico", "/robots.txt", "/llms.txt", "/sitemap.xml"]) {
131 assert.ok(unlimited(path), path);
132 assert.equal(await pageLimited(env, request(path), path), null, path);
133 }
134 assert.ok(!unlimited("/acme/rocket/blob/main/logo.png"), "a file in a repository is a page");
135});
136
137test("costly pages are recognised", () => {
138 for (const path of [
139 "/search",
140 "/search.data",
141 "/acme/rocket/archive/main.zip",
142 "/acme/rocket/actions/runs/run_1",
143 "/acme/rocket/actions/runs/run_1.data",
144 "/acme/rocket/actions/runs/run_1/logs.zip",
145 "/acme/rocket/actions/runs/run_1/artifacts/dist",
146 "/acme/rocket/actions/jobs/job_1/log.txt",
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9147 "/acme/rocket/security/report",
148 "/acme/rocket/security/report.data",
Merge branch 'worktree-agent-a8752162fea25f63f' into spend-guardrails149 ]) {
150 assert.ok(heavy(path), path);
151 }
A running job's log is live, as on GitHub. A step's output reaches its run's page within about two seconds: the runner sends what is printed every second, and the page asks every second while a step runs, every three while none does; an answer that fails (a 429, a 5xx, the network) is asked again after a wait that doubles to half a minute, or as long as Retry-After says, where before one such answer stopped the log for good and the step said "No output yet." until the page was reloaded, and a signed-out viewer's run page, refreshing beside its log, ran into the 30-a-minute limit within a minute; the live log now counts as a page, and only log downloads as costly. A hidden tab stops asking and catches up when shown, and the page says when it is reconnecting. The running step opens by itself, its timer counts, its log follows the newest line while its end is on screen and offers Jump to latest once the viewer scrolls up, a step that has printed nothing for a while says "Running · no output for 2m 10s", and the job's header names the step it is on. Colour that tools print (bold, dim, italic, underline, the 16 colours as theme tokens for light and dark, 256 colours and 24-bit colour, text and background) is drawn as printed, other escape codes are dropped and links keep their text, and search reads the plain text and keeps the colours around what it marks. Every line is stamped with the time it was printed, as GitHub's raw logs are, so log.txt and the run's zip have a time at the start of each line, and a clock button beside Search logs shows the times, remembered by the browser. Set up job now gives the runner's version and name, the operating system, the runner image, the machine's architecture, CPUs and memory, the token's permissions under G1T_TOKEN Permissions, the secret source and the actions it downloads; each run step opens with GitHub's folded Run group, the script in cyan, the shell's whole path and the step's env, each uses step with its with inputs (an action's defaults among them) and env; cache and artifact transfers say how far they have got every five seconds; and a last Complete job step sets the job's outputs and stops what the steps left running, found by RUNNER_TRACKING_ID as GitHub's runner finds it. Two batches of a job's log written at once can no longer reach for the same seq and lose one. The Actions guide and the rate limits page say so.152 // A running job's live log is asked for every second; it counts as a page.
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9153 for (const path of ["/", "/acme/rocket", "/acme/rocket/actions", "/acme/rocket/issues/1", "/acme/search", "/acme/rocket/security", "/acme/rocket/security/reports", "/acme/rocket/actions/jobs/job_1/log"]) {
Merge branch 'worktree-agent-a8752162fea25f63f' into spend-guardrails154 assert.ok(!heavy(path), path);
155 }
156});
157
158test("the session cookie is read from among others", () => {
159 assert.equal(sessionCookie("theme=dark; g1t_session=abc; x=1"), "abc");
160 assert.equal(sessionCookie("g1t_session=abc"), "abc");
161 assert.equal(sessionCookie("not_g1t_session=abc"), null);
162 assert.equal(sessionCookie(null), null);
163});
164
165/** A wrangler.jsonc as JSON: comments and trailing commas out, strings kept. */
166function readJsonc(path: string): { ratelimits?: { name: string; namespace_id: string; simple: { limit: number; period: number } }[] } {
167 const text = readFileSync(new URL(path, import.meta.url), "utf8")
168 .replace(/("(?:\\.|[^"\\])*")|\/\/[^\n]*|\/\*[\s\S]*?\*\//g, (_, string: string | undefined) => string ?? "")
169 .replace(/,(\s*[}\]])/g, "$1");
170 return JSON.parse(text);
171}
172
173test("every wrangler.jsonc declares the rate limits RATE_LIMITS lists, and only those", () => {
174 const ids = new Set<number>();
175 const workers = new Set(Object.values(RATE_LIMITS).map((spec) => spec.worker));
176 for (const worker of workers) {
177 const declared = readJsonc(`../../../../${worker}/wrangler.jsonc`).ratelimits ?? [];
178 const listed = Object.entries(RATE_LIMITS).filter(([, spec]) => spec.worker === worker);
179 assert.deepEqual(
180 declared.map((binding) => binding.name).sort(),
181 listed.map(([name]) => name).sort(),
182 `${worker}'s bindings`,
183 );
184 for (const [name, spec] of listed) {
185 const binding = declared.find((b) => b.name === name)!;
186 assert.equal(Number(binding.namespace_id), spec.namespaceId, `${name}'s namespace id`);
187 assert.equal(binding.simple.limit, spec.limit, `${name}'s limit`);
188 assert.equal(binding.simple.period, LIMIT_PERIOD_SECONDS, `${name}'s period`);
189 }
190 }
191 for (const spec of Object.values(RATE_LIMITS)) {
192 assert.ok(!ids.has(spec.namespaceId), `namespace id ${spec.namespaceId} is used once`);
193 ids.add(spec.namespaceId);
194 }
195});
Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address196
197test("repository files on the usercontent origin count per address; avatars do not", async () => {
198 const env = { WEB_ANONYMOUS_LIMIT: binding(1) };
199 const raw = "/acme/rocket/raw/main/logo.png";
200 assert.equal(await usercontentLimited(env, request(raw), raw), null);
201 assert.equal((await usercontentLimited(env, request(raw), raw))?.status, 429);
202 assert.equal(await usercontentLimited(env, request("/avatars/" + "a".repeat(64)), "/avatars/" + "a".repeat(64)), null);
Merge branch 'worktree-agent-a5b0d9e2733e9e382'203 const image = "/production-images/" + "b".repeat(64);
204 assert.equal(await usercontentLimited(env, request(image), image), null);
Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address205 assert.deepEqual(env.WEB_ANONYMOUS_LIMIT.keys, ["ip:203.0.113.9", "ip:203.0.113.9"]);
206});
Merge GitHub pull request #8 (claude/cool-newton-7nm8r9): Docker deployments on g1t.page behind a switch that stays off until Cloudflare's container APIs are checked live, workflows a workspace manages in its repositories, tarball and zipball downloads, refs/pull/* kept to g1t alone, framework detection for g1t.page builds, and a workflow's event payload matching GitHub's.207
208test("a signed archive link is known by its token, and only on an archive", () => {
209 assert.equal(archiveLink("/acme/web/archive/abc.tar.gz", "https://g1t.sh/acme/web/archive/abc.tar.gz?token=t1"), "t1");
210 assert.equal(archiveLink("/acme/web/archive/main.zip", "https://g1t.sh/acme/web/archive/main.zip?token=t2"), "t2");
211 assert.equal(archiveLink("/acme/web/archive/main.zip", "https://g1t.sh/acme/web/archive/main.zip"), null);
212 assert.equal(archiveLink("/acme/web/issues", "https://g1t.sh/acme/web/issues?token=t3"), null);
213});

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