Skip to content
1,516 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.

Download ZIP deflates sixteen files at a time, within a Worker's memory1//! REST: each route maps an HTTP request onto one operation.
2
3use serde_json::{Map, Value};
4
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb975use crate::about::AboutOp;
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.6use crate::run_artifacts::ArtifactsOp;
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.7use crate::archives::ArchiveOp;
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca8use crate::deploy_keys::DeployKeysOp;
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb979use crate::deployments::DeploymentsOp;
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.10use crate::telemetry::TelemetryOp;
Merge packages: roles, Actions access, source label, soft delete, API11use crate::packages::PackagesOp;
Merge main into Artifacts Phase 212use crate::folios::FoliosOp;
Merge branch 'worktree-agent-a3abfcce648e87dca'13use crate::protection::ProtectionOp;
API and MCP for a workspace's personal access token rules, members' tokens and approvals14use crate::token_policy::TokenOp;
Download ZIP deflates sixteen files at a time, within a Worker's memory15use crate::operations::Op;
Merge checks: statuses and check runs on every commit16use crate::checks::ChecksOp;
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge17use crate::rules::RulesOp;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar18use crate::security::SecurityOp;
Download ZIP deflates sixteen files at a time, within a Worker's memory19
20pub struct Route {
21 pub method: &'static str,
22 /// Segments starting with `:` are parameters.
23 pub path: &'static str,
24 pub op: Op,
25 /// Query parameters the route reads, as `(name in the URL, input name)`.
26 pub query: &'static [(&'static str, &'static str)],
27}
28
29const fn route(
30 method: &'static str,
31 path: &'static str,
32 op: Op,
33 query: &'static [(&'static str, &'static str)],
34) -> Route {
35 Route {
36 method,
37 path,
38 op,
39 query,
40 }
41}
42
43pub const ROUTES: &[Route] = &[
44 route("GET", "/user", Op::Whoami, &[]),
45 route("POST", "/workspaces", Op::CreateWorkspace, &[]),
Merge branch 'worktree-agent-ad7c6d88d93adc817'46 route("GET", "/workspaces/:workspace", Op::GetWorkspace, &[]),
Download ZIP deflates sixteen files at a time, within a Worker's memory47 route("DELETE", "/workspaces/:workspace", Op::DeleteWorkspace, &[]),
48 route("GET", "/user/emails", Op::ListEmails, &[]),
49 route("POST", "/user/emails", Op::AddEmail, &[]),
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)50 route("POST", "/user/emails/confirm", Op::ConfirmEmail, &[]),
Download ZIP deflates sixteen files at a time, within a Worker's memory51 route("DELETE", "/user/emails/:email", Op::RemoveEmail, &[]),
52 route("PATCH", "/user/email-settings", Op::UpdateEmailSettings, &[]),
Merge workspace invitations: nobody joins a workspace without saying yes, people are found by username, your own invites can bring someone in, and nobody is left without a workspace (identity 0040)53 // Invitations to workspaces waiting for your answer.
54 route("GET", "/user/invitations", Op::ListInvitations, &[]),
55 route("POST", "/user/invitations/:id/accept", Op::AcceptInvitation, &[]),
56 route("POST", "/user/invitations/:id/decline", Op::DeclineInvitation, &[]),
Download ZIP deflates sixteen files at a time, within a Worker's memory57 route("GET", "/workspaces/:workspace/invitations", Op::ListWorkspaceInvites, &[]),
API and MCP for a workspace's personal access token rules, members' tokens and approvals58 // A workspace's rules for personal access tokens, and its members' tokens.
59 route("GET", "/workspaces/:workspace/personal-access-token-policy", Op::Tokens(TokenOp::GetTokenPolicy), &[]),
60 route("PATCH", "/workspaces/:workspace/personal-access-token-policy", Op::Tokens(TokenOp::SetTokenPolicy), &[]),
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers61 route("GET", "/workspaces/:workspace/personal-access-tokens", Op::Tokens(TokenOp::ListMemberTokens), &[]),
API and MCP for a workspace's personal access token rules, members' tokens and approvals62 route("POST", "/workspaces/:workspace/personal-access-tokens/:id", Op::Tokens(TokenOp::RevokeMemberToken), &[]),
63 route("GET", "/workspaces/:workspace/personal-access-token-requests", Op::Tokens(TokenOp::ListTokenRequests), &[]),
64 route("POST", "/workspaces/:workspace/personal-access-token-requests/:id", Op::Tokens(TokenOp::ReviewTokenRequest), &[]),
Download ZIP deflates sixteen files at a time, within a Worker's memory65 route("POST", "/workspaces/:workspace/invitations", Op::InviteMember, &[]),
66 route("DELETE", "/workspaces/:workspace/invitations/:id", Op::RevokeWorkspaceInvite, &[]),
67 // Who has access. GitHub's addresses, but for adding someone, which
68 // takes an email address as well as a username.
69 route("GET", "/repos/:owner/:name/collaborators", Op::ListCollaborators, &[]),
70 route("POST", "/repos/:owner/:name/collaborators", Op::AddCollaborator, &[]),
71 route("PATCH", "/repos/:owner/:name/collaborators/:username", Op::UpdateCollaborator, &[]),
72 route("DELETE", "/repos/:owner/:name/collaborators/:username", Op::RemoveCollaborator, &[]),
73 route(
74 "GET",
75 "/repos/:owner/:name/collaborators/:username/permission",
76 Op::GetCollaboratorPermission,
77 &[],
78 ),
79 route("GET", "/repos/:owner/:name/invitations", Op::ListRepoInvitations, &[]),
80 route("DELETE", "/repos/:owner/:name/invitations/:id", Op::RevokeRepoInvitation, &[]),
81 route("GET", "/user/repository_invitations", Op::ListMyRepoInvitations, &[]),
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca82 // Deploy keys, at GitHub's addresses.
83 route("GET", "/repos/:owner/:name/keys", Op::DeployKeys(DeployKeysOp::ListDeployKeys), &[]),
84 route("POST", "/repos/:owner/:name/keys", Op::DeployKeys(DeployKeysOp::CreateDeployKey), &[]),
85 route("GET", "/repos/:owner/:name/keys/:id", Op::DeployKeys(DeployKeysOp::GetDeployKey), &[]),
86 route("DELETE", "/repos/:owner/:name/keys/:id", Op::DeployKeys(DeployKeysOp::DeleteDeployKey), &[]),
API: notifications over REST and MCP, with notifications scopes87 // Your notifications: threads, marking them, and what you subscribe
88 // to and watch. GitHub's addresses, with g1t's saved and snoozed.
89 route("GET", "/notifications", Op::ListNotifications, &[("all", "all"), ("participating", "participating"), ("view", "view"), ("reason", "reason"), ("severity", "severity"), ("since", "since"), ("before", "before"), ("cursor", "cursor"), ("per_page", "per_page")]),
90 route("PUT", "/notifications", Op::MarkNotificationsRead, &[]),
91 route("GET", "/notifications/threads/:id", Op::GetNotificationThread, &[]),
92 route("PATCH", "/notifications/threads/:id", Op::MarkThreadRead, &[]),
93 route("DELETE", "/notifications/threads/:id", Op::MarkThreadDone, &[]),
94 route("PUT", "/notifications/threads/:id/saved", Op::SaveThread, &[]),
95 route("DELETE", "/notifications/threads/:id/saved", Op::SaveThread, &[]),
96 route("PUT", "/notifications/threads/:id/snooze", Op::SnoozeThread, &[]),
97 route("DELETE", "/notifications/threads/:id/snooze", Op::SnoozeThread, &[]),
98 route("GET", "/notifications/threads/:id/subscription", Op::GetThreadSubscription, &[]),
99 route("PUT", "/notifications/threads/:id/subscription", Op::SetThreadSubscription, &[]),
100 route("DELETE", "/notifications/threads/:id/subscription", Op::DeleteThreadSubscription, &[]),
101 route("GET", "/repos/:owner/:name/notifications", Op::ListNotifications, &[("all", "all"), ("participating", "participating"), ("view", "view"), ("reason", "reason"), ("severity", "severity"), ("since", "since"), ("before", "before"), ("cursor", "cursor"), ("per_page", "per_page")]),
102 route("PUT", "/repos/:owner/:name/notifications", Op::MarkNotificationsRead, &[]),
103 route("GET", "/repos/:owner/:name/subscription", Op::GetRepoSubscription, &[]),
104 route("PUT", "/repos/:owner/:name/subscription", Op::SetRepoSubscription, &[]),
105 route("DELETE", "/repos/:owner/:name/subscription", Op::DeleteRepoSubscription, &[]),
106 route("GET", "/repos/:owner/:name/issues/:number/subscription", Op::GetThreadSubscription, &[]),
107 route("PUT", "/repos/:owner/:name/issues/:number/subscription", Op::SetThreadSubscription, &[]),
108 route("DELETE", "/repos/:owner/:name/issues/:number/subscription", Op::DeleteThreadSubscription, &[]),
109 route("GET", "/user/subscriptions", Op::ListWatchedRepos, &[]),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97110 // Stars: yours, and who starred a repository.
111 route("GET", "/user/starred", Op::About(AboutOp::ListStarred), &[]),
112 route("GET", "/user/starred/:owner/:name", Op::About(AboutOp::CheckStarred), &[]),
113 route("PUT", "/user/starred/:owner/:name", Op::About(AboutOp::Star), &[]),
114 route("DELETE", "/user/starred/:owner/:name", Op::About(AboutOp::Unstar), &[]),
115 route("GET", "/repos/:owner/:name/stargazers", Op::About(AboutOp::ListStargazers), &[("page", "page")]),
116 // What the default branch says about a repository, kept by commit.
117 route("GET", "/repos/:owner/:name/languages", Op::About(AboutOp::GetLanguages), &[]),
118 route("GET", "/repos/:owner/:name/contributors", Op::About(AboutOp::ListContributors), &[]),
119 route("GET", "/repos/:owner/:name/license", Op::About(AboutOp::GetLicense), &[]),
120 // Releases: `latest` and `tags/…` before an id.
121 route("GET", "/repos/:owner/:name/releases", Op::About(AboutOp::ListReleases), &[]),
122 route("POST", "/repos/:owner/:name/releases", Op::About(AboutOp::CreateRelease), &[]),
123 route("GET", "/repos/:owner/:name/releases/latest", Op::About(AboutOp::GetLatestRelease), &[]),
124 route("GET", "/repos/:owner/:name/releases/tags/:tag", Op::About(AboutOp::GetReleaseByTag), &[]),
125 route("GET", "/repos/:owner/:name/releases/:id", Op::About(AboutOp::GetRelease), &[]),
126 route("PATCH", "/repos/:owner/:name/releases/:id", Op::About(AboutOp::UpdateRelease), &[]),
127 route("DELETE", "/repos/:owner/:name/releases/:id", Op::About(AboutOp::DeleteRelease), &[]),
API: pinned projects over REST and MCP128 // Your pinned projects in a workspace, in your order.
129 route("GET", "/user/pinned_projects/:workspace", Op::ListPinnedProjects, &[]),
130 route("PUT", "/user/pinned_projects/:workspace", Op::ReorderPinnedProjects, &[]),
131 route("PUT", "/user/pinned_projects/:workspace/:project", Op::PinProject, &[]),
132 route("DELETE", "/user/pinned_projects/:workspace/:project", Op::UnpinProject, &[]),
Download ZIP deflates sixteen files at a time, within a Worker's memory133 route("PATCH", "/user/repository_invitations/:id", Op::AcceptRepoInvitation, &[]),
134 route("DELETE", "/user/repository_invitations/:id", Op::DeclineRepoInvitation, &[]),
135 route("PATCH", "/workspaces/:workspace", Op::UpdateWorkspace, &[]),
Merge main (membership, two-factor, GitHub repo roles) into tokens136 // Members and owners: GitHub's organization members, by username.
137 route("GET", "/workspaces/:workspace/members", Op::ListMembers, &[]),
138 route("PATCH", "/workspaces/:workspace/members/:username", Op::UpdateMember, &[]),
139 route("DELETE", "/workspaces/:workspace/members/:username", Op::RemoveMember, &[]),
140 route("POST", "/workspaces/:workspace/transfer_ownership", Op::TransferOwnership, &[]),
141 route("DELETE", "/user/memberships/:workspace", Op::LeaveWorkspace, &[]),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97142 // A workspace's projects: what each is, where it runs, its links.
143 route("GET", "/workspaces/:workspace/projects", Op::ListProjects, &[]),
144 route("GET", "/workspaces/:workspace/projects/:project", Op::GetProject, &[]),
145 route("PATCH", "/workspaces/:workspace/projects/:project", Op::UpdateProject, &[]),
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.146 // A project's observability: what its software did, and how it is collected.
147 route(
148 "GET",
149 "/workspaces/:workspace/projects/:project/observability/overview",
150 Op::Telemetry(TelemetryOp::GetOverview),
151 &[("from", "from"), ("to", "to"), ("environment", "environment"), ("service", "service"), ("deployment_id", "deployment_id"), ("step", "step")],
152 ),
153 route(
154 "GET",
155 "/workspaces/:workspace/projects/:project/observability/requests",
156 Op::Telemetry(TelemetryOp::ListRequests),
157 &[
158 ("from", "from"),
159 ("to", "to"),
160 ("q", "q"),
161 ("status", "status"),
162 ("errors_only", "errors_only"),
163 ("route", "route"),
164 ("min_duration_ms", "min_duration_ms"),
165 ("environment", "environment"),
166 ("service", "service"),
167 ("deployment_id", "deployment_id"),
168 ("cursor", "cursor"),
169 ("limit", "limit"),
170 ],
171 ),
172 route("GET", "/workspaces/:workspace/projects/:project/observability/requests/:id", Op::Telemetry(TelemetryOp::GetRequest), &[]),
173 route(
174 "GET",
175 "/workspaces/:workspace/projects/:project/observability/logs",
176 Op::Telemetry(TelemetryOp::ListLogs),
177 &[
178 ("from", "from"),
179 ("to", "to"),
180 ("q", "q"),
181 ("level", "level"),
182 ("trace_id", "trace_id"),
183 ("request_id", "request_id"),
184 ("environment", "environment"),
185 ("service", "service"),
186 ("deployment_id", "deployment_id"),
187 ("cursor", "cursor"),
188 ("limit", "limit"),
189 ],
190 ),
191 route(
192 "GET",
193 "/workspaces/:workspace/projects/:project/observability/traces",
194 Op::Telemetry(TelemetryOp::ListTraces),
195 &[
196 ("from", "from"),
197 ("to", "to"),
198 ("q", "q"),
199 ("errors_only", "errors_only"),
200 ("min_duration_ms", "min_duration_ms"),
201 ("environment", "environment"),
202 ("service", "service"),
203 ("cursor", "cursor"),
204 ("limit", "limit"),
205 ],
206 ),
207 route("GET", "/workspaces/:workspace/projects/:project/observability/traces/:trace_id", Op::Telemetry(TelemetryOp::GetTrace), &[]),
208 route(
209 "GET",
210 "/workspaces/:workspace/projects/:project/observability/errors",
211 Op::Telemetry(TelemetryOp::ListErrors),
212 &[("state", "state"), ("environment", "environment"), ("service", "service"), ("from", "from"), ("to", "to")],
213 ),
214 route("PATCH", "/workspaces/:workspace/projects/:project/observability/errors/:fingerprint", Op::Telemetry(TelemetryOp::UpdateError), &[]),
215 route("GET", "/workspaces/:workspace/projects/:project/observability/metrics", Op::Telemetry(TelemetryOp::ListMetrics), &[]),
216 route(
217 "GET",
218 "/workspaces/:workspace/projects/:project/observability/metrics/:metric",
219 Op::Telemetry(TelemetryOp::GetMetric),
220 &[("from", "from"), ("to", "to"), ("aggregate", "aggregate"), ("group_by", "group_by"), ("step", "step"), ("service", "service"), ("environment", "environment")],
221 ),
222 route("GET", "/workspaces/:workspace/projects/:project/observability/settings", Op::Telemetry(TelemetryOp::GetSettings), &[]),
223 route("PATCH", "/workspaces/:workspace/projects/:project/observability/settings", Op::Telemetry(TelemetryOp::UpdateSettings), &[]),
224 route("GET", "/workspaces/:workspace/projects/:project/observability/tokens", Op::Telemetry(TelemetryOp::ListTokens), &[]),
225 route("POST", "/workspaces/:workspace/projects/:project/observability/tokens", Op::Telemetry(TelemetryOp::CreateToken), &[]),
226 route("DELETE", "/workspaces/:workspace/projects/:project/observability/tokens/:id", Op::Telemetry(TelemetryOp::DeleteToken), &[]),
227 route("GET", "/workspaces/:workspace/projects/:project/observability/exports", Op::Telemetry(TelemetryOp::ListExports), &[]),
228 route("POST", "/workspaces/:workspace/projects/:project/observability/exports", Op::Telemetry(TelemetryOp::CreateExport), &[]),
229 route("PATCH", "/workspaces/:workspace/projects/:project/observability/exports/:id", Op::Telemetry(TelemetryOp::UpdateExport), &[]),
230 route("DELETE", "/workspaces/:workspace/projects/:project/observability/exports/:id", Op::Telemetry(TelemetryOp::DeleteExport), &[]),
231 route("GET", "/workspaces/:workspace/projects/:project/observability/usage", Op::Telemetry(TelemetryOp::GetUsage), &[]),
Download ZIP deflates sixteen files at a time, within a Worker's memory232 route("PUT", "/workspaces/:workspace/base_permission", Op::SetBasePermission, &[]),
233 route(
234 "GET",
235 "/workspaces/:workspace/outside_collaborators",
236 Op::ListOutsideCollaborators,
237 &[],
238 ),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar239 // Teams: a workspace's groups of members, with roles on repositories.
240 route("GET", "/workspaces/:workspace/teams", Op::ListTeams, &[("q", "query")]),
241 route("POST", "/workspaces/:workspace/teams", Op::CreateTeam, &[]),
242 route("GET", "/workspaces/:workspace/teams/:team", Op::GetTeam, &[]),
243 route("PATCH", "/workspaces/:workspace/teams/:team", Op::UpdateTeam, &[]),
244 route("DELETE", "/workspaces/:workspace/teams/:team", Op::DeleteTeam, &[]),
245 route(
246 "GET",
247 "/workspaces/:workspace/teams/:team/members",
248 Op::ListTeamMembers,
249 &[("include_child_teams", "include_child_teams")],
250 ),
251 route("PUT", "/workspaces/:workspace/teams/:team/members/:username", Op::SetTeamMember, &[]),
252 route("DELETE", "/workspaces/:workspace/teams/:team/members/:username", Op::RemoveTeamMember, &[]),
253 route("GET", "/workspaces/:workspace/teams/:team/teams", Op::ListChildTeams, &[]),
254 route("GET", "/workspaces/:workspace/teams/:team/repos", Op::ListTeamRepos, &[]),
255 route("PUT", "/workspaces/:workspace/teams/:team/repos/:repo", Op::SetTeamRepo, &[]),
256 route("DELETE", "/workspaces/:workspace/teams/:team/repos/:repo", Op::RemoveTeamRepo, &[]),
257 route(
258 "PUT",
259 "/workspaces/:workspace/teams/:team/review_assignment",
260 Op::SetTeamReviewAssignment,
261 &[],
262 ),
263 route("GET", "/workspaces/:workspace/members/:username/teams", Op::ListUserTeams, &[]),
Billing, integrations, security and identity no longer serve g1t's own agents. Billing stops metering agent runs, model tokens and AI Gateway requests, drops its model catalogue and price-keeper model prices, and stops selling AI credit, which only ever paid for those; credit bought earlier still pays toward what is owed. A usage report, statement or invoice that holds an old agent or model meter shows it as "Other usage" rather than failing, and the get_ai_credit and buy_ai_credit API operations and MCP billing actions go. Integrations no longer connects model providers or routes models, and no longer assigns imported issues or alerts to g1t; a model provider already connected can still be disconnected. Security drops "Fix with g1t". Identity no longer creates agent tokens or puts agents on teams, a team's lead is a person, and run credentials are issued only for checks, the merge queue, merge checks, deployments and bumps; tokens and credentials already issued keep working until they expire. The reserved names g1t and g1t-agent stay reserved. No tables are dropped.264 // A workspace's billing: usage, budget and invoices.
Usage, Billing settings and prepaid AI credit; fixes from the UX audit265 route(
266 "GET",
267 "/workspaces/:workspace/usage",
268 Op::GetUsage,
269 &[("from", "from"), ("until", "until"), ("products", "products"), ("projects", "projects"), ("group_by", "group_by")],
270 ),
271 route("GET", "/workspaces/:workspace/budget", Op::GetBudget, &[]),
272 route("PUT", "/workspaces/:workspace/budget", Op::SetBudget, &[]),
273 route("GET", "/workspaces/:workspace/invoices", Op::ListInvoices, &[]),
274 route("GET", "/workspaces/:workspace/billing_details", Op::GetBillingDetails, &[]),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar275 // Code owners: the CODEOWNERS file, checked.
276 route("GET", "/repos/:owner/:name/codeowners/errors", Op::GetCodeownersErrors, &[("ref", "ref")]),
Download ZIP deflates sixteen files at a time, within a Worker's memory277 // Security alerts: secrets and vulnerable dependencies.
278 route(
279 "GET",
280 "/repos/:owner/:name/security/alerts",
281 Op::ListSecurityAlerts,
282 &[("state", "state"), ("kind", "kind")],
283 ),
284 route("POST", "/repos/:owner/:name/security/alerts/:id/dismiss", Op::DismissSecurityAlert, &[]),
285 route("POST", "/repos/:owner/:name/security/alerts/:id/reopen", Op::ReopenSecurityAlert, &[]),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar286 // The security suite: secret scanning, code scanning, vulnerability
287 // alerts and the supply chain, at the common addresses.
288 route("GET", "/repos/:owner/:name/secret-scanning/alerts", Op::Security(SecurityOp::ListSecretAlerts), &[("state", "state"), ("secret_type", "secret_type"), ("validity", "validity"), ("bypassed", "bypassed")]),
289 route("GET", "/workspaces/:workspace/secret-scanning/alerts", Op::Security(SecurityOp::ListSecretAlerts), &[("state", "state"), ("secret_type", "secret_type"), ("validity", "validity"), ("bypassed", "bypassed")]),
290 route("GET", "/repos/:owner/:name/secret-scanning/alerts/:id", Op::Security(SecurityOp::GetSecretAlert), &[]),
291 route("PATCH", "/repos/:owner/:name/secret-scanning/alerts/:id", Op::Security(SecurityOp::UpdateSecretAlert), &[]),
292 route("GET", "/repos/:owner/:name/secret-scanning/alerts/:id/locations", Op::Security(SecurityOp::ListSecretLocations), &[]),
293 route("POST", "/repos/:owner/:name/secret-scanning/alerts/:id/bypass", Op::Security(SecurityOp::BypassPushProtection), &[]),
294 route("POST", "/repos/:owner/:name/secret-scanning/alerts/:id/validity", Op::Security(SecurityOp::CheckSecretValidity), &[]),
295 route("GET", "/workspaces/:workspace/secret-scanning/bypass-requests", Op::Security(SecurityOp::ListBypassRequests), &[("state", "state"), ("repo", "repo")]),
296 route("PATCH", "/workspaces/:workspace/secret-scanning/bypass-requests/:id", Op::Security(SecurityOp::ReviewBypassRequest), &[]),
297 route("POST", "/repos/:owner/:name/secret-scanning/custom-patterns/dry-run", Op::Security(SecurityOp::DryRunCustomPattern), &[]),
298 route("POST", "/workspaces/:workspace/secret-scanning/custom-patterns/dry-run", Op::Security(SecurityOp::DryRunCustomPattern), &[]),
299 route("GET", "/repos/:owner/:name/secret-scanning/custom-patterns", Op::Security(SecurityOp::ListCustomPatterns), &[]),
300 route("GET", "/workspaces/:workspace/secret-scanning/custom-patterns", Op::Security(SecurityOp::ListCustomPatterns), &[]),
301 route("POST", "/repos/:owner/:name/secret-scanning/custom-patterns", Op::Security(SecurityOp::CreateCustomPattern), &[]),
302 route("POST", "/workspaces/:workspace/secret-scanning/custom-patterns", Op::Security(SecurityOp::CreateCustomPattern), &[]),
303 route("PATCH", "/repos/:owner/:name/secret-scanning/custom-patterns/:id", Op::Security(SecurityOp::UpdateCustomPattern), &[]),
304 route("PATCH", "/workspaces/:workspace/secret-scanning/custom-patterns/:id", Op::Security(SecurityOp::UpdateCustomPattern), &[]),
305 route("DELETE", "/repos/:owner/:name/secret-scanning/custom-patterns/:id", Op::Security(SecurityOp::DeleteCustomPattern), &[]),
306 route("DELETE", "/workspaces/:workspace/secret-scanning/custom-patterns/:id", Op::Security(SecurityOp::DeleteCustomPattern), &[]),
307 route("GET", "/repos/:owner/:name/code-scanning/alerts", Op::Security(SecurityOp::ListCodeAlerts), &[("state", "state"), ("severity", "severity"), ("tool", "tool"), ("rule_id", "rule_id")]),
308 route("GET", "/workspaces/:workspace/code-scanning/alerts", Op::Security(SecurityOp::ListCodeAlerts), &[("state", "state"), ("severity", "severity"), ("tool", "tool"), ("rule_id", "rule_id")]),
309 route("GET", "/repos/:owner/:name/code-scanning/alerts/:number", Op::Security(SecurityOp::GetCodeAlert), &[]),
310 route("PATCH", "/repos/:owner/:name/code-scanning/alerts/:number", Op::Security(SecurityOp::UpdateCodeAlert), &[]),
311 route("GET", "/repos/:owner/:name/code-scanning/analyses", Op::Security(SecurityOp::ListAnalyses), &[]),
312 route("POST", "/repos/:owner/:name/code-scanning/sarifs", Op::Security(SecurityOp::UploadSarif), &[]),
313 route("GET", "/repos/:owner/:name/code-scanning/sarifs/:id", Op::Security(SecurityOp::GetSarifUpload), &[]),
314 route("GET", "/repos/:owner/:name/vulnerability-alerts", Op::Security(SecurityOp::ListVulnerabilityAlerts), &[("state", "state"), ("severity", "severity"), ("ecosystem", "ecosystem"), ("package", "package")]),
315 route("GET", "/workspaces/:workspace/vulnerability-alerts", Op::Security(SecurityOp::ListVulnerabilityAlerts), &[("state", "state"), ("severity", "severity"), ("ecosystem", "ecosystem"), ("package", "package")]),
316 route("GET", "/repos/:owner/:name/vulnerability-alerts/:id", Op::Security(SecurityOp::GetVulnerabilityAlert), &[]),
317 route("PATCH", "/repos/:owner/:name/vulnerability-alerts/:id", Op::Security(SecurityOp::UpdateVulnerabilityAlert), &[]),
318 route("GET", "/repos/:owner/:name/dependency-graph", Op::Security(SecurityOp::GetDependencyGraph), &[]),
319 route("GET", "/repos/:owner/:name/dependency-graph/sbom", Op::Security(SecurityOp::GetSbom), &[]),
320 route("GET", "/repos/:owner/:name/dependency-graph/compare/:basehead", Op::Security(SecurityOp::CompareDependencies), &[]),
321 route("GET", "/repos/:owner/:name/security/settings", Op::Security(SecurityOp::GetSettings), &[]),
322 route("PATCH", "/repos/:owner/:name/security/settings", Op::Security(SecurityOp::UpdateSettings), &[]),
323 route("GET", "/workspaces/:workspace/security/settings", Op::Security(SecurityOp::GetWorkspaceSettings), &[]),
324 route("PATCH", "/workspaces/:workspace/security/settings", Op::Security(SecurityOp::UpdateWorkspaceSettings), &[]),
325 route("GET", "/workspaces/:workspace/security/overview", Op::Security(SecurityOp::GetOverview), &[("days", "days")]),
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9326 // Reporting: a repository's advisories, and the vulnerability reports sent to its maintainers.
327 route("GET", "/repos/:owner/:name/security/advisories", Op::Security(SecurityOp::ListAdvisories), &[("state", "state")]),
328 route("POST", "/repos/:owner/:name/security/advisories", Op::Security(SecurityOp::CreateAdvisory), &[]),
329 route("GET", "/repos/:owner/:name/security/advisories/:id", Op::Security(SecurityOp::GetAdvisory), &[]),
330 route("PATCH", "/repos/:owner/:name/security/advisories/:id", Op::Security(SecurityOp::UpdateAdvisory), &[]),
331 route("POST", "/repos/:owner/:name/security/advisories/:id/state", Op::Security(SecurityOp::SetAdvisoryState), &[]),
332 route("GET", "/repos/:owner/:name/security/reports", Op::Security(SecurityOp::ListReports), &[("state", "state")]),
333 route("POST", "/repos/:owner/:name/security/reports", Op::Security(SecurityOp::ReportVulnerability), &[]),
334 route("PATCH", "/repos/:owner/:name/security/reports/:id", Op::Security(SecurityOp::ReviewReport), &[]),
Download ZIP deflates sixteen files at a time, within a Worker's memory335 route("GET", "/repos", Op::ListRepos, &[("q", "query")]),
336 route(
337 "GET",
338 "/search",
339 Op::Search,
340 &[("q", "query"), ("type", "type"), ("page", "page"), ("per_page", "per_page")],
341 ),
342 route("POST", "/repos", Op::CreateRepo, &[]),
343 route("GET", "/repos/:owner/:name", Op::GetRepo, &[]),
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.344 // A branch, tag or commit as one archive: a redirect to a link good for
345 // ten minutes, as GitHub's go to codeload.
346 route("GET", "/repos/:owner/:name/tarball/:ref", Op::Archives(ArchiveOp::DownloadTarball), &[]),
347 route("GET", "/repos/:owner/:name/zipball/:ref", Op::Archives(ArchiveOp::DownloadZipball), &[]),
Download ZIP deflates sixteen files at a time, within a Worker's memory348 route("PATCH", "/repos/:owner/:name", Op::UpdateRepo, &[]),
349 route("POST", "/repos/:owner/:name/transfer", Op::TransferRepo, &[]),
350 route("DELETE", "/repos/:owner/:name", Op::DeleteRepo, &[]),
351 route(
352 "GET",
353 "/workspaces/:workspace/repos/deleted",
354 Op::ListDeletedRepos,
355 &[],
356 ),
357 route("POST", "/repos/:owner/:name/restore", Op::RestoreRepo, &[]),
358 route("POST", "/repos/:owner/:name/purge", Op::PurgeRepo, &[]),
359 route("POST", "/repos/:owner/:name/rename", Op::RenameRepo, &[]),
360 route("POST", "/repos/:owner/:name/archive", Op::ArchiveRepo, &[]),
361 route("POST", "/repos/:owner/:name/unarchive", Op::UnarchiveRepo, &[]),
362 route(
363 "POST",
364 "/repos/:owner/:name/visibility",
365 Op::SetRepoVisibility,
366 &[],
367 ),
368 route(
369 "POST",
370 "/repos/:owner/:name/branches/:branch/rename",
371 Op::RenameBranch,
372 &[],
373 ),
The Branches page does things to branches now. It has tabs (Overview, Yours, Active, Stale, All, kept in the address as ?tab=) and search; Overview shows the default branch, then the first five of your branches, the active ones and the stale ones, each linking to the rest. Each branch is a row with its name in a code chip with a copy button, who made its latest commit and when, that commit's checks, behind and ahead counts with bars drawn against the largest shown, and its pull request (the open one, else the newest merged or closed) with its state; on a phone the columns fold under the name. Yours is branches whose latest commit is the viewer's; stale is no commit in 90 days.374 route("POST", "/repos/:owner/:name/branches", Op::CreateBranch, &[]),
375 route("DELETE", "/repos/:owner/:name/branches/:branch", Op::DeleteBranch, &[("head", "head")]),
Download ZIP deflates sixteen files at a time, within a Worker's memory376 route(
377 "GET",
378 "/repos/:owner/:name/settings",
379 Op::GetRepoSettings,
380 &[],
381 ),
382 route(
383 "PATCH",
384 "/repos/:owner/:name/settings",
385 Op::UpdateRepoSettings,
386 &[],
387 ),
388 route("GET", "/repos/:owner/:name/check-names", Op::ListCheckNames, &[]),
Merge checks: statuses and check runs on every commit389 // Checks: GitHub's addresses for statuses, check runs and check suites.
390 route("POST", "/repos/:owner/:name/statuses/:sha", Op::Checks(ChecksOp::CreateCommitStatus), &[]),
391 route("GET", "/repos/:owner/:name/commits/:ref/statuses", Op::Checks(ChecksOp::ListCommitStatuses), &[]),
392 route("GET", "/repos/:owner/:name/commits/:ref/status", Op::Checks(ChecksOp::GetCombinedStatus), &[]),
393 route(
394 "GET",
395 "/repos/:owner/:name/commits/:ref/check-runs",
396 Op::Checks(ChecksOp::ListCheckRunsForRef),
397 &[("check_name", "check_name"), ("status", "status"), ("app", "app"), ("filter", "filter")],
398 ),
399 route(
400 "GET",
401 "/repos/:owner/:name/commits/:ref/check-suites",
402 Op::Checks(ChecksOp::ListCheckSuitesForRef),
403 &[("app", "app"), ("check_name", "check_name")],
404 ),
405 route("POST", "/repos/:owner/:name/check-runs", Op::Checks(ChecksOp::CreateCheckRun), &[]),
406 route("GET", "/repos/:owner/:name/check-runs/:id", Op::Checks(ChecksOp::GetCheckRun), &[]),
407 route("PATCH", "/repos/:owner/:name/check-runs/:id", Op::Checks(ChecksOp::UpdateCheckRun), &[]),
408 route("GET", "/repos/:owner/:name/check-runs/:id/annotations", Op::Checks(ChecksOp::ListCheckRunAnnotations), &[]),
409 route("POST", "/repos/:owner/:name/check-runs/:id/rerequest", Op::Checks(ChecksOp::RerequestCheckRun), &[]),
410 route("GET", "/repos/:owner/:name/check-suites/:id", Op::Checks(ChecksOp::GetCheckSuite), &[]),
411 route("POST", "/repos/:owner/:name/check-suites/:id/rerequest", Op::Checks(ChecksOp::RerequestCheckSuite), &[]),
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge412 // Rulesets: a repository's, a workspace's, the rules of one branch,
413 // and how they judged pushes and merges.
414 route("GET", "/repos/:owner/:name/rulesets", Op::Rules(RulesOp::ListRepoRulesets), &[("include_parents", "include_parents")]),
415 route("POST", "/repos/:owner/:name/rulesets", Op::Rules(RulesOp::CreateRepoRuleset), &[]),
416 route("GET", "/repos/:owner/:name/rulesets/:id", Op::Rules(RulesOp::GetRepoRuleset), &[]),
417 route("PUT", "/repos/:owner/:name/rulesets/:id", Op::Rules(RulesOp::UpdateRepoRuleset), &[]),
418 route("DELETE", "/repos/:owner/:name/rulesets/:id", Op::Rules(RulesOp::DeleteRepoRuleset), &[]),
419 route("GET", "/repos/:owner/:name/rules/branches/:branch", Op::Rules(RulesOp::GetBranchRules), &[("target", "target")]),
420 route(
421 "GET",
422 "/repos/:owner/:name/rules/evaluations",
423 Op::Rules(RulesOp::ListRuleEvaluations),
424 &[("ruleset_id", "ruleset_id"), ("verdict", "verdict"), ("problems_only", "problems_only"), ("before", "before"), ("limit", "limit")],
425 ),
426 route("GET", "/workspaces/:workspace/rulesets", Op::Rules(RulesOp::ListWorkspaceRulesets), &[]),
427 route("POST", "/workspaces/:workspace/rulesets", Op::Rules(RulesOp::CreateWorkspaceRuleset), &[]),
428 route("GET", "/workspaces/:workspace/rulesets/:id", Op::Rules(RulesOp::GetWorkspaceRuleset), &[]),
429 route("PUT", "/workspaces/:workspace/rulesets/:id", Op::Rules(RulesOp::UpdateWorkspaceRuleset), &[]),
430 route("DELETE", "/workspaces/:workspace/rulesets/:id", Op::Rules(RulesOp::DeleteWorkspaceRuleset), &[]),
431 route(
432 "GET",
433 "/workspaces/:workspace/rules/evaluations",
434 Op::Rules(RulesOp::ListWorkspaceRuleEvaluations),
435 &[("ruleset_id", "ruleset_id"), ("verdict", "verdict"), ("problems_only", "problems_only"), ("before", "before"), ("limit", "limit")],
436 ),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97437 // Deployments wherever they run, their statuses, and environments.
438 route(
439 "GET",
440 "/repos/:owner/:name/deployments",
441 Op::Deployments(DeploymentsOp::ListDeployments),
442 &[("environment", "environment"), ("ref", "ref"), ("sha", "sha"), ("task", "task"), ("state", "state"), ("source", "source"), ("creator", "creator"), ("page", "page"), ("per_page", "per_page")],
443 ),
444 route("POST", "/repos/:owner/:name/deployments", Op::Deployments(DeploymentsOp::CreateDeployment), &[]),
Merge branch 'worktree-agent-a4d35ee70c50683d0' into claude/cool-newton-7nm8r9445 // A g1t Actions job opening a g1t.page build it runs itself.
446 route("POST", "/repos/:owner/:name/deployments/builds", Op::Deployments(DeploymentsOp::StartDeploymentBuild), &[]),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97447 route("GET", "/repos/:owner/:name/deployments/:id", Op::Deployments(DeploymentsOp::GetDeployment), &[]),
448 route("GET", "/repos/:owner/:name/deployments/:id/statuses", Op::Deployments(DeploymentsOp::ListDeploymentStatuses), &[]),
449 route("POST", "/repos/:owner/:name/deployments/:id/statuses", Op::Deployments(DeploymentsOp::CreateDeploymentStatus), &[]),
450 route("GET", "/repos/:owner/:name/environments", Op::Deployments(DeploymentsOp::ListEnvironments), &[]),
451 route("GET", "/repos/:owner/:name/environments/:environment", Op::Deployments(DeploymentsOp::GetEnvironment), &[]),
Merge branch 'worktree-agent-a3abfcce648e87dca'452 // Environments' protection rules, and the runs they hold.
453 route("PUT", "/repos/:owner/:name/environments/:environment", Op::Protection(ProtectionOp::UpdateEnvironment), &[]),
454 route("DELETE", "/repos/:owner/:name/environments/:environment", Op::Protection(ProtectionOp::DeleteEnvironment), &[]),
455 route(
456 "GET",
457 "/repos/:owner/:name/actions/runs/:id/pending_deployments",
458 Op::Protection(ProtectionOp::GetPendingDeployments),
459 &[],
460 ),
461 route(
462 "POST",
463 "/repos/:owner/:name/actions/runs/:id/pending_deployments",
464 Op::Protection(ProtectionOp::ReviewPendingDeployments),
465 &[],
466 ),
467 route("POST", "/repos/:owner/:name/actions/runs/:id/approve", Op::Protection(ProtectionOp::ApproveWorkflowRun), &[]),
468 route("GET", "/repos/:owner/:name/actions/permissions/workflow", Op::Protection(ProtectionOp::GetWorkflowPermissions), &[]),
469 route("PUT", "/repos/:owner/:name/actions/permissions/workflow", Op::Protection(ProtectionOp::SetWorkflowPermissions), &[]),
470 route(
471 "GET",
472 "/repos/:owner/:name/actions/permissions/fork-pr-contributor-approval",
473 Op::Protection(ProtectionOp::GetForkPrApproval),
474 &[],
475 ),
476 route(
477 "PUT",
478 "/repos/:owner/:name/actions/permissions/fork-pr-contributor-approval",
479 Op::Protection(ProtectionOp::SetForkPrApproval),
480 &[],
481 ),
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts482 route("GET", "/repos/:owner/:name/actions/permissions/access", Op::Protection(ProtectionOp::GetActionsAccess), &[]),
483 route("PUT", "/repos/:owner/:name/actions/permissions/access", Op::Protection(ProtectionOp::SetActionsAccess), &[]),
Merge branch 'worktree-agent-a3abfcce648e87dca'484 route("POST", "/repos/:owner/:name/dispatches", Op::Protection(ProtectionOp::CreateRepositoryDispatch), &[]),
485 route(
486 "GET",
487 "/workspaces/:workspace/actions/permissions/workflow",
488 Op::Protection(ProtectionOp::GetWorkspaceWorkflowPermissions),
489 &[],
490 ),
491 route(
492 "PUT",
493 "/workspaces/:workspace/actions/permissions/workflow",
494 Op::Protection(ProtectionOp::SetWorkspaceWorkflowPermissions),
495 &[],
496 ),
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.497 route(
498 "GET",
499 "/workspaces/:workspace/actions/managed-workflows",
500 Op::Protection(ProtectionOp::ListWorkspaceManagedWorkflows),
501 &[],
502 ),
503 route(
504 "POST",
505 "/workspaces/:workspace/actions/managed-workflows",
506 Op::Protection(ProtectionOp::CreateWorkspaceManagedWorkflow),
507 &[],
508 ),
509 route(
510 "PATCH",
511 "/workspaces/:workspace/actions/managed-workflows/:workflow",
512 Op::Protection(ProtectionOp::UpdateWorkspaceManagedWorkflow),
513 &[],
514 ),
515 route(
516 "DELETE",
517 "/workspaces/:workspace/actions/managed-workflows/:workflow",
518 Op::Protection(ProtectionOp::DeleteWorkspaceManagedWorkflow),
519 &[],
520 ),
Download ZIP deflates sixteen files at a time, within a Worker's memory521 route("GET", "/repos/:owner/:name/queue", Op::GetMergeQueue, &[]),
522 route("POST", "/repos/:owner/:name/memory", Op::Remember, &[]),
523 route(
524 "GET",
525 "/repos/:owner/:name/memory",
526 Op::Recall,
527 &[("q", "query"), ("limit", "limit")],
528 ),
529 route(
530 "GET",
531 "/repos/:owner/:name/events",
532 Op::ListEvents,
533 &[("before", "before")],
534 ),
535 route("GET", "/repos/:owner/:name/labels", Op::ListLabels, &[]),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar536 route("POST", "/repos/:owner/:name/labels", Op::CreateLabel, &[]),
537 route("POST", "/repos/:owner/:name/labels/defaults", Op::AddDefaultLabels, &[]),
538 route("PATCH", "/repos/:owner/:name/labels/:label", Op::UpdateLabel, &[]),
539 route("DELETE", "/repos/:owner/:name/labels/:label", Op::DeleteLabel, &[]),
540 route("GET", "/repos/:owner/:name/issues/:number/labels", Op::ListIssueLabels, &[]),
541 route("POST", "/repos/:owner/:name/issues/:number/labels", Op::AddIssueLabels, &[]),
542 route("PUT", "/repos/:owner/:name/issues/:number/labels", Op::SetIssueLabels, &[]),
543 route("DELETE", "/repos/:owner/:name/issues/:number/labels", Op::RemoveIssueLabels, &[]),
544 route("DELETE", "/repos/:owner/:name/issues/:number/labels/:label", Op::RemoveIssueLabels, &[]),
545 route("GET", "/repos/:owner/:name/milestones", Op::ListMilestones, &[("state", "state")]),
546 route("POST", "/repos/:owner/:name/milestones", Op::CreateMilestone, &[]),
547 route("GET", "/repos/:owner/:name/milestones/:milestone", Op::GetMilestone, &[]),
548 route("PATCH", "/repos/:owner/:name/milestones/:milestone", Op::UpdateMilestone, &[]),
549 route("DELETE", "/repos/:owner/:name/milestones/:milestone", Op::DeleteMilestone, &[]),
Download ZIP deflates sixteen files at a time, within a Worker's memory550 route(
551 "GET",
552 "/repos/:owner/:name/issues",
553 Op::ListIssues,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar554 &[("state", "state"), ("label", "label"), ("milestone", "milestone")],
Download ZIP deflates sixteen files at a time, within a Worker's memory555 ),
556 route("POST", "/repos/:owner/:name/issues", Op::CreateIssue, &[]),
557 route(
558 "GET",
559 "/repos/:owner/:name/issues/:number",
560 Op::GetIssue,
561 &[],
562 ),
563 route(
564 "PATCH",
565 "/repos/:owner/:name/issues/:number",
566 Op::UpdateIssue,
567 &[],
568 ),
569 route(
570 "POST",
571 "/repos/:owner/:name/issues/:number/close",
572 Op::CloseIssue,
573 &[],
574 ),
575 route(
576 "POST",
577 "/repos/:owner/:name/issues/:number/reopen",
578 Op::ReopenIssue,
579 &[],
580 ),
581 route(
582 "POST",
583 "/repos/:owner/:name/issues/import",
584 Op::ImportIssue,
585 &[],
586 ),
587 route(
588 "GET",
589 "/repos/:owner/:name/context",
590 Op::GetContext,
591 &[("reference", "reference")],
592 ),
593 route(
594 "GET",
595 "/workspaces/:workspace/integrations",
596 Op::ListIntegrations,
597 &[],
598 ),
599 route(
600 "POST",
601 "/workspaces/:workspace/integrations",
602 Op::ConnectIntegration,
603 &[],
604 ),
605 route(
606 "GET",
607 "/repos/:owner/:name/hooks",
608 Op::ListWebhooks,
609 &[],
610 ),
611 route(
612 "POST",
613 "/repos/:owner/:name/hooks",
614 Op::CreateWebhook,
615 &[],
616 ),
617 route(
618 "PATCH",
619 "/repos/:owner/:name/hooks/:id",
620 Op::UpdateWebhook,
621 &[],
622 ),
623 route(
624 "DELETE",
625 "/repos/:owner/:name/hooks/:id",
626 Op::DeleteWebhook,
627 &[],
628 ),
629 route(
630 "POST",
631 "/repos/:owner/:name/hooks/:id/pings",
632 Op::PingWebhook,
633 &[],
634 ),
635 route(
636 "GET",
637 "/repos/:owner/:name/hooks/:id/deliveries",
638 Op::ListWebhookDeliveries,
639 &[],
640 ),
641 route(
642 "POST",
643 "/repos/:owner/:name/hooks/:id/deliveries/:delivery/redeliver",
644 Op::RedeliverWebhook,
645 &[],
646 ),
647 route(
648 "GET",
649 "/workspaces/:workspace/hooks",
650 Op::ListWebhooks,
651 &[],
652 ),
653 route(
654 "POST",
655 "/workspaces/:workspace/hooks",
656 Op::CreateWebhook,
657 &[],
658 ),
659 route(
660 "PATCH",
661 "/workspaces/:workspace/hooks/:id",
662 Op::UpdateWebhook,
663 &[],
664 ),
665 route(
666 "DELETE",
667 "/workspaces/:workspace/hooks/:id",
668 Op::DeleteWebhook,
669 &[],
670 ),
671 route(
672 "POST",
673 "/workspaces/:workspace/hooks/:id/pings",
674 Op::PingWebhook,
675 &[],
676 ),
677 route(
678 "GET",
679 "/workspaces/:workspace/hooks/:id/deliveries",
680 Op::ListWebhookDeliveries,
681 &[],
682 ),
683 route(
684 "POST",
685 "/workspaces/:workspace/hooks/:id/deliveries/:delivery/redeliver",
686 Op::RedeliverWebhook,
687 &[],
688 ),
689 route(
AI Gateway: OpenAI's format, open models, and your own providers690 "PATCH",
691 "/workspaces/:workspace/integrations/:id",
692 Op::UpdateIntegration,
693 &[],
694 ),
695 route(
Download ZIP deflates sixteen files at a time, within a Worker's memory696 "DELETE",
697 "/workspaces/:workspace/integrations/:id",
698 Op::DisconnectIntegration,
699 &[],
700 ),
701 route(
702 "POST",
703 "/workspaces/:workspace/integrations/:id/test",
704 Op::TestIntegration,
705 &[],
706 ),
707 route(
708 "GET",
709 "/repos/:owner/:name/actions/workflows",
710 Op::ListWorkflows,
711 &[],
712 ),
713 route(
714 "GET",
715 "/repos/:owner/:name/actions/workflows/:workflow/runs",
716 Op::ListWorkflowRuns,
717 &[("branch", "branch"), ("event", "event"), ("per_page", "limit")],
718 ),
719 route(
720 "POST",
721 "/repos/:owner/:name/actions/workflows/:workflow/dispatches",
722 Op::DispatchWorkflow,
723 &[],
724 ),
725 route(
726 "PATCH",
727 "/repos/:owner/:name/actions/workflows/:workflow",
728 Op::UpdateWorkflow,
729 &[],
730 ),
731 route(
732 "PUT",
733 "/repos/:owner/:name/actions/workflows/:workflow/enable",
734 Op::UpdateWorkflow,
735 &[],
736 ),
737 route(
738 "PUT",
739 "/repos/:owner/:name/actions/workflows/:workflow/disable",
740 Op::UpdateWorkflow,
741 &[],
742 ),
743 route(
744 "GET",
745 "/repos/:owner/:name/actions/runs",
746 Op::ListWorkflowRuns,
747 &[("workflow", "workflow"), ("branch", "branch"), ("event", "event"), ("pull", "pull"), ("head_sha", "sha"), ("per_page", "limit")],
748 ),
749 route(
750 "GET",
751 "/repos/:owner/:name/actions/runs/:id",
752 Op::GetWorkflowRun,
753 &[],
754 ),
755 route(
Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009)756 "GET",
757 "/repos/:owner/:name/actions/runs/:id/attempts/:attempt",
758 Op::GetWorkflowRun,
759 &[],
760 ),
761 route(
Download ZIP deflates sixteen files at a time, within a Worker's memory762 "POST",
763 "/repos/:owner/:name/actions/runs/:id/cancel",
764 Op::CancelWorkflowRun,
765 &[],
766 ),
767 route(
768 "POST",
Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009)769 "/repos/:owner/:name/actions/runs/:id/force-cancel",
770 Op::CancelWorkflowRun,
771 &[],
772 ),
773 route(
774 "POST",
Download ZIP deflates sixteen files at a time, within a Worker's memory775 "/repos/:owner/:name/actions/runs/:id/rerun",
776 Op::RerunWorkflowRun,
777 &[],
778 ),
779 route(
780 "POST",
781 "/repos/:owner/:name/actions/runs/:id/rerun-failed-jobs",
782 Op::RerunWorkflowRun,
783 &[],
784 ),
785 route(
Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009)786 "POST",
787 "/repos/:owner/:name/actions/jobs/:job/rerun",
788 Op::RerunWorkflowRun,
789 &[],
790 ),
791 route(
Download ZIP deflates sixteen files at a time, within a Worker's memory792 "GET",
793 "/repos/:owner/:name/actions/jobs/:job/logs",
794 Op::GetJobLogs,
795 &[("after", "after")],
796 ),
Merge packages: roles, Actions access, source label, soft delete, API797 // Packages, at GitHub's addresses with the workspace in place of the
798 // organization. A name with a slash is one URL-encoded segment.
799 route("GET", "/workspaces/:workspace/packages", Op::Packages(PackagesOp::ListPackages), &[("package_type", "package_type"), ("q", "q"), ("state", "state")]),
800 route("GET", "/workspaces/:workspace/packages/:package_type/:package_name", Op::Packages(PackagesOp::GetPackage), &[]),
801 route("PATCH", "/workspaces/:workspace/packages/:package_type/:package_name", Op::Packages(PackagesOp::UpdatePackage), &[]),
802 route("DELETE", "/workspaces/:workspace/packages/:package_type/:package_name", Op::Packages(PackagesOp::DeletePackage), &[]),
803 route("POST", "/workspaces/:workspace/packages/:package_type/:package_name/restore", Op::Packages(PackagesOp::RestorePackage), &[]),
804 route("GET", "/workspaces/:workspace/packages/:package_type/:package_name/versions", Op::Packages(PackagesOp::ListVersions), &[("state", "state")]),
805 route("GET", "/workspaces/:workspace/packages/:package_type/:package_name/versions/:version_id", Op::Packages(PackagesOp::GetVersion), &[]),
806 route("DELETE", "/workspaces/:workspace/packages/:package_type/:package_name/versions/:version_id", Op::Packages(PackagesOp::DeleteVersion), &[]),
807 route("POST", "/workspaces/:workspace/packages/:package_type/:package_name/versions/:version_id/restore", Op::Packages(PackagesOp::RestoreVersion), &[]),
808 route("PUT", "/workspaces/:workspace/packages/:package_type/:package_name/repository", Op::Packages(PackagesOp::LinkPackage), &[]),
809 route("DELETE", "/workspaces/:workspace/packages/:package_type/:package_name/repository", Op::Packages(PackagesOp::UnlinkPackage), &[]),
810 route("GET", "/workspaces/:workspace/packages/:package_type/:package_name/access", Op::Packages(PackagesOp::ListAccess), &[]),
811 route("PUT", "/workspaces/:workspace/packages/:package_type/:package_name/access", Op::Packages(PackagesOp::SetAccess), &[]),
812 route("DELETE", "/workspaces/:workspace/packages/:package_type/:package_name/access", Op::Packages(PackagesOp::RemoveAccess), &[("username", "username"), ("team", "team")]),
813 route("GET", "/workspaces/:workspace/packages/:package_type/:package_name/actions-access", Op::Packages(PackagesOp::ListActionsAccess), &[]),
814 route("PUT", "/workspaces/:workspace/packages/:package_type/:package_name/actions-access", Op::Packages(PackagesOp::SetActionsAccess), &[]),
815 route("DELETE", "/workspaces/:workspace/packages/:package_type/:package_name/actions-access/:repository", Op::Packages(PackagesOp::RemoveActionsAccess), &[]),
Actions: OIDC tokens, the toolkit's cache and artifact services, and artifacts in R2816 // Artifacts, at GitHub's addresses. `…/zip` answers with a redirect to
817 // a signed link (lib.rs).
818 route("GET", "/repos/:owner/:name/actions/artifacts", Op::Artifacts(ArtifactsOp::ListArtifacts), &[("name", "name"), ("page", "page"), ("per_page", "per_page")]),
819 route("GET", "/repos/:owner/:name/actions/runs/:id/artifacts", Op::Artifacts(ArtifactsOp::ListRunArtifacts), &[("name", "name")]),
820 route("GET", "/repos/:owner/:name/actions/artifacts/:id", Op::Artifacts(ArtifactsOp::GetArtifact), &[]),
821 route("GET", "/repos/:owner/:name/actions/artifacts/:id/zip", Op::Artifacts(ArtifactsOp::DownloadArtifact), &[]),
822 route("DELETE", "/repos/:owner/:name/actions/artifacts/:id", Op::Artifacts(ArtifactsOp::DeleteArtifact), &[]),
823 route("GET", "/repos/:owner/:name/actions/permissions/artifact-and-log-retention", Op::Artifacts(ArtifactsOp::GetArtifactRetention), &[]),
824 route("PUT", "/repos/:owner/:name/actions/permissions/artifact-and-log-retention", Op::Artifacts(ArtifactsOp::SetArtifactRetention), &[]),
Download ZIP deflates sixteen files at a time, within a Worker's memory825 route(
826 "GET",
827 "/repos/:owner/:name/actions/secrets",
828 Op::ListActionsSecrets,
829 &[],
830 ),
831 route(
832 "PUT",
833 "/repos/:owner/:name/actions/secrets/:setting",
834 Op::SetActionsSecret,
835 &[],
836 ),
837 route(
838 "DELETE",
839 "/repos/:owner/:name/actions/secrets/:setting",
840 Op::DeleteActionsSecret,
841 &[],
842 ),
843 route(
844 "GET",
845 "/repos/:owner/:name/actions/variables",
846 Op::ListActionsVariables,
847 &[],
848 ),
849 route(
850 "POST",
851 "/repos/:owner/:name/actions/variables",
852 Op::SetActionsVariable,
853 &[],
854 ),
855 route(
856 "PATCH",
857 "/repos/:owner/:name/actions/variables/:setting",
858 Op::SetActionsVariable,
859 &[],
860 ),
861 route(
862 "DELETE",
863 "/repos/:owner/:name/actions/variables/:setting",
864 Op::DeleteActionsVariable,
865 &[],
866 ),
867 route(
868 "GET",
869 "/workspaces/:workspace/actions/secrets",
870 Op::ListActionsSecrets,
871 &[],
872 ),
873 route(
874 "PUT",
875 "/workspaces/:workspace/actions/secrets/:setting",
876 Op::SetActionsSecret,
877 &[],
878 ),
879 route(
880 "DELETE",
881 "/workspaces/:workspace/actions/secrets/:setting",
882 Op::DeleteActionsSecret,
883 &[],
884 ),
885 route(
886 "GET",
887 "/workspaces/:workspace/actions/variables",
888 Op::ListActionsVariables,
889 &[],
890 ),
891 route(
892 "POST",
893 "/workspaces/:workspace/actions/variables",
894 Op::SetActionsVariable,
895 &[],
896 ),
897 route(
898 "PATCH",
899 "/workspaces/:workspace/actions/variables/:setting",
900 Op::SetActionsVariable,
901 &[],
902 ),
903 route(
904 "DELETE",
905 "/workspaces/:workspace/actions/variables/:setting",
906 Op::DeleteActionsVariable,
907 &[],
908 ),
909 // Self-hosted runners: a repository's own, or a workspace's.
910 route("GET", "/repos/:owner/:name/actions/runners", Op::ListRunners, &[]),
911 route("POST", "/repos/:owner/:name/actions/runners/registration-token", Op::CreateRunnerRegistrationToken, &[]),
912 route("DELETE", "/repos/:owner/:name/actions/runners/:id", Op::RemoveRunner, &[]),
913 route("GET", "/repos/:owner/:name/actions/runner-settings", Op::GetRunnerSettings, &[]),
914 route("PATCH", "/repos/:owner/:name/actions/runner-settings", Op::UpdateRunnerSettings, &[]),
915 route("GET", "/workspaces/:workspace/actions/runners", Op::ListRunners, &[]),
916 route("POST", "/workspaces/:workspace/actions/runners/registration-token", Op::CreateRunnerRegistrationToken, &[]),
917 route("DELETE", "/workspaces/:workspace/actions/runners/:id", Op::RemoveRunner, &[]),
918 route("GET", "/workspaces/:workspace/actions/runner-settings", Op::GetRunnerSettings, &[]),
919 route("PATCH", "/workspaces/:workspace/actions/runner-settings", Op::UpdateRunnerSettings, &[]),
920 route("GET", "/workspaces/:workspace/actions/runner-groups", Op::ListRunnerGroups, &[]),
921 route("POST", "/workspaces/:workspace/actions/runner-groups", Op::CreateRunnerGroup, &[]),
922 route("PATCH", "/workspaces/:workspace/actions/runner-groups/:id", Op::UpdateRunnerGroup, &[]),
923 route("DELETE", "/workspaces/:workspace/actions/runner-groups/:id", Op::DeleteRunnerGroup, &[]),
924 route(
925 "POST",
926 "/repos/:owner/:name/issues/:number/comments",
927 Op::AddComment,
928 &[],
929 ),
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts930 route("PATCH", "/repos/:owner/:name/issues/comments/:comment_id", Op::EditComment, &[]),
931 route("DELETE", "/repos/:owner/:name/issues/comments/:comment_id", Op::DeleteComment, &[]),
Download ZIP deflates sixteen files at a time, within a Worker's memory932 route(
933 "GET",
934 "/repos/:owner/:name/pulls",
935 Op::ListPullRequests,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar936 &[("state", "state"), ("label", "label"), ("milestone", "milestone"), ("base", "base")],
Download ZIP deflates sixteen files at a time, within a Worker's memory937 ),
938 route(
939 "POST",
940 "/repos/:owner/:name/pulls",
941 Op::CreatePullRequest,
942 &[],
943 ),
944 route(
945 "GET",
946 "/repos/:owner/:name/pulls/:number",
947 Op::GetPullRequest,
948 &[],
949 ),
950 route(
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar951 "PATCH",
952 "/repos/:owner/:name/pulls/:number",
953 Op::UpdatePullRequest,
954 &[],
955 ),
956 route(
Download ZIP deflates sixteen files at a time, within a Worker's memory957 "GET",
958 "/repos/:owner/:name/pulls/:number/changes",
959 Op::GetPullRequestChanges,
960 &[],
961 ),
962 route(
963 "POST",
964 "/repos/:owner/:name/pulls/:number/reviews",
965 Op::ReviewPullRequest,
966 &[],
967 ),
968 route(
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar969 "POST",
970 "/repos/:owner/:name/pulls/:number/requested_reviewers",
971 Op::RequestReviewers,
972 &[],
973 ),
974 route(
975 "DELETE",
976 "/repos/:owner/:name/pulls/:number/requested_reviewers",
977 Op::RemoveRequestedReviewers,
978 &[],
979 ),
980 route(
Download ZIP deflates sixteen files at a time, within a Worker's memory981 "GET",
982 "/repos/:owner/:name/pulls/:number/session",
983 Op::ReadSession,
984 &[("after", "after")],
985 ),
986 route(
987 "POST",
988 "/repos/:owner/:name/pulls/:number/session",
989 Op::RecordSession,
990 &[],
991 ),
992 route(
993 "POST",
994 "/repos/:owner/:name/pulls/:number/ready",
995 Op::MarkPullRequestReady,
996 &[],
997 ),
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts998 route("POST", "/repos/:owner/:name/pulls/:number/draft", Op::ConvertPullRequestToDraft, &[]),
Download ZIP deflates sixteen files at a time, within a Worker's memory999 route(
1000 "POST",
1001 "/repos/:owner/:name/pulls/:number/close",
1002 Op::ClosePullRequest,
1003 &[],
1004 ),
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1005 route("POST", "/repos/:owner/:name/pulls/:number/reopen", Op::ReopenPullRequest, &[]),
Download ZIP deflates sixteen files at a time, within a Worker's memory1006 route(
1007 "POST",
1008 "/repos/:owner/:name/pulls/:number/merge",
1009 Op::MergePullRequest,
1010 &[],
1011 ),
Merge main into Artifacts Phase 21012 // Artifacts mode's docs, slides, designs and dashboards. Search comes
1013 // before an artifact by id, which it would otherwise match.
1014 route("GET", "/workspaces/:workspace/artifacts", Op::Folios(FoliosOp::List), &[("tab", "tab"), ("kind", "kind"), ("space", "space"), ("project", "project"), ("q", "q"), ("state", "state"), ("cursor", "cursor"), ("limit", "limit")]),
1015 route("POST", "/workspaces/:workspace/artifacts", Op::Folios(FoliosOp::Create), &[]),
1016 route("GET", "/workspaces/:workspace/artifacts/search", Op::Folios(FoliosOp::Search), &[("q", "q"), ("kind", "kind"), ("space", "space"), ("project", "project"), ("limit", "limit")]),
1017 route("GET", "/workspaces/:workspace/artifacts/:artifact_id", Op::Folios(FoliosOp::Get), &[]),
1018 route("PATCH", "/workspaces/:workspace/artifacts/:artifact_id", Op::Folios(FoliosOp::Update), &[]),
1019 route("DELETE", "/workspaces/:workspace/artifacts/:artifact_id", Op::Folios(FoliosOp::Trash), &[]),
1020 route("POST", "/workspaces/:workspace/artifacts/:artifact_id/restore", Op::Folios(FoliosOp::Restore), &[]),
1021 route("POST", "/workspaces/:workspace/artifacts/:artifact_id/purge", Op::Folios(FoliosOp::Purge), &[]),
1022 route("GET", "/workspaces/:workspace/artifacts/:artifact_id/content", Op::Folios(FoliosOp::GetContent), &[]),
1023 route("PUT", "/workspaces/:workspace/artifacts/:artifact_id/content", Op::Folios(FoliosOp::Edit), &[]),
1024 route("GET", "/workspaces/:workspace/artifacts/:artifact_id/access", Op::Folios(FoliosOp::GetAccess), &[]),
1025 route("PUT", "/workspaces/:workspace/artifacts/:artifact_id/access", Op::Folios(FoliosOp::SetAccess), &[]),
1026 route("GET", "/workspaces/:workspace/artifacts/:artifact_id/versions", Op::Folios(FoliosOp::ListVersions), &[]),
1027 route("POST", "/workspaces/:workspace/artifacts/:artifact_id/versions/:version_id/restore", Op::Folios(FoliosOp::RestoreVersion), &[]),
1028 route("GET", "/workspaces/:workspace/artifact-templates", Op::Folios(FoliosOp::ListTemplates), &[("kind", "kind")]),
1029 route("GET", "/workspaces/:workspace/artifact-spaces", Op::Folios(FoliosOp::ListSpaces), &[]),
1030 route("POST", "/workspaces/:workspace/datasets/query", Op::Folios(FoliosOp::QueryDataset), &[]),
Download ZIP deflates sixteen files at a time, within a Worker's memory1031];
1032
1033impl Route {
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1034 /// Whether the route answers success with `204` and no body, as
1035 /// GitHub's address for the same does. MCP still answers `true`.
1036 pub fn no_content(&self) -> bool {
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r91037 matches!(
1038 self.op,
1039 Op::DeleteComment
1040 )
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1041 }
1042
Download ZIP deflates sixteen files at a time, within a Worker's memory1043 /// The names of the route's path parameters, in order.
1044 pub fn params(&self) -> impl Iterator<Item = &'static str> {
1045 self.path
1046 .split('/')
1047 .filter_map(|segment| segment.strip_prefix(':'))
1048 }
1049
1050 /// The values of the path parameters, if `path` is this route's.
1051 fn matches<'a>(&self, path: &'a str) -> Option<Vec<(&'static str, &'a str)>> {
1052 let mut values = Vec::new();
1053 let mut actual = path.trim_end_matches('/').split('/');
1054 for expected in self.path.split('/') {
1055 let segment = actual.next()?;
1056 match expected.strip_prefix(':') {
1057 Some(name) if !segment.is_empty() => values.push((name, segment)),
1058 Some(_) => return None,
1059 None if expected == segment => {}
1060 None => return None,
1061 }
1062 }
1063 actual.next().is_none().then_some(values)
1064 }
1065}
1066
1067/// A path segment with its `%XX` escapes decoded; as given when that is not
1068/// UTF-8.
1069fn percent_decoded(segment: &str) -> String {
1070 let bytes = segment.as_bytes();
1071 let mut out = Vec::with_capacity(bytes.len());
1072 let mut i = 0;
1073 while i < bytes.len() {
1074 let escaped = (bytes[i] == b'%')
1075 .then(|| segment.get(i + 1..i + 3))
1076 .flatten()
1077 .filter(|hex| hex.bytes().all(|byte| byte.is_ascii_hexdigit()))
1078 .and_then(|hex| u8::from_str_radix(hex, 16).ok());
1079 match escaped {
1080 Some(byte) => {
1081 out.push(byte);
1082 i += 3;
1083 }
1084 None => {
1085 out.push(bytes[i]);
1086 i += 1;
1087 }
1088 }
1089 }
1090 String::from_utf8(out).unwrap_or_else(|_| segment.to_owned())
1091}
1092
1093/// The route for a request, and the operation input it describes.
1094///
1095/// The input is the JSON body, overlaid with the query parameters the route
1096/// reads and then with what the path names: `owner` and `name` become
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1097/// `repo`, as does a team's `repo` with its `workspace`, and `number`
1098/// becomes an integer.
Download ZIP deflates sixteen files at a time, within a Worker's memory1099pub fn resolve(
1100 method: &str,
1101 path: &str,
1102 query: &[(String, String)],
1103 body: Value,
1104) -> Option<(&'static Route, Value)> {
1105 let (route, params) = ROUTES
1106 .iter()
1107 .filter(|route| route.method == method)
1108 .find_map(|route| Some((route, route.matches(path)?)))?;
1109
1110 let mut input = match body {
1111 Value::Object(fields) => fields,
1112 _ => Map::new(),
1113 };
1114 for (name, key) in route.query {
1115 if let Some((_, value)) = query.iter().find(|(query_name, _)| query_name == name) {
1116 input.insert((*key).to_owned(), Value::String(value.clone()));
1117 }
1118 }
1119 let param = |wanted: &str| {
1120 params
1121 .iter()
1122 .find(|(name, _)| *name == wanted)
1123 .map(|(_, value)| *value)
1124 };
1125 if let (Some(owner), Some(name)) = (param("owner"), param("name")) {
1126 input.insert("repo".to_owned(), Value::String(format!("{owner}/{name}")));
1127 }
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb971128 // Every other name the path gives, under that name; the ones below that
1129 // need more (a repository, a number, an encoded name) are set after.
1130 for (key, value) in &params {
1131 if !matches!(*key, "owner" | "name") {
1132 input.insert((*key).to_owned(), Value::String(percent_decoded(value)));
Download ZIP deflates sixteen files at a time, within a Worker's memory1133 }
1134 }
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1135 // A repository of a team's workspace, named by itself.
1136 if let (Some(workspace), Some(name)) = (param("workspace"), param("repo")) {
1137 input.insert("repo".to_owned(), Value::String(format!("{workspace}/{name}")));
1138 }
1139 // A branch name may hold slashes, sent URL-encoded as one segment, and
1140 // a label's name spaces.
Download ZIP deflates sixteen files at a time, within a Worker's memory1141 if let Some(branch) = param("branch") {
1142 input.insert("branch".to_owned(), Value::String(percent_decoded(branch)));
1143 }
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb971144 // An environment's name may hold slashes and spaces, URL-encoded.
1145 if let Some(environment) = param("environment") {
1146 input.insert("environment".to_owned(), Value::String(percent_decoded(environment)));
1147 }
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1148 if let Some(label) = param("label") {
1149 input.insert("label".to_owned(), Value::String(percent_decoded(label)));
1150 }
1151 if let Some(milestone) = param("milestone") {
1152 // Not a number: zero, which no milestone has.
1153 input.insert("milestone".to_owned(), milestone.parse::<u32>().unwrap_or(0).into());
1154 }
Download ZIP deflates sixteen files at a time, within a Worker's memory1155 // GitHub says some things with the path alone.
1156 if route.path.ends_with("/enable") || route.path.ends_with("/disable") {
1157 input.insert("enabled".to_owned(), Value::Bool(route.path.ends_with("/enable")));
1158 }
1159 if route.path.ends_with("/rerun-failed-jobs") {
1160 input.insert("failed_only".to_owned(), Value::Bool(true));
1161 }
Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009)1162 if route.path.ends_with("/force-cancel") {
1163 input.insert("force".to_owned(), Value::Bool(true));
1164 }
API: notifications over REST and MCP, with notifications scopes1165 // Unsaving and waking a thread are a DELETE of what PUT made.
1166 if route.method == "DELETE" && route.path.ends_with("/saved") {
1167 input.insert("saved".to_owned(), Value::Bool(false));
1168 }
1169 if route.method == "DELETE" && route.path.ends_with("/snooze") {
1170 input.remove("until");
1171 }
Download ZIP deflates sixteen files at a time, within a Worker's memory1172 if let Some(number) = param("number") {
1173 // Not a number: zero, which no issue or pull request has.
1174 input.insert(
1175 "number".to_owned(),
1176 number.parse::<u32>().unwrap_or(0).into(),
1177 );
1178 }
1179 Some((route, Value::Object(input)))
1180}
1181
1182#[cfg(test)]
1183mod tests {
1184 use serde_json::json;
1185
1186 use super::*;
1187
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb971188 /// Every name a route's path gives reaches the operation: a name left
1189 /// off the lists above is dropped, and the operation answers that it
1190 /// was not given (the project routes were, until this test).
1191 #[test]
1192 fn every_path_parameter_reaches_the_input() {
1193 for route in ROUTES.iter() {
1194 let names: Vec<&str> = route.path.split('/').filter_map(|part| part.strip_prefix(':')).collect();
1195 if names.is_empty() {
1196 continue;
1197 }
1198 let path: String = route
1199 .path
1200 .split('/')
1201 .map(|part| match part.strip_prefix(':') {
1202 Some("number" | "milestone") => "7".to_owned(),
1203 Some(name) => format!("{name}-x"),
1204 None => part.to_owned(),
1205 })
1206 .collect::<Vec<_>>()
1207 .join("/");
1208 let (found, input) = resolve(route.method, &path, &[], json!({})).unwrap();
1209 // A path two routes could take is checked under the first.
1210 if found.path != route.path {
1211 continue;
1212 }
1213 for name in names {
1214 let key = match name {
1215 "owner" | "name" => "repo",
1216 "repo" if names_has_workspace(route.path) => "repo",
1217 other => other,
1218 };
1219 assert!(
1220 input.get(key).is_some_and(|value| !value.is_null()),
1221 "{} {}: :{name} does not reach the input",
1222 route.method,
1223 route.path
1224 );
1225 }
1226 }
1227 }
1228
1229 fn names_has_workspace(path: &str) -> bool {
1230 path.split('/').any(|part| part == ":workspace")
1231 }
1232
Download ZIP deflates sixteen files at a time, within a Worker's memory1233 #[test]
1234 fn a_path_resolves_to_its_operation_and_input() {
1235 let (route, input) = resolve(
1236 "POST",
1237 "/repos/flagon-io/hello/pulls/14/merge",
1238 &[],
1239 json!({ "keep_issue_open": true, "number": 99, "repo": "someone/else" }),
1240 )
1241 .unwrap();
1242 assert_eq!(route.op, Op::MergePullRequest);
1243 // What the path names wins over the body.
1244 assert_eq!(
1245 input,
1246 json!({ "keep_issue_open": true, "number": 14, "repo": "flagon-io/hello" })
1247 );
1248 }
1249
1250 #[test]
1251 fn a_branch_with_slashes_is_one_encoded_segment() {
1252 let (route, input) = resolve(
1253 "POST",
1254 "/repos/flagon-io/hello/branches/feature%2Flogin/rename",
1255 &[],
1256 json!({ "new_name": "feature/sign-in" }),
1257 )
1258 .unwrap();
1259 assert_eq!(route.op, Op::RenameBranch);
1260 assert_eq!(
1261 input,
1262 json!({ "new_name": "feature/sign-in", "branch": "feature/login", "repo": "flagon-io/hello" })
1263 );
1264 assert_eq!(percent_decoded("100%"), "100%");
1265 assert_eq!(percent_decoded("a%2bb%zz"), "a+b%zz");
1266 }
1267
1268 #[test]
1269 fn a_collaborator_is_named_by_username() {
1270 let (route, input) = resolve(
1271 "PATCH",
1272 "/repos/flagon-io/hello/collaborators/ada",
1273 &[],
1274 json!({ "role": "maintain" }),
1275 )
1276 .unwrap();
1277 assert_eq!(route.op, Op::UpdateCollaborator);
1278 assert_eq!(input, json!({ "role": "maintain", "username": "ada", "repo": "flagon-io/hello" }));
1279 let (route, input) = resolve("DELETE", "/user/repository_invitations/rin_1", &[], Value::Null).unwrap();
1280 assert_eq!(route.op, Op::DeclineRepoInvitation);
1281 assert_eq!(input, json!({ "id": "rin_1" }));
1282 }
1283
1284 #[test]
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1285 fn teams_are_addressed_by_workspace_and_slug() {
1286 let query = [("q".to_owned(), "back".to_owned())];
1287 let (route, input) = resolve("GET", "/workspaces/acme/teams", &query, Value::Null).unwrap();
1288 assert_eq!(route.op, Op::ListTeams);
1289 assert_eq!(input, json!({ "query": "back", "workspace": "acme" }));
1290 let (route, input) = resolve("PATCH", "/workspaces/acme/teams/backend", &[], json!({ "name": "Back end" })).unwrap();
1291 assert_eq!(route.op, Op::UpdateTeam);
1292 assert_eq!(input, json!({ "name": "Back end", "workspace": "acme", "team": "backend" }));
1293 let (route, input) =
1294 resolve("PUT", "/workspaces/acme/teams/backend/members/ana", &[], json!({ "role": "maintainer" })).unwrap();
1295 assert_eq!(route.op, Op::SetTeamMember);
1296 assert_eq!(input, json!({ "role": "maintainer", "workspace": "acme", "team": "backend", "username": "ana" }));
1297 let query = [("include_child_teams".to_owned(), "true".to_owned())];
1298 let (route, input) = resolve("GET", "/workspaces/acme/teams/backend/members", &query, Value::Null).unwrap();
1299 assert_eq!(route.op, Op::ListTeamMembers);
1300 assert_eq!(input, json!({ "include_child_teams": "true", "workspace": "acme", "team": "backend" }));
1301 // A repository is named by itself, in the team's workspace.
1302 let (route, input) =
1303 resolve("PUT", "/workspaces/acme/teams/backend/repos/rocket", &[], json!({ "role": "write" })).unwrap();
1304 assert_eq!(route.op, Op::SetTeamRepo);
1305 assert_eq!(input, json!({ "role": "write", "workspace": "acme", "team": "backend", "repo": "acme/rocket" }));
1306 let op = |method: &str, path: &str| resolve(method, path, &[], Value::Null).unwrap().0.op;
1307 assert_eq!(op("DELETE", "/workspaces/acme/teams/backend/repos/rocket"), Op::RemoveTeamRepo);
1308 assert_eq!(op("GET", "/workspaces/acme/teams/backend/teams"), Op::ListChildTeams);
1309 assert_eq!(op("GET", "/workspaces/acme/teams/backend/repos"), Op::ListTeamRepos);
1310 assert_eq!(op("PUT", "/workspaces/acme/teams/backend/review_assignment"), Op::SetTeamReviewAssignment);
1311 assert_eq!(op("DELETE", "/workspaces/acme/teams/backend"), Op::DeleteTeam);
1312 assert_eq!(op("POST", "/workspaces/acme/teams"), Op::CreateTeam);
1313 assert_eq!(op("GET", "/workspaces/acme/teams/backend"), Op::GetTeam);
1314 assert_eq!(op("DELETE", "/workspaces/acme/teams/backend/members/ana"), Op::RemoveTeamMember);
1315 let (route, input) = resolve("GET", "/workspaces/acme/members/ana/teams", &[], Value::Null).unwrap();
1316 assert_eq!(route.op, Op::ListUserTeams);
1317 assert_eq!(input, json!({ "workspace": "acme", "username": "ana" }));
1318 }
1319
1320 #[test]
1321 fn reviewers_are_requested_and_code_owners_checked_on_a_repository() {
1322 let body = json!({ "reviewers": ["ana"], "team_reviewers": ["backend"] });
1323 let (route, input) = resolve("POST", "/repos/acme/rocket/pulls/7/requested_reviewers", &[], body.clone()).unwrap();
1324 assert_eq!(route.op, Op::RequestReviewers);
1325 assert_eq!(
1326 input,
1327 json!({ "reviewers": ["ana"], "team_reviewers": ["backend"], "repo": "acme/rocket", "number": 7 })
1328 );
1329 let (route, _) = resolve("DELETE", "/repos/acme/rocket/pulls/7/requested_reviewers", &[], body).unwrap();
1330 assert_eq!(route.op, Op::RemoveRequestedReviewers);
1331 let query = [("ref".to_owned(), "main".to_owned())];
1332 let (route, input) = resolve("GET", "/repos/acme/rocket/codeowners/errors", &query, Value::Null).unwrap();
1333 assert_eq!(route.op, Op::GetCodeownersErrors);
1334 assert_eq!(input, json!({ "ref": "main", "repo": "acme/rocket" }));
1335 }
1336
1337 #[test]
API: notifications over REST and MCP, with notifications scopes1338 fn notifications_are_addressed_as_threads_and_by_issue() {
1339 let (route, input) = resolve("DELETE", "/notifications/threads/ntf_1", &[], Value::Null).unwrap();
1340 assert_eq!(route.op, Op::MarkThreadDone);
1341 assert_eq!(input, json!({ "id": "ntf_1" }));
1342 let (route, input) = resolve("DELETE", "/notifications/threads/ntf_1/saved", &[], Value::Null).unwrap();
1343 assert_eq!(route.op, Op::SaveThread);
1344 assert_eq!(input, json!({ "id": "ntf_1", "saved": false }));
1345 let (route, input) = resolve("DELETE", "/notifications/threads/ntf_1/snooze", &[], json!({ "until": "x" })).unwrap();
1346 assert_eq!(route.op, Op::SnoozeThread);
1347 assert_eq!(input, json!({ "id": "ntf_1" }));
1348 let query = [("all".to_owned(), "true".to_owned()), ("per_page".to_owned(), "50".to_owned())];
1349 let (route, input) = resolve("GET", "/repos/acme/rocket/notifications", &query, Value::Null).unwrap();
1350 assert_eq!(route.op, Op::ListNotifications);
1351 assert_eq!(input, json!({ "all": "true", "per_page": "50", "repo": "acme/rocket" }));
1352 let (route, input) = resolve("PUT", "/repos/acme/rocket/issues/7/subscription", &[], json!({ "ignored": true })).unwrap();
1353 assert_eq!(route.op, Op::SetThreadSubscription);
1354 assert_eq!(input, json!({ "ignored": true, "number": 7, "repo": "acme/rocket" }));
1355 assert_eq!(resolve("GET", "/user/subscriptions", &[], Value::Null).unwrap().0.op, Op::ListWatchedRepos);
1356 }
1357
1358 #[test]
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1359 fn labels_and_milestones_are_named_in_the_path() {
1360 let (route, input) = resolve("PATCH", "/repos/acme/web/labels/good%20first%20issue", &[], json!({ "color": "7057ff" })).unwrap();
1361 assert_eq!(route.op, Op::UpdateLabel);
1362 assert_eq!(input, json!({ "color": "7057ff", "label": "good first issue", "repo": "acme/web" }));
1363 let (route, input) = resolve("DELETE", "/repos/acme/web/issues/7/labels/bug", &[], Value::Null).unwrap();
1364 assert_eq!(route.op, Op::RemoveIssueLabels);
1365 assert_eq!(input, json!({ "label": "bug", "number": 7, "repo": "acme/web" }));
1366 let (route, input) = resolve("DELETE", "/repos/acme/web/issues/7/labels", &[], Value::Null).unwrap();
1367 assert_eq!(route.op, Op::RemoveIssueLabels);
1368 assert_eq!(input, json!({ "number": 7, "repo": "acme/web" }));
1369 let (route, _) = resolve("POST", "/repos/acme/web/labels/defaults", &[], Value::Null).unwrap();
1370 assert_eq!(route.op, Op::AddDefaultLabels);
1371 let (route, input) = resolve("PATCH", "/repos/acme/web/milestones/3", &[], json!({ "state": "closed" })).unwrap();
1372 assert_eq!(route.op, Op::UpdateMilestone);
1373 assert_eq!(input, json!({ "state": "closed", "milestone": 3, "repo": "acme/web" }));
1374 let (route, input) = resolve("PATCH", "/repos/acme/web/pulls/9", &[], json!({ "base": "release" })).unwrap();
1375 assert_eq!(route.op, Op::UpdatePullRequest);
1376 assert_eq!(input, json!({ "base": "release", "number": 9, "repo": "acme/web" }));
1377 }
1378
1379 #[test]
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1380 fn pull_requests_reopen_and_turn_draft_and_comments_are_named_by_id() {
1381 let op = |method: &str, path: &str| resolve(method, path, &[], Value::Null).unwrap().0.op;
1382 assert_eq!(op("POST", "/repos/acme/web/pulls/9/reopen"), Op::ReopenPullRequest);
1383 assert_eq!(op("POST", "/repos/acme/web/pulls/9/draft"), Op::ConvertPullRequestToDraft);
1384 assert_eq!(op("POST", "/repos/acme/web/pulls/9/close"), Op::ClosePullRequest);
1385 // Reopening and closing with a PATCH, as `state`.
1386 let (route, input) = resolve("PATCH", "/repos/acme/web/pulls/9", &[], json!({ "state": "open" })).unwrap();
1387 assert_eq!(route.op, Op::UpdatePullRequest);
1388 assert_eq!(input, json!({ "state": "open", "number": 9, "repo": "acme/web" }));
1389 let (route, input) =
1390 resolve("PATCH", "/repos/acme/web/issues/comments/cmt_1", &[], json!({ "body": "Better" })).unwrap();
1391 assert_eq!(route.op, Op::EditComment);
1392 assert_eq!(input, json!({ "body": "Better", "comment_id": "cmt_1", "repo": "acme/web" }));
1393 let (route, input) = resolve("DELETE", "/repos/acme/web/issues/comments/cmt_1", &[], Value::Null).unwrap();
1394 assert_eq!(route.op, Op::DeleteComment);
1395 assert_eq!(input, json!({ "comment_id": "cmt_1", "repo": "acme/web" }));
1396 // Still an issue's comments, labels and subscription.
1397 assert_eq!(op("POST", "/repos/acme/web/issues/7/comments"), Op::AddComment);
1398 assert_eq!(op("DELETE", "/repos/acme/web/issues/7/labels"), Op::RemoveIssueLabels);
1399 assert_eq!(op("DELETE", "/repos/acme/web/issues/7/subscription"), Op::DeleteThreadSubscription);
1400 }
1401
1402 #[test]
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.1403 fn observability_is_addressed_by_workspace_and_project() {
1404 let query = [("level".to_owned(), "error,warn".to_owned()), ("q".to_owned(), "timeout".to_owned())];
1405 let (route, input) = resolve("GET", "/workspaces/acme/projects/web/observability/logs", &query, Value::Null).unwrap();
1406 assert_eq!(route.op, Op::Telemetry(TelemetryOp::ListLogs));
1407 assert_eq!(input, json!({ "level": "error,warn", "q": "timeout", "workspace": "acme", "project": "web" }));
1408 let (route, input) =
1409 resolve("GET", "/workspaces/acme/projects/web/observability/metrics/http.server.request.duration", &[], Value::Null).unwrap();
1410 assert_eq!(route.op, Op::Telemetry(TelemetryOp::GetMetric));
1411 assert_eq!(input["metric"], json!("http.server.request.duration"));
1412 let (route, input) =
1413 resolve("PATCH", "/workspaces/acme/projects/web/observability/errors/f00d", &[], json!({ "state": "resolved" })).unwrap();
1414 assert_eq!(route.op, Op::Telemetry(TelemetryOp::UpdateError));
1415 assert_eq!(input, json!({ "state": "resolved", "workspace": "acme", "project": "web", "fingerprint": "f00d" }));
1416 }
1417
1418 #[test]
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1419 fn billing_is_addressed_by_workspace() {
1420 let query = [("from".to_owned(), "2026-10-01".to_owned()), ("products".to_owned(), "agent,sandboxes".to_owned())];
1421 let (route, input) = resolve("GET", "/workspaces/acme/usage", &query, Value::Null).unwrap();
1422 assert_eq!(route.op, Op::GetUsage);
1423 assert_eq!(input, json!({ "from": "2026-10-01", "products": "agent,sandboxes", "workspace": "acme" }));
1424 let (route, input) = resolve("PUT", "/workspaces/acme/budget", &[], json!({ "alerts": [50] })).unwrap();
1425 assert_eq!(route.op, Op::SetBudget);
1426 assert_eq!(input, json!({ "alerts": [50], "workspace": "acme" }));
1427 let op = |method: &str, path: &str| resolve(method, path, &[], Value::Null).unwrap().0.op;
1428 assert_eq!(op("GET", "/workspaces/acme/budget"), Op::GetBudget);
1429 assert_eq!(op("GET", "/workspaces/acme/invoices"), Op::ListInvoices);
1430 assert_eq!(op("GET", "/workspaces/acme/billing_details"), Op::GetBillingDetails);
1431 }
1432
1433 #[test]
Merge checks: statuses and check runs on every commit1434 fn checks_are_at_githubs_addresses() {
1435 let sha = "a".repeat(40);
1436 let body = json!({ "state": "success", "context": "ci/build" });
1437 let (route, input) = resolve("POST", &format!("/repos/acme/web/statuses/{sha}"), &[], body).unwrap();
1438 assert_eq!(route.op, Op::Checks(ChecksOp::CreateCommitStatus));
1439 assert_eq!(input, json!({ "state": "success", "context": "ci/build", "sha": sha, "repo": "acme/web" }));
1440 let (route, input) = resolve("GET", "/repos/acme/web/commits/release%2F1.x/status", &[], Value::Null).unwrap();
1441 assert_eq!(route.op, Op::Checks(ChecksOp::GetCombinedStatus));
1442 assert_eq!(input, json!({ "ref": "release/1.x", "repo": "acme/web" }));
1443 let query = [("check_name".to_owned(), "lint".to_owned())];
1444 let (route, input) = resolve("GET", "/repos/acme/web/commits/main/check-runs", &query, Value::Null).unwrap();
1445 assert_eq!(route.op, Op::Checks(ChecksOp::ListCheckRunsForRef));
1446 assert_eq!(input, json!({ "check_name": "lint", "ref": "main", "repo": "acme/web" }));
1447 let (route, input) = resolve("PATCH", "/repos/acme/web/check-runs/cr_1", &[], json!({ "conclusion": "success" })).unwrap();
1448 assert_eq!(route.op, Op::Checks(ChecksOp::UpdateCheckRun));
1449 assert_eq!(input, json!({ "conclusion": "success", "id": "cr_1", "repo": "acme/web" }));
1450 let op = |method: &str, path: &str| resolve(method, path, &[], Value::Null).unwrap().0.op;
1451 assert_eq!(op("POST", "/repos/acme/web/check-runs"), Op::Checks(ChecksOp::CreateCheckRun));
1452 assert_eq!(op("GET", "/repos/acme/web/check-runs/cr_1/annotations"), Op::Checks(ChecksOp::ListCheckRunAnnotations));
1453 assert_eq!(op("POST", "/repos/acme/web/check-suites/cs_1/rerequest"), Op::Checks(ChecksOp::RerequestCheckSuite));
1454 assert_eq!(op("GET", "/repos/acme/web/commits/main/check-suites"), Op::Checks(ChecksOp::ListCheckSuitesForRef));
1455 }
1456
1457 #[test]
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.1458 fn archives_are_at_githubs_addresses_and_redirect() {
1459 let sha = "4807077b296e6edbf410d55e72749d3e1170c291";
1460 let (route, input) = resolve("GET", &format!("/repos/acme/web/tarball/{sha}"), &[], Value::Null).unwrap();
1461 assert_eq!(route.op, Op::Archives(ArchiveOp::DownloadTarball));
1462 assert_eq!(input, json!({ "ref": sha, "repo": "acme/web" }));
1463 // A branch with slashes, URL-encoded as octokit sends it.
1464 let (route, input) = resolve("GET", "/repos/acme/web/zipball/release%2F1.x", &[], Value::Null).unwrap();
1465 assert_eq!(route.op, Op::Archives(ArchiveOp::DownloadZipball));
1466 assert_eq!(input, json!({ "ref": "release/1.x", "repo": "acme/web" }));
1467 // Both answer with a redirect to their link, as the artifact download does.
1468 assert!(route.op.redirects());
1469 assert!(Op::Archives(ArchiveOp::DownloadTarball).redirects());
1470 assert!(Op::Artifacts(ArtifactsOp::DownloadArtifact).redirects());
1471 assert!(!Op::GetRepo.redirects());
1472 // Anyone may ask for a public repository's.
1473 assert!(!route.op.needs_user());
1474 }
1475
1476 #[test]
Download ZIP deflates sixteen files at a time, within a Worker's memory1477 fn query_parameters_are_renamed() {
1478 let query = [
1479 ("q".to_owned(), "parser".to_owned()),
1480 ("x".to_owned(), "y".to_owned()),
1481 ];
1482 let (route, input) = resolve("GET", "/repos", &query, Value::Null).unwrap();
1483 assert_eq!(route.op, Op::ListRepos);
1484 assert_eq!(input, json!({ "query": "parser" }));
1485 }
1486
1487 #[test]
1488 fn method_and_shape_must_match() {
1489 assert!(resolve("GET", "/repos/a/b/issues/1/close", &[], Value::Null).is_none());
1490 assert!(resolve("GET", "/repos/a", &[], Value::Null).is_none());
1491 assert!(resolve("GET", "/repos/a/b/issues/1/extra", &[], Value::Null).is_none());
1492 assert!(resolve("GET", "/repos/a/b/", &[], Value::Null).is_some());
1493 }
1494
1495 #[test]
1496 fn every_parameter_and_query_name_is_an_input() {
1497 for route in ROUTES {
1498 let properties = route.op.properties();
1499 for (_, key) in route.query {
1500 assert!(properties.contains_key(*key), "{}: {key}", route.path);
1501 }
1502 for name in route.params() {
1503 let covered = matches!(name, "owner" | "name") && properties.contains_key("repo")
1504 || properties.contains_key(name);
1505 assert!(covered, "{}: {name}", route.path);
1506 }
1507 }
1508 }
1509
1510 #[test]
1511 fn every_operation_has_a_route() {
1512 for op in Op::ALL {
1513 assert!(ROUTES.iter().any(|route| route.op == op), "{}", op.name());
1514 }
1515 }
1516}

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