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

A clone answered from the pack cache is served before the free operation cap: it is not an operation1//! The public API: REST at api.g1t.sh and the MCP server at mcp.g1t.sh.
2//!
3//! One Worker, two hostnames. Both are thin adapters over the same
4//! operations (see [`operations::Op`]), which call the services that own
5//! the data. This Worker holds none.
6
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb977mod about;
The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook.8mod run_artifacts;
Merge branch 'worktree-agent-a3b9b1f730f55b5bf'9mod caches;
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.10mod archives;
Merge branch 'worktree-agent-aaf03bdceac799c89'11mod addresses;
A clone answered from the pack cache is served before the free operation cap: it is not an operation12mod alerts;
13mod audit;
Usage, Billing settings and prepaid AI credit; fixes from the UX audit14mod billing;
A clone answered from the pack cache is served before the free operation cap: it is not an operation15mod blobs;
Merge branch 'main' into rm-agents16mod broker;
Merge checks: statuses and check runs on every commit17mod checks;
Merge branch 'worktree-agent-a6ec0a89d7fe7a9ae'18mod coordination;
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb9719mod deployments;
Projects have Observability: their requests, logs, traces, metrics and errors, from apps on g1t.page with nothing to set up, and from software anywhere else over OpenTelemetry.20mod telemetry;
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca21mod deploy_keys;
Merge branch 'worktree-agent-a8752162fea25f63f' into spend-guardrails22mod limits;
Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009)23mod logs;
A clone answered from the pack cache is served before the free operation cap: it is not an operation24mod mcp;
API: notifications over REST and MCP, with notifications scopes25mod notifications;
A clone answered from the pack cache is served before the free operation cap: it is not an operation26mod oauth;
Actions: OIDC tokens, the toolkit's cache and artifact services, and artifacts in R227mod oidc;
Merge packages: roles, Actions access, source label, soft delete, API28mod packages;
Merge main into Artifacts Phase 229mod folios;
Merge branch 'worktree-agent-ad531323a21f4c975'30mod follow;
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar31mod people;
A clone answered from the pack cache is served before the free operation cap: it is not an operation32mod openapi;
API: pinned projects over REST and MCP33mod pins;
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb9734mod projects;
Merge branch 'worktree-agent-a3abfcce648e87dca'35mod protection;
A clone answered from the pack cache is served before the free operation cap: it is not an operation36mod operations;
37mod renamed;
38#[cfg(test)]
39mod responses;
40mod rest;
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge41mod rules;
A clone answered from the pack cache is served before the free operation cap: it is not an operation42mod runners;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar43mod security;
A clone answered from the pack cache is served before the free operation cap: it is not an operation44mod tools;
Merge branch 'worktree-agent-ab8033ab973f8d99a'45mod why;
API and MCP for a workspace's personal access token rules, members' tokens and approvals46mod token_policy;
Actions: OIDC tokens, the toolkit's cache and artifact services, and artifacts in R247mod toolkit;
A clone answered from the pack cache is served before the free operation cap: it is not an operation48
49use g1t_contracts::identity::{
50 DeviceClaim, DeviceClaimArgs, DeviceStart, DeviceStartArgs, TokenArgs,
51};
52use g1t_contracts::work::{
The API and MCP server no longer offer g1t's own agents. g1t is what your coding agents work against, not an agent of its own, so the operations that drove hosted agents are gone: messaging an agent on a pull request (message_agent, take_messages, answer_message), assigning or delegating an issue to g1t (assign_issue, delegate), plans (plan_work, get_plan, apply_plan), the context hub (search_context, get_entity), model routes, agents' computers, AI Gateway requests, "Fix with g1t" on a security alert, and every Customize route (marketplaces, plugins, connectors). The callbacks the agent sandbox reported through (/agent-runs/*, /runs/:id/usage, /plans/:id, /reviews/:id) go with them, update_repo_settings stops accepting agent_review, max_revisions and hold_low_confidence, import_issue stops accepting assign, and an old agent token with no run behind it is refused.53 CheckRun, Mergeable, QueueState, ReportChecksArgs, ReportMergecheckArgs,
54 ReportQueueArgs,
A clone answered from the pack cache is served before the free operation cap: it is not an operation55};
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)56use g1t_contracts::{Failure, FailureCode, Outcome, PrincipalKind, User, Viewer};
A clone answered from the pack cache is served before the free operation cap: it is not an operation57use g1t_kit::wire;
58use serde_json::{Value, json};
59use worker::{Context, Env, Method, Request, Response, Result, event};
60
61use operations::Services;
62
63fn method_name(method: Method) -> &'static str {
64 match method {
65 Method::Get => "GET",
66 Method::Post => "POST",
67 Method::Patch => "PATCH",
68 Method::Put => "PUT",
69 Method::Delete => "DELETE",
70 Method::Options => "OPTIONS",
71 Method::Head => "HEAD",
72 _ => "OTHER",
73 }
74}
75
76/// A JSON response. Every body the API sends has its keys in `snake_case`;
77/// the contracts it passes through are `camelCase`, so they are converted
78/// here, on the way out (see [`g1t_kit::wire`]). The OpenAPI document and
79/// the MCP protocol's own envelope keep the spelling their standards use.
80pub(crate) fn reply<T: serde::Serialize>(value: &T) -> Result<Response> {
81 Response::from_json(&wire::snake_case(serde_json::to_value(value)?))
82}
83
84/// The parts of a job's spec (`POST /actions/jobs/{job}/spec`) that are the
85/// workflow file, GitHub's contexts and event, and where to check out, all
86/// passed through as they are.
87const JOB_SPEC_AS_GIVEN: &[&str] = &[
Merge branch 'worktree-agent-a3abfcce648e87dca'88 "spec", "workflow", "github", "event", "contexts", "checkout", "permissions",
A clone answered from the pack cache is served before the free operation cap: it is not an operation89];
90
Actions: OIDC tokens, the toolkit's cache and artifact services, and artifacts in R291/// Puts the toolkit's variables in a job's spec: its runtime token, where
92/// the toolkit's services are, and where to ask for an OIDC token when the
93/// job may have one and this installation issues them. The `runtime` the
94/// actions service sent goes no further.
95fn with_runtime(spec: &mut Value, api: &str, oidc: bool) {
96 let Some(runtime) = spec.as_object_mut().and_then(|s| s.remove("runtime")) else { return };
97 let Some(token) = runtime["token"].as_str().filter(|t| !t.is_empty()) else { return };
98 let id_token = oidc && runtime["id_token"].as_bool() == Some(true);
99 let vars = toolkit::runtime_variables(api, token, id_token);
100 if let Some(variables) = spec.get_mut("variables").and_then(Value::as_object_mut) {
101 variables.extend(vars);
102 }
103}
104
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)105/// What a person whose account has not confirmed its email address may
106/// call: who they are, their addresses, and confirming one with the code
107/// from the email. Nothing over MCP.
108fn pending_may(method: &str, path: &str, on_mcp: bool) -> bool {
109 !on_mcp
110 && matches!(
111 (method, path.trim_end_matches('/')),
112 ("GET", "/user") | ("GET", "/user/emails") | ("POST", "/user/emails/confirm")
113 )
114}
115
A clone answered from the pack cache is served before the free operation cap: it is not an operation116/// An error in the shape every endpoint uses.
117fn failure(failure: &Failure) -> Result<Response> {
118 Ok(reply(&json!({ "error": failure }))?.with_status(failure.code.http_status()))
119}
120
121fn fail(code: FailureCode, message: &str) -> Result<Response> {
122 failure(&Failure {
123 code,
124 message: message.to_owned(),
125 })
126}
127
128/// A request body as JSON. An empty or malformed body is no input.
129async fn json_body(request: &mut Request) -> Value {
130 request.json().await.unwrap_or(Value::Null)
131}
132
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9'133/// Who a request's `Authorization: Bearer g1t_…` (or `token g1t_…`, as
134/// Octokit sends it) names. A missing token is an anonymous viewer; a
135/// wrong one is refused, so that a typo does not silently look signed out.
A clone answered from the pack cache is served before the free operation cap: it is not an operation136async fn authenticate(
137 request: &Request,
138 services: &Services,
139) -> Result<std::result::Result<Viewer, Response>> {
140 let header = request.headers().get("authorization")?.unwrap_or_default();
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9'141 let Some(token) = limits::bearer(&header) else {
142 return Ok(Ok(None));
A clone answered from the pack cache is served before the free operation cap: it is not an operation143 };
144 let viewer: Viewer = g1t_kit::call(
145 &services.identity,
146 "user_for_access_token",
147 &TokenArgs {
148 token: token.to_owned(),
149 },
150 )
151 .await?;
152 if viewer.is_some() {
153 return Ok(Ok(viewer));
154 }
155 let mut response = fail(FailureCode::Unauthenticated, "Invalid access token.")?;
156 // Tells an MCP client where to sign in again.
157 response.headers_mut().set(
158 "www-authenticate",
Merge branch 'worktree-agent-aaf03bdceac799c89'159 &format!("{}, error=\"invalid_token\"", services.addresses.mcp_challenge()),
A clone answered from the pack cache is served before the free operation cap: it is not an operation160 )?;
161 Ok(Err(response))
162}
163
164/// Where everything is, for someone or something exploring the API.
Merge branch 'worktree-agent-aaf03bdceac799c89'165fn index(addresses: &addresses::Addresses) -> Value {
166 let api = &addresses.api;
167 let repo = format!("{api}/repos/{{owner}}/{{name}}");
A clone answered from the pack cache is served before the free operation cap: it is not an operation168 json!({
169 "documentation_url": "https://docs.g1t.sh/reference/api/",
Merge branch 'worktree-agent-aaf03bdceac799c89'170 "openapi_url": format!("{api}/openapi.json"),
171 "mcp_url": addresses.mcp,
172 "current_user_url": format!("{api}/user"),
173 "workspaces_url": format!("{api}/workspaces"),
174 "repositories_url": format!("{api}/repos{{?q}}"),
175 "search_url": format!("{api}/search{{?q,type,page,per_page}}"),
A clone answered from the pack cache is served before the free operation cap: it is not an operation176 "repository_url": repo,
177 "repository_events_url": format!("{repo}/events{{?before}}"),
178 "labels_url": format!("{repo}/labels"),
179 "issues_url": format!("{repo}/issues{{?state,label}}"),
180 "issue_url": format!("{repo}/issues/{{number}}"),
181 "issue_comments_url": format!("{repo}/issues/{{number}}/comments"),
182 "pulls_url": format!("{repo}/pulls{{?state}}"),
183 "pull_url": format!("{repo}/pulls/{{number}}"),
184 "pull_changes_url": format!("{repo}/pulls/{{number}}/changes"),
185 "pull_reviews_url": format!("{repo}/pulls/{{number}}/reviews"),
186 "pull_session_url": format!("{repo}/pulls/{{number}}/session{{?after}}"),
Merge branch 'worktree-agent-aaf03bdceac799c89'187 "device_code_url": format!("{api}/device/code"),
188 "device_token_url": format!("{api}/device/token"),
189 "oauth_metadata_url": format!("{api}/.well-known/oauth-authorization-server"),
190 "git_url": format!("{}/{{owner}}/{{name}}.git", addresses.site),
191 "integrations_url": format!("{api}/workspaces/{{workspace}}/integrations"),
A clone answered from the pack cache is served before the free operation cap: it is not an operation192 "context_url": format!("{repo}/context{{?reference}}"),
193 "import_issue_url": format!("{repo}/issues/import"),
Merge branch 'worktree-agent-aaf03bdceac799c89'194 "hooks_url": format!("{api}/hooks/{{integration}}"),
A clone answered from the pack cache is served before the free operation cap: it is not an operation195 })
196}
197
198// Signing in from a tool. Accounts are created, and passwords typed, only
199// in a browser; a tool gets its token by having a person approve a code.
200
201/// Passes a request from an outside system to its connection, as it came:
202/// its signature covers the exact bytes of the body.
203async fn receive_hook(request: &mut Request, services: &Services, id: &str) -> Result<Response> {
204 let headers: std::collections::HashMap<String, String> = request
205 .headers()
206 .entries()
207 .map(|(name, value)| (name.to_lowercase(), value))
208 .collect();
209 let body = request.text().await.unwrap_or_default();
210 // A push to GitHub with many commits makes a large payload.
211 let limit = if id == "github" { 10_000_000 } else { 1_000_000 };
212 if body.len() > limit {
213 return Ok(reply(&json!({ "message": "The body is too large." }))?.with_status(413));
214 }
215 // g1t's GitHub App has one webhook for every installation; it is
216 // checked against the app's own secret.
217 let (method, args) = if id == "github" {
218 ("github_receive", json!({ "headers": headers, "body": body }))
219 } else {
220 ("receive", json!({ "id": id, "headers": headers, "body": body }))
221 };
222 let received: g1t_contracts::integrations::Received =
223 g1t_kit::call(&services.integrations, method, &args).await?;
224 Ok(reply(&json!({ "message": received.message }))?.with_status(received.status))
225}
226
227async fn receive_stripe(request: &mut Request, env: &Env) -> Result<Response> {
228 let signature = request.headers().get("stripe-signature")?.unwrap_or_default();
229 let payload = request.text().await.unwrap_or_default();
230 if payload.len() > 1_000_000 || signature.is_empty() {
231 return Ok(reply(&json!({ "message": "Not a Stripe event." }))?.with_status(400));
232 }
233 let handled: g1t_contracts::Outcome<bool> = g1t_kit::call(
234 &env.service("BILLING")?,
235 "stripe_webhook",
236 &g1t_contracts::billing::StripeWebhookArgs { payload, signature },
237 )
238 .await?;
239 // A refusal is a 400, so Stripe shows it as failed; anything handled,
240 // or already handled, is a 200, so Stripe stops sending it.
241 Ok(match handled {
242 g1t_contracts::Outcome::Ok(_) => reply(&json!({ "received": true }))?,
243 g1t_contracts::Outcome::Fail(failure) => {
244 reply(&json!({ "message": failure.message }))?.with_status(400)
245 }
246 })
247}
248
249async fn device_code(request: &mut Request, services: &Services) -> Result<Response> {
250 let body = json_body(request).await;
251 let started: DeviceStart = g1t_kit::call(
252 &services.identity,
253 "device_start",
254 &DeviceStartArgs {
255 client_name: body["client_name"].as_str().unwrap_or_default().to_owned(),
256 },
257 )
258 .await?;
259 reply(&json!({
260 "device_code": started.device_code,
261 "user_code": started.user_code,
Merge branch 'worktree-agent-aaf03bdceac799c89'262 "verification_uri": format!("{}/device", services.addresses.site),
263 "verification_uri_complete": format!("{}/device?code={}", services.addresses.site, started.user_code),
A clone answered from the pack cache is served before the free operation cap: it is not an operation264 "expires_in": started.expires_in,
265 "interval": started.interval,
266 }))
267}
268
269async fn device_token(request: &mut Request, services: &Services) -> Result<Response> {
270 let body = json_body(request).await;
271 let claim: DeviceClaim = g1t_kit::call(
272 &services.identity,
273 "device_claim",
274 &DeviceClaimArgs {
275 device_code: body["device_code"].as_str().unwrap_or_default().to_owned(),
276 },
277 )
278 .await?;
279 reply(&match claim {
280 DeviceClaim::Approved { token, user } => json!({
281 "status": "approved",
282 "token": token,
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar283 "display_username": user.display_username.clone().filter(|display| display.eq_ignore_ascii_case(&user.username)).unwrap_or_else(|| user.username.clone()),
A clone answered from the pack cache is served before the free operation cap: it is not an operation284 "username": user.username,
285 "verified": user.verified,
286 }),
287 DeviceClaim::Pending => json!({ "status": "pending" }),
288 DeviceClaim::Denied => json!({ "status": "denied" }),
289 DeviceClaim::Expired => json!({ "status": "expired" }),
290 })
291}
292
293/// A sandbox reporting on a run of an issue's commands, from before a pull
294/// request's checks were the workflows run on it.
295/// The run's own token, in the body, is the credential: it was given to
296/// that sandbox and to nothing else.
297async fn report_checks(
298 request: &mut Request,
299 services: &Services,
300 run_id: &str,
301) -> Result<Response> {
302 let body = json_body(request).await;
303 let reported: Outcome<CheckRun> = g1t_kit::call(
304 &services.work,
305 "report_checks",
306 &ReportChecksArgs {
307 run_id: run_id.to_owned(),
308 token: body["token"].as_str().unwrap_or_default().to_owned(),
309 results: serde_json::from_value(body["results"].clone()).unwrap_or_default(),
310 error: body["error"].as_str().map(str::to_owned),
311 skip: false,
312 },
313 )
314 .await?;
315 match reported {
316 Outcome::Ok(run) => reply(&json!({ "status": run.status })),
317 Outcome::Fail(refused) => failure(&refused),
318 }
319}
320
321/// A sandbox reporting one tested state of a merge queue. As with checks,
322/// the entry's own token is the credential.
323async fn report_queue(
324 request: &mut Request,
325 services: &Services,
326 entry_id: &str,
327) -> Result<Response> {
328 let body = json_body(request).await;
329 let reported: Outcome<QueueState> = g1t_kit::call(
330 &services.work,
331 "report_queue",
332 &ReportQueueArgs {
333 entry_id: entry_id.to_owned(),
334 token: body["token"].as_str().unwrap_or_default().to_owned(),
335 combined_commit: body["combinedCommit"].as_str().map(str::to_owned),
336 results: serde_json::from_value(body["results"].clone()).unwrap_or_default(),
337 error: body["error"].as_str().map(str::to_owned),
338 conflict_with: body["conflictWith"].as_u64().map(|n| n as u32),
339 conflicts: serde_json::from_value(body["conflicts"].clone()).unwrap_or_default(),
340 },
341 )
342 .await?;
343 match reported {
344 Outcome::Ok(state) => reply(&json!({ "state": state })),
345 Outcome::Fail(refused) => failure(&refused),
346 }
347}
348
349/// A sandbox reporting whether a pull request merges cleanly. As with
350/// checks, the probe's own token is the credential.
351async fn report_mergecheck(
352 request: &mut Request,
353 services: &Services,
354 pull_id: &str,
355) -> Result<Response> {
356 let body = json_body(request).await;
357 let reported: Outcome<Mergeable> = g1t_kit::call(
358 &services.work,
359 "report_mergecheck",
360 &ReportMergecheckArgs {
361 pull_id: pull_id.to_owned(),
362 token: body["token"].as_str().unwrap_or_default().to_owned(),
363 conflicts: serde_json::from_value(body["conflicts"].clone()).unwrap_or_default(),
364 error: body["error"].as_str().map(str::to_owned),
365 },
366 )
367 .await?;
368 match reported {
369 Outcome::Ok(state) => reply(&json!({ "mergeable": state })),
370 Outcome::Fail(refused) => failure(&refused),
371 }
372}
373
374/// A backup's sandbox, passed on to the repos service, which holds the
375/// job (services/repos/src/backups.rs; the flow is in
376/// `g1t_contracts::backups`):
377///
378/// - `POST /backups/{job}/spec`: what to cut, and a read-only git credential
379/// - `PUT /backups/{job}/parts/{n}`: one part of the bundle, as bytes
380/// - `POST /backups/{job}/complete` with `{ refs, size, sha256, parts, fetched_bytes }`
381/// - `POST /backups/{job}/fail` with `{ error, fetched_bytes }`
382///
383/// Bodies are passed through as they are: snake_case already, and a
384/// bundle's refs are keyed by ref names, which must not be converted.
385async fn backup_job(request: &mut Request, services: &Services, method: &str, path: &str) -> Result<Response> {
386 use g1t_contracts::backups::TOKEN_HEADER;
387 let token = request.headers().get(TOKEN_HEADER)?.unwrap_or_default();
388 let rest = path.trim_start_matches("/backups/");
389 let (job, action) = rest.split_once('/').unwrap_or((rest, ""));
390 if job.is_empty() || token.is_empty() {
391 return fail(FailureCode::Unauthenticated, "A backup job's token is required.");
392 }
393 if method == "PUT" && action.starts_with("parts/") {
394 let bytes = request.bytes().await?;
395 if bytes.len() as u64 > g1t_contracts::backups::PART_BYTES {
396 return fail(FailureCode::Invalid, "A part holds 32 MiB at most.");
397 }
398 let headers = worker::Headers::new();
399 headers.set(TOKEN_HEADER, &token)?;
400 let mut init = worker::RequestInit::new();
401 init.with_method(Method::Put)
402 .with_headers(headers)
403 .with_body(Some(worker::js_sys::Uint8Array::from(bytes.as_slice()).into()));
404 let forwarded = Request::new_with_init(&format!("https://repos/backups/{job}/{action}"), &init)?;
405 let mut answered = services.repos.fetch_request(forwarded).await?;
406 return outcome_as_given(answered.json().await?);
407 }
408 let rpc = match (method, action) {
409 ("POST", "spec") => "backup_spec",
410 ("POST", "complete") => "backup_complete",
411 ("POST", "fail") => "backup_fail",
412 _ => return fail(FailureCode::NotFound, "No such endpoint."),
413 };
414 let mut body = json_body(request).await;
415 if !body.is_object() {
416 body = json!({});
417 }
418 body["job_id"] = json!(job);
419 body["token"] = json!(token);
420 let answered: Value = g1t_kit::call(&services.repos, rpc, &body).await?;
421 outcome_as_given(answered)
422}
423
424/// An `Outcome` from a service whose keys are already the API's: the value,
425/// or the failure in the shape every endpoint uses.
426fn outcome_as_given(answered: Value) -> Result<Response> {
427 match serde_json::from_value::<Outcome<Value>>(answered)? {
428 Outcome::Ok(value) => Response::from_json(&value),
429 Outcome::Fail(refused) => failure(&refused),
430 }
431}
432
433async fn respond(mut request: Request, env: &Env) -> Result<Response> {
434 let method = method_name(request.method());
435 if method == "OPTIONS" {
436 return Ok(Response::empty()?.with_status(204));
437 }
438 let url = request.url()?;
439 // Paths carry no version. An earlier form began with `/v1`, which is
440 // still accepted so that nothing already written against it breaks.
441 let path = match url.path().strip_prefix("/v1") {
442 Some(rest) if rest.is_empty() || rest.starts_with('/') => rest.to_owned(),
443 _ => url.path().to_owned(),
444 };
445 let mut services = Services::new(env)?;
Merge branch 'worktree-agent-aaf03bdceac799c89'446 // MCP is a host of its own hosted, and may be a path on this one
447 // self-hosted (addresses.rs).
448 let on_mcp = services.addresses.mcp_path(&url).is_some();
A clone answered from the pack cache is served before the free operation cap: it is not an operation449
450 // Stripe reporting to billing. Signed with the secret of the endpoint
451 // billing registered; the body goes through exactly as received, since
452 // the signature covers its bytes.
453 if method == "POST" && !on_mcp && path == "/stripe/webhook" {
454 return receive_stripe(&mut request, env).await;
455 }
456
457 // Outside systems reporting to a connection. They sign what they send
458 // with the connection's own secret, which is not a g1t token, so this
459 // comes before anything that would read one.
460 if method == "POST" && !on_mcp
461 && let Some(id) = path.strip_prefix("/hooks/").filter(|id| !id.is_empty() && !id.contains('/')) {
462 return receive_hook(&mut request, &services, id).await;
463 }
464
465 // A sandbox building a deployment, reporting with its build's token,
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.466 // which is not a g1t token, or a container app's Worker reporting how
467 // long its container ran, with its own token. The body goes through as
468 // it is: it can carry a Worker's bundled code.
A clone answered from the pack cache is served before the free operation cap: it is not an operation469 if method == "POST" && !on_mcp
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.470 && let Some(rest) = path
471 .strip_prefix("/deployments/jobs/")
472 .map(|rest| format!("jobs/{rest}"))
473 .or_else(|| path.strip_prefix("/deployments/apps/").filter(|rest| rest.ends_with("/meter")).map(|rest| format!("apps/{rest}")))
A clone answered from the pack cache is served before the free operation cap: it is not an operation474 {
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.475 let target = format!("https://deployments/{rest}");
A clone answered from the pack cache is served before the free operation cap: it is not an operation476 let body = request.bytes().await?;
477 let headers = worker::Headers::new();
478 headers.set("content-type", "application/json")?;
479 let mut init = worker::RequestInit::new();
480 init.with_method(Method::Post)
481 .with_headers(headers)
482 .with_body(Some(worker::js_sys::Uint8Array::from(body.as_slice()).into()));
483 let mut answer = env
484 .service("DEPLOYMENTS")?
485 .fetch_request(Request::new_with_init(&target, &init)?)
486 .await?;
487 // A fresh response: a fetched one's headers cannot be changed, and
488 // every response gets the API's own on the way out.
489 let status = answer.status_code();
490 let bytes = answer.bytes().await?;
491 let bytes = match serde_json::from_slice::<Value>(&bytes) {
492 Ok(body) => serde_json::to_vec(&wire::snake_case(body))?,
493 Err(_) => bytes,
494 };
495 return Ok(Response::from_bytes(bytes)?
496 .with_status(status)
497 .with_headers({
498 let headers = worker::Headers::new();
499 headers.set("content-type", "application/json")?;
500 headers
501 }));
502 }
503
Actions: OIDC tokens, the toolkit's cache and artifact services, and artifacts in R2504 // The services GitHub's toolkit calls from inside a job, with its
505 // runtime token, and the links they hand out (toolkit.rs).
506 if !on_mcp && method == "POST"
507 && let Some(rest) = path.strip_prefix("/twirp/")
508 {
509 let (service, rpc) = rest.split_once('/').unwrap_or((rest, ""));
510 let (service, rpc) = (service.to_owned(), rpc.to_owned());
511 return toolkit::twirp(request, env, &services, &service, &rpc).await;
512 }
513 if !on_mcp && let Some(rest) = path.strip_prefix("/actions/toolkit/_apis/artifactcache/") {
514 let rest = rest.to_owned();
515 return toolkit::cache_v1(request, env, &services, method, &rest).await;
516 }
517 if !on_mcp && let Some(token) = path.strip_prefix("/actions/toolkit/blobs/") {
518 let token = token.to_owned();
519 return toolkit::blob(request, env, &services, method, &token).await;
520 }
521 // g1t as an OIDC issuer for workflow jobs (oidc.rs).
522 if !on_mcp && method == "GET" && path.starts_with("/actions/oidc/") {
523 return oidc::handle(&request, env, &services, &path).await;
524 }
525
A clone answered from the pack cache is served before the free operation cap: it is not an operation526 // A sandbox's artifacts and cache, with its job's token, which is not a
527 // g1t token either.
528 if !on_mcp
529 && let Some(rest) = path.strip_prefix("/actions/jobs/")
530 && (rest.contains("/artifacts") || rest.ends_with("/cache") || rest.contains("/cache/uploads"))
531 {
532 let rest = rest.to_owned();
533 return blobs::for_job(request, env, &services, method, &rest).await;
534 }
Merge branch 'main' into rm-agents535 // A self-hosted job's request to a brokered secret's host, passed to
536 // g1t to be filled in (broker.rs). The job's token is the credential.
537 if !on_mcp
538 && method == "POST"
539 && let Some(job) = broker::job_of(&path)
540 {
541 let job = job.to_owned();
542 return broker::relay(request, &services, &job).await;
543 }
A clone answered from the pack cache is served before the free operation cap: it is not an operation544
545 // A self-hosted runner, with a registration token or its own
546 // credential, neither of which is a g1t access token.
547 if method == "POST"
548 && !on_mcp
549 && path.starts_with("/runners/")
550 && let Some(response) = runners::handle(&mut request, &services, &path).await?
551 {
552 return Ok(response);
553 }
554
Merge branch 'worktree-agent-a8752162fea25f63f' into spend-guardrails555 // Per token, or per address without one (limits.rs).
556 if let Some(limited) = limits::limited(&request, env, method, &path, on_mcp).await? {
557 return Ok(limited);
558 }
A clone answered from the pack cache is served before the free operation cap: it is not an operation559 let viewer = match authenticate(&request, &services).await? {
560 Ok(viewer) => viewer,
Merge branch 'worktree-agent-a8752162fea25f63f' into spend-guardrails561 Err(refused) => return Ok(limits::wrong_token(&request, env, on_mcp).await?.unwrap_or(refused)),
A clone answered from the pack cache is served before the free operation cap: it is not an operation562 };
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)563 // A person who has not confirmed their email address: who they are,
564 // their addresses, and confirming one, nothing else (REST or MCP).
565 if viewer.as_ref().is_some_and(User::awaits_confirmation) && !pending_may(method, &path, on_mcp) {
566 return fail(
567 FailureCode::Forbidden,
568 &g1t_contracts::accounts::confirm_email_first(&services.addresses.site),
569 );
570 }
A clone answered from the pack cache is served before the free operation cap: it is not an operation571 services.audit = audit::AuditContext::of(&request, on_mcp);
The API and MCP server no longer offer g1t's own agents. g1t is what your coding agents work against, not an agent of its own, so the operations that drove hosted agents are gone: messaging an agent on a pull request (message_agent, take_messages, answer_message), assigning or delegating an issue to g1t (assign_issue, delegate), plans (plan_work, get_plan, apply_plan), the context hub (search_context, get_entity), model routes, agents' computers, AI Gateway requests, "Fix with g1t" on a security alert, and every Customize route (marketplaces, plugins, connectors). The callbacks the agent sandbox reported through (/agent-runs/*, /runs/:id/usage, /plans/:id, /reviews/:id) go with them, update_repo_settings stops accepting agent_review, max_revisions and hold_low_confidence, import_issue stops accepting assign, and an old agent token with no run behind it is refused.572 // A run's token: what it may do comes with it, on the composite
573 // identity identity resolved it to. Any other token of g1t's own
574 // principal (the agent tokens g1t no longer issues) does nothing.
A clone answered from the pack cache is served before the free operation cap: it is not an operation575 if let Some(acting) = viewer.as_ref().and_then(|viewer| viewer.acting.as_ref()) {
576 services.scope = Some(acting.scope.clone());
577 } else if viewer.as_ref().is_some_and(|viewer| viewer.kind == PrincipalKind::Agent) {
The API and MCP server no longer offer g1t's own agents. g1t is what your coding agents work against, not an agent of its own, so the operations that drove hosted agents are gone: messaging an agent on a pull request (message_agent, take_messages, answer_message), assigning or delegating an issue to g1t (assign_issue, delegate), plans (plan_work, get_plan, apply_plan), the context hub (search_context, get_entity), model routes, agents' computers, AI Gateway requests, "Fix with g1t" on a security alert, and every Customize route (marketplaces, plugins, connectors). The callbacks the agent sandbox reported through (/agent-runs/*, /runs/:id/usage, /plans/:id, /reviews/:id) go with them, update_repo_settings stops accepting agent_review, max_revisions and hold_low_confidence, import_issue stops accepting assign, and an old agent token with no run behind it is refused.578 return fail(FailureCode::Unauthenticated, "Invalid access token.");
A clone answered from the pack cache is served before the free operation cap: it is not an operation579 }
580 if let Some(response) = oauth::handle(&mut request, &services, method, &path).await? {
581 return Ok(response);
582 }
583 if on_mcp {
584 return mcp::handle(request, &services, &viewer).await;
585 }
586
Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009)587 // Workflow logs to download: a run's as a zip, a job's as text (logs.rs).
588 if method == "GET" {
589 let text = url.query_pairs().any(|(name, value)| name == "format" && value == "text");
590 if let Some(wanted) = logs::wanted(&path, text) {
591 return logs::download(&services, &viewer, wanted).await;
592 }
593 }
594
A clone answered from the pack cache is served before the free operation cap: it is not an operation595 match (method, path.trim_end_matches('/')) {
Merge branch 'worktree-agent-aaf03bdceac799c89'596 ("GET", "") => return reply(&index(&services.addresses)),
A clone answered from the pack cache is served before the free operation cap: it is not an operation597 ("GET", "/openapi.json") => return Response::from_json(&openapi::document()),
Actions: OIDC tokens, the toolkit's cache and artifact services, and artifacts in R2598 // One of a run's artifacts downloaded by name (the run's artifacts
599 // are listed by the REST route in rest.rs).
600 ("GET", path) if path.starts_with("/repos/") && path.contains("/actions/runs/") && path.contains("/artifacts/") => {
A clone answered from the pack cache is served before the free operation cap: it is not an operation601 let parts: Vec<&str> = path.trim_start_matches("/repos/").split('/').collect();
Actions: OIDC tokens, the toolkit's cache and artifact services, and artifacts in R2602 if let [owner, repo, "actions", "runs", run, "artifacts", name] = parts.as_slice() {
Merge branch 'worktree-agent-a3abfcce648e87dca'603 // A workflow job's token reaches its own repository only.
604 if viewer
605 .as_ref()
606 .and_then(|user| user.token.as_deref())
607 .is_some_and(|token| !token.reaches(&format!("{owner}/{repo}")))
608 {
609 return fail(FailureCode::NotFound, "No such run.");
610 }
Actions: OIDC tokens, the toolkit's cache and artifact services, and artifacts in R2611 return blobs::download(env, &services, &viewer, owner, repo, run, name).await;
A clone answered from the pack cache is served before the free operation cap: it is not an operation612 }
613 }
614 ("POST", "/device/code") => return device_code(&mut request, &services).await,
615 ("POST", "/device/token") => return device_token(&mut request, &services).await,
616 // Where a pull request lives, for a tool that knows only its fork.
617 ("GET", path) if path.starts_with("/pulls/") && !path[7..].contains('/') => {
618 let located: Outcome<Value> = g1t_kit::call(
619 &services.work,
620 "locate_pull",
621 &json!({ "id": &path[7..], "viewer": viewer }),
622 )
623 .await?;
624 return match located {
625 Outcome::Ok(value) => reply(&value),
626 Outcome::Fail(refused) => failure(&refused),
627 };
628 }
629 ("POST", path) if path.starts_with("/mergechecks/") => {
630 let pull_id = path.trim_start_matches("/mergechecks/").to_owned();
631 return report_mergecheck(&mut request, &services, &pull_id).await;
632 }
633 // A sandbox making a repository's nightly backup. The job's own
634 // token, in its header, is the credential.
635 (method, path) if path.starts_with("/backups/") => {
636 return backup_job(&mut request, &services, method, path).await;
637 }
638 ("POST", path) if path.starts_with("/queue/") => {
639 let entry_id = path.trim_start_matches("/queue/").to_owned();
640 return report_queue(&mut request, &services, &entry_id).await;
641 }
642 // A sandbox running a GitHub Actions job: fetching the job, and
643 // reporting how it goes. The job's own token is the credential.
Merge remote-tracking branch 'origin/claude/peaceful-franklin-7qtkwx'644 // A sandbox downloading an action's archive that g1t keeps
645 // (actions' registry.rs). The job's own token is the credential.
646 ("POST", path) if path.starts_with("/actions/jobs/") && path.ends_with("/archive") => {
647 let job = path.trim_start_matches("/actions/jobs/").trim_end_matches("/archive").to_owned();
648 let body = json_body(&mut request).await;
649 let kept: Outcome<Value> = g1t_kit::call(
650 &services.actions,
651 "job_action_archive",
652 &json!({ "job": job, "token": body["token"], "report": body["report"] }),
653 )
654 .await?;
655 let kept = match kept {
656 Outcome::Ok(kept) => kept,
657 Outcome::Fail(refused) => return failure(&refused),
658 };
659 let bucket = env.bucket("ACTIONS_CACHE")?;
660 let object = kept["object"].as_str().unwrap_or_default();
661 let Some(stored) = bucket.get(object).execute().await? else {
662 return fail(FailureCode::NotFound, "The archive is gone.");
663 };
664 let Some(body) = stored.body() else {
665 return fail(FailureCode::NotFound, "The archive is gone.");
666 };
667 let mut response = Response::from_body(body.response_body()?)?;
668 response.headers_mut().set("content-type", "application/gzip")?;
669 return Ok(response);
670 }
A clone answered from the pack cache is served before the free operation cap: it is not an operation671 ("POST", path) if path.starts_with("/actions/jobs/") => {
672 let rest = path.trim_start_matches("/actions/jobs/");
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts673 // `/action`: where to fetch another repository's action from (on
674 // g1t, with a read token for a private one, or GitHub).
675 let (job, method) = match (rest.strip_suffix("/spec"), rest.strip_suffix("/action")) {
676 (Some(job), _) => (job.to_owned(), "job_spec"),
677 (_, Some(job)) => (job.to_owned(), "job_action"),
678 _ => (rest.to_owned(), "job_report"),
A clone answered from the pack cache is served before the free operation cap: it is not an operation679 };
680 let body = json_body(&mut request).await;
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.681 // `rotate`: a harness that takes a new token with the spec and
682 // lets go of the one it asked with (plan.rs `job_spec`).
683 let rotate = method == "job_spec" && body["rotate"].as_bool() == Some(true);
Merge branch 'main' into rm-agents684 // `broker`: a self-hosted harness that passes its job's
685 // requests to brokered hosts through g1t (broker.rs).
686 let broker = method == "job_spec" && body["broker"].as_bool() == Some(true);
A clone answered from the pack cache is served before the free operation cap: it is not an operation687 let answered: Outcome<Value> = g1t_kit::call(
688 &services.actions,
689 method,
Merge branch 'main' into rm-agents690 &json!({ "job": job, "token": body["token"], "report": body["report"], "rotate": rotate, "broker": broker }),
A clone answered from the pack cache is served before the free operation cap: it is not an operation691 )
692 .await?;
693 return match answered {
694 // A job's spec is the workflow and its contexts as GitHub
695 // has them; only g1t's own keys around them are converted.
Actions: OIDC tokens, the toolkit's cache and artifact services, and artifacts in R2696 Outcome::Ok(value) => {
697 let mut spec = wire::snake_case_keeping(value, JOB_SPEC_AS_GIVEN);
698 with_runtime(&mut spec, &services.addresses.api, oidc::configured(env));
699 Response::from_json(&spec)
700 }
A clone answered from the pack cache is served before the free operation cap: it is not an operation701 Outcome::Fail(refused) => failure(&refused),
702 };
703 }
704 ("POST", path) if path.starts_with("/checks/") => {
705 let run_id = path.trim_start_matches("/checks/").to_owned();
706 return report_checks(&mut request, &services, &run_id).await;
707 }
708 _ => {}
709 }
710
711 let query: Vec<(String, String)> = url
712 .query_pairs()
713 .map(|(name, value)| (name.into_owned(), value.into_owned()))
714 .collect();
715 let body = if method == "GET" {
716 Value::Null
717 } else {
718 snake_case_keys(json_body(&mut request).await)
719 };
720 let Some((route, input)) = rest::resolve(method, &path, &query, body) else {
721 return fail(FailureCode::NotFound, "No such endpoint.");
722 };
723 match audit::run(route.op, &services, &viewer, &input).await? {
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.724 // A download is a redirect to its signed link, as GitHub's is: an
725 // artifact's, and a repository's tarball and zipball.
726 Outcome::Ok(value) if route.op.redirects() => {
Actions: OIDC tokens, the toolkit's cache and artifact services, and artifacts in R2727 match value["url"].as_str().and_then(|url| worker::Url::parse(url).ok()) {
728 Some(url) => Response::redirect_with_status(url, 302),
729 None => reply(&value),
730 }
731 }
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts732 // A deleted comment has nothing to say, as GitHub's says nothing.
733 Outcome::Ok(_) if route.no_content() => Ok(Response::empty()?.with_status(204)),
Projects have Observability: their requests, logs, traces, metrics and errors, from apps on g1t.page with nothing to set up, and from software anywhere else over OpenTelemetry.734 Outcome::Ok(value) => Response::from_json(&wire::snake_case_keeping(value, route.op.as_given())),
A clone answered from the pack cache is served before the free operation cap: it is not an operation735 // A token without the scope a call needs is told which one.
736 Outcome::Fail(refused) => match (refused.code, audit::missing_scope(route.op, &viewer, &input)) {
737 (FailureCode::Forbidden, Some(scope)) => Ok(reply(&json!({
738 "error": {
739 "code": refused.code,
740 "message": refused.message,
741 "needed_scope": scope.as_str(),
742 }
743 }))?
744 .with_status(403)),
745 _ => failure(&refused),
746 },
747 }
748}
749
750/// Request bodies take the same keys as the MCP tools, `snake_case`, as
751/// responses use; the `camelCase` spelling is accepted too.
752fn snake_case_keys(body: Value) -> Value {
753 let Value::Object(fields) = body else {
754 return body;
755 };
756 let mut out = serde_json::Map::new();
757 for (key, value) in fields {
758 let mut snake = String::with_capacity(key.len() + 4);
759 for c in key.chars() {
760 if c.is_ascii_uppercase() {
761 snake.push('_');
762 snake.push(c.to_ascii_lowercase());
763 } else {
764 snake.push(c);
765 }
766 }
767 // A key given in both spellings keeps the snake_case one.
768 if snake != key && out.contains_key(&snake) {
769 continue;
770 }
771 out.insert(snake, value);
772 }
773 Value::Object(out)
774}
775
776#[cfg(test)]
777mod tests {
778 use super::snake_case_keys;
779 use serde_json::json;
780
781 #[test]
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)782 fn an_unconfirmed_account_may_only_see_itself_and_confirm_its_address() {
783 assert!(super::pending_may("GET", "/user", false));
784 assert!(super::pending_may("GET", "/user/emails/", false));
785 assert!(super::pending_may("POST", "/user/emails/confirm", false));
786 assert!(!super::pending_may("POST", "/user/emails", false));
787 assert!(!super::pending_may("POST", "/workspaces", false));
788 assert!(!super::pending_may("GET", "/repos/acme/rocket", false));
789 assert!(!super::pending_may("POST", "/user/emails/confirm", true));
790 assert!(!super::pending_may("POST", "/", true));
791 }
792
793 #[test]
Actions: OIDC tokens, the toolkit's cache and artifact services, and artifacts in R2794 fn a_job_spec_gets_the_toolkits_variables() {
795 // As the actions service sends it, converted as the API does.
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.796 let sent = json!({ "variables": { "GITHUB_SHA": "abc" }, "runtime": { "token": "h.p.s", "idToken": true }, "nextToken": "n" });
Actions: OIDC tokens, the toolkit's cache and artifact services, and artifacts in R2797 let mut spec = g1t_kit::wire::snake_case_keeping(sent.clone(), super::JOB_SPEC_AS_GIVEN);
798 super::with_runtime(&mut spec, "https://api.g1t.sh", true);
799 assert!(spec.get("runtime").is_none(), "the runner never sees it");
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.800 // The token that replaces the one the harness asked with.
801 assert_eq!(spec["next_token"], "n");
Actions: OIDC tokens, the toolkit's cache and artifact services, and artifacts in R2802 let vars = &spec["variables"];
803 assert_eq!(vars["GITHUB_SHA"], "abc");
804 assert_eq!(vars["ACTIONS_RUNTIME_TOKEN"], "h.p.s");
805 assert_eq!(vars["ACTIONS_CACHE_URL"], "https://api.g1t.sh/actions/toolkit/");
806 assert_eq!(vars["ACTIONS_ID_TOKEN_REQUEST_TOKEN"], "h.p.s");
807 // No OIDC key here: no OIDC variables, whatever the job may do.
808 let mut spec = g1t_kit::wire::snake_case_keeping(sent, super::JOB_SPEC_AS_GIVEN);
809 super::with_runtime(&mut spec, "https://api.g1t.sh", false);
810 assert!(spec["variables"].get("ACTIONS_ID_TOKEN_REQUEST_URL").is_none());
811 assert_eq!(spec["variables"]["ACTIONS_RESULTS_URL"], "https://api.g1t.sh/");
812 }
813
814 #[test]
A clone answered from the pack cache is served before the free operation cap: it is not an operation815 fn camel_case_keys_are_accepted() {
816 assert_eq!(
817 snake_case_keys(json!({ "countAgentApprovals": false, "title": "x" })),
818 json!({ "count_agent_approvals": false, "title": "x" })
819 );
820 }
821
822 #[test]
823 fn snake_case_wins_when_both_are_given() {
824 assert_eq!(
825 snake_case_keys(json!({ "keep_issue_open": true, "keepIssueOpen": false })),
826 json!({ "keep_issue_open": true })
827 );
828 }
829}
830
831// The API is called from browsers too: the reference's explorer, and apps
832// built on g1t. It carries no cookies, so any origin may call it.
Projects have Observability: their requests, logs, traces, metrics and errors, from apps on g1t.page with nothing to set up, and from software anywhere else over OpenTelemetry.833/// The signal an OTLP path sends (`/otlp/v1/traces`, `logs` or `metrics`),
834/// if it is one.
835fn otlp_signal(path: &str) -> Option<&'static str> {
836 let path = path.strip_prefix("/v1").filter(|rest| rest.starts_with('/')).unwrap_or(path);
837 match path.trim_end_matches('/') {
838 "/otlp/v1/traces" => Some("traces"),
839 "/otlp/v1/logs" => Some("logs"),
840 "/otlp/v1/metrics" => Some("metrics"),
841 _ => None,
842 }
843}
844
845/// OpenTelemetry sent to the API's own address: a self-hosted install's
846/// only door, and a second one hosted. It goes to the telemetry service as
847/// it came, which checks the project's ingest token itself (not a g1t
848/// token), and its answer comes back as it is, CORS headers included.
849async fn forward_otlp(mut request: Request, env: &Env, signal: &str) -> Result<Response> {
850 let method = request.method();
851 let headers = worker::Headers::new();
852 for name in ["authorization", "x-g1t-token", "content-type", "content-encoding"] {
853 if let Some(value) = request.headers().get(name)? {
854 headers.set(name, &value)?;
855 }
856 }
857 let mut init = worker::RequestInit::new();
858 init.with_method(method.clone()).with_headers(headers);
859 if method == Method::Post {
860 let body = request.bytes().await?;
861 init.with_body(Some(worker::js_sys::Uint8Array::from(body.as_slice()).into()));
862 }
863 let target = format!("https://telemetry/v1/{signal}");
864 let mut answer = env.service("TELEMETRY")?.fetch_request(Request::new_with_init(&target, &init)?).await?;
865 // A fresh response, with the service's headers: a fetched one's cannot
866 // be changed, and nothing should be added to them.
867 let status = answer.status_code();
868 let headers = worker::Headers::new();
869 for (name, value) in answer.headers().entries() {
870 headers.set(&name, &value)?;
871 }
872 let bytes = answer.bytes().await?;
873 let response = if bytes.is_empty() { Response::empty()? } else { Response::from_bytes(bytes)? };
874 Ok(response.with_status(status).with_headers(headers))
875}
876
A clone answered from the pack cache is served before the free operation cap: it is not an operation877#[event(fetch)]
878async fn fetch(request: Request, env: Env, _ctx: Context) -> Result<Response> {
Projects have Observability: their requests, logs, traces, metrics and errors, from apps on g1t.page with nothing to set up, and from software anywhere else over OpenTelemetry.879 // OTLP passes through untouched, before any g1t token is read.
880 if let Some(signal) = otlp_signal(&request.path()) {
881 return forward_otlp(request, &env, signal).await;
882 }
A clone answered from the pack cache is served before the free operation cap: it is not an operation883 let mut response = respond(request, &env).await?;
884 let headers = response.headers_mut();
885 headers.set("access-control-allow-origin", "*")?;
886 headers.set(
887 "access-control-allow-headers",
888 "authorization, content-type",
889 )?;
890 headers.set("access-control-allow-methods", "GET, POST, PATCH, OPTIONS")?;
Merge branch 'worktree-agent-a8752162fea25f63f' into spend-guardrails891 headers.set("access-control-expose-headers", "www-authenticate, retry-after")?;
A clone answered from the pack cache is served before the free operation cap: it is not an operation892 Ok(response)
893}

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