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

g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1//! 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 remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r98use crate::customize::CustomizeOp;
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca9use crate::deploy_keys::DeployKeysOp;
Merge branch 'mirroring' into artifacts-mode10use crate::mirrors::MirrorsOp;
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb9711use 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.12use crate::telemetry::TelemetryOp;
Merge packages: roles, Actions access, source label, soft delete, API13use crate::packages::PackagesOp;
Merge main into Artifacts Phase 214use crate::folios::FoliosOp;
Merge branch 'worktree-agent-a3abfcce648e87dca'15use crate::protection::ProtectionOp;
API and MCP for a workspace's personal access token rules, members' tokens and approvals16use crate::token_policy::TokenOp;
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights17use crate::operations::Op;
Merge checks: statuses and check runs on every commit18use crate::checks::ChecksOp;
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge19use crate::rules::RulesOp;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar20use crate::security::SecurityOp;
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights21
22pub struct Route {
23 pub method: &'static str,
24 /// Segments starting with `:` are parameters.
25 pub path: &'static str,
26 pub op: Op,
27 /// Query parameters the route reads, as `(name in the URL, input name)`.
28 pub query: &'static [(&'static str, &'static str)],
29}
30
31const fn route(
32 method: &'static str,
33 path: &'static str,
34 op: Op,
35 query: &'static [(&'static str, &'static str)],
36) -> Route {
37 Route {
38 method,
39 path,
40 op,
41 query,
42 }
43}
44
45pub const ROUTES: &[Route] = &[
46 route("GET", "/user", Op::Whoami, &[]),
47 route("POST", "/workspaces", Op::CreateWorkspace, &[]),
Merge branch 'worktree-agent-ad7c6d88d93adc817'48 route("GET", "/workspaces/:workspace", Op::GetWorkspace, &[]),
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights49 route("DELETE", "/workspaces/:workspace", Op::DeleteWorkspace, &[]),
50 route("GET", "/user/emails", Op::ListEmails, &[]),
51 route("POST", "/user/emails", Op::AddEmail, &[]),
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)52 route("POST", "/user/emails/confirm", Op::ConfirmEmail, &[]),
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights53 route("DELETE", "/user/emails/:email", Op::RemoveEmail, &[]),
54 route("PATCH", "/user/email-settings", Op::UpdateEmailSettings, &[]),
55 route("GET", "/user/invites", Op::ListInvites, &[]),
56 route("POST", "/user/invites", Op::CreateInvite, &[]),
57 route("DELETE", "/user/invites/:id", Op::RevokeInvite, &[]),
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)58 // Invitations to workspaces waiting for your answer.
59 route("GET", "/user/invitations", Op::ListInvitations, &[]),
60 route("POST", "/user/invitations/:id/accept", Op::AcceptInvitation, &[]),
61 route("POST", "/user/invitations/:id/decline", Op::DeclineInvitation, &[]),
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights62 route("GET", "/workspaces/:workspace/invitations", Op::ListWorkspaceInvites, &[]),
API and MCP for a workspace's personal access token rules, members' tokens and approvals63 // A workspace's rules for personal access tokens, and its members' tokens.
64 route("GET", "/workspaces/:workspace/personal-access-token-policy", Op::Tokens(TokenOp::GetTokenPolicy), &[]),
65 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 managers66 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 approvals67 route("POST", "/workspaces/:workspace/personal-access-tokens/:id", Op::Tokens(TokenOp::RevokeMemberToken), &[]),
68 route("GET", "/workspaces/:workspace/personal-access-token-requests", Op::Tokens(TokenOp::ListTokenRequests), &[]),
69 route("POST", "/workspaces/:workspace/personal-access-token-requests/:id", Op::Tokens(TokenOp::ReviewTokenRequest), &[]),
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights70 route("POST", "/workspaces/:workspace/invitations", Op::InviteMember, &[]),
71 route("DELETE", "/workspaces/:workspace/invitations/:id", Op::RevokeWorkspaceInvite, &[]),
72 // Who has access. GitHub's addresses, but for adding someone, which
73 // takes an email address as well as a username.
74 route("GET", "/repos/:owner/:name/collaborators", Op::ListCollaborators, &[]),
75 route("POST", "/repos/:owner/:name/collaborators", Op::AddCollaborator, &[]),
76 route("PATCH", "/repos/:owner/:name/collaborators/:username", Op::UpdateCollaborator, &[]),
77 route("DELETE", "/repos/:owner/:name/collaborators/:username", Op::RemoveCollaborator, &[]),
78 route(
79 "GET",
80 "/repos/:owner/:name/collaborators/:username/permission",
81 Op::GetCollaboratorPermission,
82 &[],
83 ),
84 route("GET", "/repos/:owner/:name/invitations", Op::ListRepoInvitations, &[]),
85 route("DELETE", "/repos/:owner/:name/invitations/:id", Op::RevokeRepoInvitation, &[]),
86 route("GET", "/user/repository_invitations", Op::ListMyRepoInvitations, &[]),
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca87 // Deploy keys, at GitHub's addresses.
88 route("GET", "/repos/:owner/:name/keys", Op::DeployKeys(DeployKeysOp::ListDeployKeys), &[]),
89 route("POST", "/repos/:owner/:name/keys", Op::DeployKeys(DeployKeysOp::CreateDeployKey), &[]),
90 route("GET", "/repos/:owner/:name/keys/:id", Op::DeployKeys(DeployKeysOp::GetDeployKey), &[]),
91 route("DELETE", "/repos/:owner/:name/keys/:id", Op::DeployKeys(DeployKeysOp::DeleteDeployKey), &[]),
Merge branch 'mirroring' into artifacts-mode92 // Mirroring: a repository's remotes, takeovers and hand-backs.
93 route("GET", "/repos/:owner/:name/mirror", Op::Mirrors(MirrorsOp::GetMirror), &[]),
94 route("GET", "/repos/:owner/:name/mirror/hand-back", Op::Mirrors(MirrorsOp::GetHandBackPlan), &[]),
95 route("POST", "/repos/:owner/:name/mirror/take-over", Op::Mirrors(MirrorsOp::TakeOver), &[]),
96 route("POST", "/repos/:owner/:name/mirror/ci", Op::Mirrors(MirrorsOp::SetCiFailover), &[]),
97 route("POST", "/repos/:owner/:name/mirror/hand-back", Op::Mirrors(MirrorsOp::HandBack), &[]),
98 route("POST", "/repos/:owner/:name/mirror/move-to-g1t", Op::Mirrors(MirrorsOp::MoveToG1t), &[]),
99 route("POST", "/repos/:owner/:name/mirror/sync", Op::Mirrors(MirrorsOp::SyncMirror), &[]),
100 route("POST", "/repos/:owner/:name/mirror/remotes", Op::Mirrors(MirrorsOp::AddRemote), &[]),
101 route("PATCH", "/repos/:owner/:name/mirror/remotes/:id", Op::Mirrors(MirrorsOp::UpdateRemote), &[]),
102 route("DELETE", "/repos/:owner/:name/mirror/remotes/:id", Op::Mirrors(MirrorsOp::RemoveRemote), &[]),
API: notifications over REST and MCP, with notifications scopes103 // Your notifications: threads, marking them, and what you subscribe
104 // to and watch. GitHub's addresses, with g1t's saved and snoozed.
105 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")]),
106 route("PUT", "/notifications", Op::MarkNotificationsRead, &[]),
107 route("GET", "/notifications/threads/:id", Op::GetNotificationThread, &[]),
108 route("PATCH", "/notifications/threads/:id", Op::MarkThreadRead, &[]),
109 route("DELETE", "/notifications/threads/:id", Op::MarkThreadDone, &[]),
110 route("PUT", "/notifications/threads/:id/saved", Op::SaveThread, &[]),
111 route("DELETE", "/notifications/threads/:id/saved", Op::SaveThread, &[]),
112 route("PUT", "/notifications/threads/:id/snooze", Op::SnoozeThread, &[]),
113 route("DELETE", "/notifications/threads/:id/snooze", Op::SnoozeThread, &[]),
114 route("GET", "/notifications/threads/:id/subscription", Op::GetThreadSubscription, &[]),
115 route("PUT", "/notifications/threads/:id/subscription", Op::SetThreadSubscription, &[]),
116 route("DELETE", "/notifications/threads/:id/subscription", Op::DeleteThreadSubscription, &[]),
117 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")]),
118 route("PUT", "/repos/:owner/:name/notifications", Op::MarkNotificationsRead, &[]),
119 route("GET", "/repos/:owner/:name/subscription", Op::GetRepoSubscription, &[]),
120 route("PUT", "/repos/:owner/:name/subscription", Op::SetRepoSubscription, &[]),
121 route("DELETE", "/repos/:owner/:name/subscription", Op::DeleteRepoSubscription, &[]),
122 route("GET", "/repos/:owner/:name/issues/:number/subscription", Op::GetThreadSubscription, &[]),
123 route("PUT", "/repos/:owner/:name/issues/:number/subscription", Op::SetThreadSubscription, &[]),
124 route("DELETE", "/repos/:owner/:name/issues/:number/subscription", Op::DeleteThreadSubscription, &[]),
125 route("GET", "/user/subscriptions", Op::ListWatchedRepos, &[]),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97126 // Stars: yours, and who starred a repository.
127 route("GET", "/user/starred", Op::About(AboutOp::ListStarred), &[]),
128 route("GET", "/user/starred/:owner/:name", Op::About(AboutOp::CheckStarred), &[]),
129 route("PUT", "/user/starred/:owner/:name", Op::About(AboutOp::Star), &[]),
130 route("DELETE", "/user/starred/:owner/:name", Op::About(AboutOp::Unstar), &[]),
131 route("GET", "/repos/:owner/:name/stargazers", Op::About(AboutOp::ListStargazers), &[("page", "page")]),
132 // What the default branch says about a repository, kept by commit.
133 route("GET", "/repos/:owner/:name/languages", Op::About(AboutOp::GetLanguages), &[]),
134 route("GET", "/repos/:owner/:name/contributors", Op::About(AboutOp::ListContributors), &[]),
135 route("GET", "/repos/:owner/:name/license", Op::About(AboutOp::GetLicense), &[]),
136 // Releases: `latest` and `tags/…` before an id.
137 route("GET", "/repos/:owner/:name/releases", Op::About(AboutOp::ListReleases), &[]),
138 route("POST", "/repos/:owner/:name/releases", Op::About(AboutOp::CreateRelease), &[]),
139 route("GET", "/repos/:owner/:name/releases/latest", Op::About(AboutOp::GetLatestRelease), &[]),
140 route("GET", "/repos/:owner/:name/releases/tags/:tag", Op::About(AboutOp::GetReleaseByTag), &[]),
141 route("GET", "/repos/:owner/:name/releases/:id", Op::About(AboutOp::GetRelease), &[]),
142 route("PATCH", "/repos/:owner/:name/releases/:id", Op::About(AboutOp::UpdateRelease), &[]),
143 route("DELETE", "/repos/:owner/:name/releases/:id", Op::About(AboutOp::DeleteRelease), &[]),
API: pinned projects over REST and MCP144 // Your pinned projects in a workspace, in your order.
145 route("GET", "/user/pinned_projects/:workspace", Op::ListPinnedProjects, &[]),
146 route("PUT", "/user/pinned_projects/:workspace", Op::ReorderPinnedProjects, &[]),
147 route("PUT", "/user/pinned_projects/:workspace/:project", Op::PinProject, &[]),
148 route("DELETE", "/user/pinned_projects/:workspace/:project", Op::UnpinProject, &[]),
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights149 route("PATCH", "/user/repository_invitations/:id", Op::AcceptRepoInvitation, &[]),
150 route("DELETE", "/user/repository_invitations/:id", Op::DeclineRepoInvitation, &[]),
151 route("PATCH", "/workspaces/:workspace", Op::UpdateWorkspace, &[]),
Merge main (membership, two-factor, GitHub repo roles) into tokens152 // Members and owners: GitHub's organization members, by username.
153 route("GET", "/workspaces/:workspace/members", Op::ListMembers, &[]),
154 route("PATCH", "/workspaces/:workspace/members/:username", Op::UpdateMember, &[]),
155 route("DELETE", "/workspaces/:workspace/members/:username", Op::RemoveMember, &[]),
156 route("POST", "/workspaces/:workspace/transfer_ownership", Op::TransferOwnership, &[]),
157 route("DELETE", "/user/memberships/:workspace", Op::LeaveWorkspace, &[]),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97158 // A workspace's projects: what each is, where it runs, its links.
159 route("GET", "/workspaces/:workspace/projects", Op::ListProjects, &[]),
160 route("GET", "/workspaces/:workspace/projects/:project", Op::GetProject, &[]),
161 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.162 // A project's observability: what its software did, and how it is collected.
163 route(
164 "GET",
165 "/workspaces/:workspace/projects/:project/observability/overview",
166 Op::Telemetry(TelemetryOp::GetOverview),
167 &[("from", "from"), ("to", "to"), ("environment", "environment"), ("service", "service"), ("deployment_id", "deployment_id"), ("step", "step")],
168 ),
169 route(
170 "GET",
171 "/workspaces/:workspace/projects/:project/observability/requests",
172 Op::Telemetry(TelemetryOp::ListRequests),
173 &[
174 ("from", "from"),
175 ("to", "to"),
176 ("q", "q"),
177 ("status", "status"),
178 ("errors_only", "errors_only"),
179 ("route", "route"),
180 ("min_duration_ms", "min_duration_ms"),
181 ("environment", "environment"),
182 ("service", "service"),
183 ("deployment_id", "deployment_id"),
184 ("cursor", "cursor"),
185 ("limit", "limit"),
186 ],
187 ),
188 route("GET", "/workspaces/:workspace/projects/:project/observability/requests/:id", Op::Telemetry(TelemetryOp::GetRequest), &[]),
189 route(
190 "GET",
191 "/workspaces/:workspace/projects/:project/observability/logs",
192 Op::Telemetry(TelemetryOp::ListLogs),
193 &[
194 ("from", "from"),
195 ("to", "to"),
196 ("q", "q"),
197 ("level", "level"),
198 ("trace_id", "trace_id"),
199 ("request_id", "request_id"),
200 ("environment", "environment"),
201 ("service", "service"),
202 ("deployment_id", "deployment_id"),
203 ("cursor", "cursor"),
204 ("limit", "limit"),
205 ],
206 ),
207 route(
208 "GET",
209 "/workspaces/:workspace/projects/:project/observability/traces",
210 Op::Telemetry(TelemetryOp::ListTraces),
211 &[
212 ("from", "from"),
213 ("to", "to"),
214 ("q", "q"),
215 ("errors_only", "errors_only"),
216 ("min_duration_ms", "min_duration_ms"),
217 ("environment", "environment"),
218 ("service", "service"),
219 ("cursor", "cursor"),
220 ("limit", "limit"),
221 ],
222 ),
223 route("GET", "/workspaces/:workspace/projects/:project/observability/traces/:trace_id", Op::Telemetry(TelemetryOp::GetTrace), &[]),
224 route(
225 "GET",
226 "/workspaces/:workspace/projects/:project/observability/errors",
227 Op::Telemetry(TelemetryOp::ListErrors),
228 &[("state", "state"), ("environment", "environment"), ("service", "service"), ("from", "from"), ("to", "to")],
229 ),
230 route("PATCH", "/workspaces/:workspace/projects/:project/observability/errors/:fingerprint", Op::Telemetry(TelemetryOp::UpdateError), &[]),
231 route("GET", "/workspaces/:workspace/projects/:project/observability/metrics", Op::Telemetry(TelemetryOp::ListMetrics), &[]),
232 route(
233 "GET",
234 "/workspaces/:workspace/projects/:project/observability/metrics/:metric",
235 Op::Telemetry(TelemetryOp::GetMetric),
236 &[("from", "from"), ("to", "to"), ("aggregate", "aggregate"), ("group_by", "group_by"), ("step", "step"), ("service", "service"), ("environment", "environment")],
237 ),
238 route("GET", "/workspaces/:workspace/projects/:project/observability/settings", Op::Telemetry(TelemetryOp::GetSettings), &[]),
239 route("PATCH", "/workspaces/:workspace/projects/:project/observability/settings", Op::Telemetry(TelemetryOp::UpdateSettings), &[]),
240 route("GET", "/workspaces/:workspace/projects/:project/observability/tokens", Op::Telemetry(TelemetryOp::ListTokens), &[]),
241 route("POST", "/workspaces/:workspace/projects/:project/observability/tokens", Op::Telemetry(TelemetryOp::CreateToken), &[]),
242 route("DELETE", "/workspaces/:workspace/projects/:project/observability/tokens/:id", Op::Telemetry(TelemetryOp::DeleteToken), &[]),
243 route("GET", "/workspaces/:workspace/projects/:project/observability/exports", Op::Telemetry(TelemetryOp::ListExports), &[]),
244 route("POST", "/workspaces/:workspace/projects/:project/observability/exports", Op::Telemetry(TelemetryOp::CreateExport), &[]),
245 route("PATCH", "/workspaces/:workspace/projects/:project/observability/exports/:id", Op::Telemetry(TelemetryOp::UpdateExport), &[]),
246 route("DELETE", "/workspaces/:workspace/projects/:project/observability/exports/:id", Op::Telemetry(TelemetryOp::DeleteExport), &[]),
247 route("GET", "/workspaces/:workspace/projects/:project/observability/usage", Op::Telemetry(TelemetryOp::GetUsage), &[]),
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights248 route("PUT", "/workspaces/:workspace/base_permission", Op::SetBasePermission, &[]),
249 route(
250 "GET",
251 "/workspaces/:workspace/outside_collaborators",
252 Op::ListOutsideCollaborators,
253 &[],
254 ),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar255 // Teams: a workspace's groups of members, with roles on repositories.
256 route("GET", "/workspaces/:workspace/teams", Op::ListTeams, &[("q", "query")]),
257 route("POST", "/workspaces/:workspace/teams", Op::CreateTeam, &[]),
258 route("GET", "/workspaces/:workspace/teams/:team", Op::GetTeam, &[]),
259 route("PATCH", "/workspaces/:workspace/teams/:team", Op::UpdateTeam, &[]),
260 route("DELETE", "/workspaces/:workspace/teams/:team", Op::DeleteTeam, &[]),
261 route(
262 "GET",
263 "/workspaces/:workspace/teams/:team/members",
264 Op::ListTeamMembers,
265 &[("include_child_teams", "include_child_teams")],
266 ),
267 route("PUT", "/workspaces/:workspace/teams/:team/members/:username", Op::SetTeamMember, &[]),
268 route("DELETE", "/workspaces/:workspace/teams/:team/members/:username", Op::RemoveTeamMember, &[]),
269 route("GET", "/workspaces/:workspace/teams/:team/teams", Op::ListChildTeams, &[]),
270 route("GET", "/workspaces/:workspace/teams/:team/repos", Op::ListTeamRepos, &[]),
271 route("PUT", "/workspaces/:workspace/teams/:team/repos/:repo", Op::SetTeamRepo, &[]),
272 route("DELETE", "/workspaces/:workspace/teams/:team/repos/:repo", Op::RemoveTeamRepo, &[]),
273 route(
274 "PUT",
275 "/workspaces/:workspace/teams/:team/review_assignment",
276 Op::SetTeamReviewAssignment,
277 &[],
278 ),
279 route("GET", "/workspaces/:workspace/members/:username/teams", Op::ListUserTeams, &[]),
Usage, Billing settings and prepaid AI credit; fixes from the UX audit280 // A workspace's billing: usage, budget, AI credit and invoices.
281 route(
282 "GET",
283 "/workspaces/:workspace/usage",
284 Op::GetUsage,
285 &[("from", "from"), ("until", "until"), ("products", "products"), ("projects", "projects"), ("group_by", "group_by")],
286 ),
287 route("GET", "/workspaces/:workspace/budget", Op::GetBudget, &[]),
288 route("PUT", "/workspaces/:workspace/budget", Op::SetBudget, &[]),
289 route("GET", "/workspaces/:workspace/ai_credit", Op::GetAiCredit, &[]),
290 route("POST", "/workspaces/:workspace/ai_credit/checkout", Op::BuyAiCredit, &[]),
291 route("GET", "/workspaces/:workspace/invoices", Op::ListInvoices, &[]),
292 route("GET", "/workspaces/:workspace/billing_details", Op::GetBillingDetails, &[]),
Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens293 // The AI Gateway's log of a workspace's requests.
294 route("GET", "/workspaces/:workspace/gateway/requests", Op::ListGatewayRequests, &[("limit", "limit"), ("before", "before")]),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar295 // Code owners: the CODEOWNERS file, checked.
296 route("GET", "/repos/:owner/:name/codeowners/errors", Op::GetCodeownersErrors, &[("ref", "ref")]),
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights297 // Security alerts: secrets and vulnerable dependencies.
298 route(
299 "GET",
300 "/repos/:owner/:name/security/alerts",
301 Op::ListSecurityAlerts,
302 &[("state", "state"), ("kind", "kind")],
303 ),
304 route("POST", "/repos/:owner/:name/security/alerts/:id/dismiss", Op::DismissSecurityAlert, &[]),
305 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 bar306 // The security suite: secret scanning, code scanning, vulnerability
307 // alerts and the supply chain, at the common addresses.
308 route("GET", "/repos/:owner/:name/secret-scanning/alerts", Op::Security(SecurityOp::ListSecretAlerts), &[("state", "state"), ("secret_type", "secret_type"), ("validity", "validity"), ("bypassed", "bypassed")]),
309 route("GET", "/workspaces/:workspace/secret-scanning/alerts", Op::Security(SecurityOp::ListSecretAlerts), &[("state", "state"), ("secret_type", "secret_type"), ("validity", "validity"), ("bypassed", "bypassed")]),
310 route("GET", "/repos/:owner/:name/secret-scanning/alerts/:id", Op::Security(SecurityOp::GetSecretAlert), &[]),
311 route("PATCH", "/repos/:owner/:name/secret-scanning/alerts/:id", Op::Security(SecurityOp::UpdateSecretAlert), &[]),
312 route("GET", "/repos/:owner/:name/secret-scanning/alerts/:id/locations", Op::Security(SecurityOp::ListSecretLocations), &[]),
313 route("POST", "/repos/:owner/:name/secret-scanning/alerts/:id/bypass", Op::Security(SecurityOp::BypassPushProtection), &[]),
314 route("POST", "/repos/:owner/:name/secret-scanning/alerts/:id/validity", Op::Security(SecurityOp::CheckSecretValidity), &[]),
315 route("GET", "/workspaces/:workspace/secret-scanning/bypass-requests", Op::Security(SecurityOp::ListBypassRequests), &[("state", "state"), ("repo", "repo")]),
316 route("PATCH", "/workspaces/:workspace/secret-scanning/bypass-requests/:id", Op::Security(SecurityOp::ReviewBypassRequest), &[]),
317 route("POST", "/repos/:owner/:name/secret-scanning/custom-patterns/dry-run", Op::Security(SecurityOp::DryRunCustomPattern), &[]),
318 route("POST", "/workspaces/:workspace/secret-scanning/custom-patterns/dry-run", Op::Security(SecurityOp::DryRunCustomPattern), &[]),
319 route("GET", "/repos/:owner/:name/secret-scanning/custom-patterns", Op::Security(SecurityOp::ListCustomPatterns), &[]),
320 route("GET", "/workspaces/:workspace/secret-scanning/custom-patterns", Op::Security(SecurityOp::ListCustomPatterns), &[]),
321 route("POST", "/repos/:owner/:name/secret-scanning/custom-patterns", Op::Security(SecurityOp::CreateCustomPattern), &[]),
322 route("POST", "/workspaces/:workspace/secret-scanning/custom-patterns", Op::Security(SecurityOp::CreateCustomPattern), &[]),
323 route("PATCH", "/repos/:owner/:name/secret-scanning/custom-patterns/:id", Op::Security(SecurityOp::UpdateCustomPattern), &[]),
324 route("PATCH", "/workspaces/:workspace/secret-scanning/custom-patterns/:id", Op::Security(SecurityOp::UpdateCustomPattern), &[]),
325 route("DELETE", "/repos/:owner/:name/secret-scanning/custom-patterns/:id", Op::Security(SecurityOp::DeleteCustomPattern), &[]),
326 route("DELETE", "/workspaces/:workspace/secret-scanning/custom-patterns/:id", Op::Security(SecurityOp::DeleteCustomPattern), &[]),
327 route("GET", "/repos/:owner/:name/code-scanning/alerts", Op::Security(SecurityOp::ListCodeAlerts), &[("state", "state"), ("severity", "severity"), ("tool", "tool"), ("rule_id", "rule_id")]),
328 route("GET", "/workspaces/:workspace/code-scanning/alerts", Op::Security(SecurityOp::ListCodeAlerts), &[("state", "state"), ("severity", "severity"), ("tool", "tool"), ("rule_id", "rule_id")]),
329 route("GET", "/repos/:owner/:name/code-scanning/alerts/:number", Op::Security(SecurityOp::GetCodeAlert), &[]),
330 route("PATCH", "/repos/:owner/:name/code-scanning/alerts/:number", Op::Security(SecurityOp::UpdateCodeAlert), &[]),
331 route("GET", "/repos/:owner/:name/code-scanning/analyses", Op::Security(SecurityOp::ListAnalyses), &[]),
332 route("POST", "/repos/:owner/:name/code-scanning/sarifs", Op::Security(SecurityOp::UploadSarif), &[]),
333 route("GET", "/repos/:owner/:name/code-scanning/sarifs/:id", Op::Security(SecurityOp::GetSarifUpload), &[]),
334 route("GET", "/repos/:owner/:name/vulnerability-alerts", Op::Security(SecurityOp::ListVulnerabilityAlerts), &[("state", "state"), ("severity", "severity"), ("ecosystem", "ecosystem"), ("package", "package")]),
335 route("GET", "/workspaces/:workspace/vulnerability-alerts", Op::Security(SecurityOp::ListVulnerabilityAlerts), &[("state", "state"), ("severity", "severity"), ("ecosystem", "ecosystem"), ("package", "package")]),
336 route("GET", "/repos/:owner/:name/vulnerability-alerts/:id", Op::Security(SecurityOp::GetVulnerabilityAlert), &[]),
337 route("PATCH", "/repos/:owner/:name/vulnerability-alerts/:id", Op::Security(SecurityOp::UpdateVulnerabilityAlert), &[]),
338 route("POST", "/repos/:owner/:name/security/alerts/:id/fix", Op::Security(SecurityOp::FixAlert), &[]),
339 route("GET", "/repos/:owner/:name/dependency-graph", Op::Security(SecurityOp::GetDependencyGraph), &[]),
340 route("GET", "/repos/:owner/:name/dependency-graph/sbom", Op::Security(SecurityOp::GetSbom), &[]),
341 route("GET", "/repos/:owner/:name/dependency-graph/compare/:basehead", Op::Security(SecurityOp::CompareDependencies), &[]),
342 route("GET", "/repos/:owner/:name/security/settings", Op::Security(SecurityOp::GetSettings), &[]),
343 route("PATCH", "/repos/:owner/:name/security/settings", Op::Security(SecurityOp::UpdateSettings), &[]),
344 route("GET", "/workspaces/:workspace/security/settings", Op::Security(SecurityOp::GetWorkspaceSettings), &[]),
345 route("PATCH", "/workspaces/:workspace/security/settings", Op::Security(SecurityOp::UpdateWorkspaceSettings), &[]),
346 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-7nm8r9347 // Reporting: a repository's advisories, and the vulnerability reports sent to its maintainers.
348 route("GET", "/repos/:owner/:name/security/advisories", Op::Security(SecurityOp::ListAdvisories), &[("state", "state")]),
349 route("POST", "/repos/:owner/:name/security/advisories", Op::Security(SecurityOp::CreateAdvisory), &[]),
350 route("GET", "/repos/:owner/:name/security/advisories/:id", Op::Security(SecurityOp::GetAdvisory), &[]),
351 route("PATCH", "/repos/:owner/:name/security/advisories/:id", Op::Security(SecurityOp::UpdateAdvisory), &[]),
352 route("POST", "/repos/:owner/:name/security/advisories/:id/state", Op::Security(SecurityOp::SetAdvisoryState), &[]),
353 route("GET", "/repos/:owner/:name/security/reports", Op::Security(SecurityOp::ListReports), &[("state", "state")]),
354 route("POST", "/repos/:owner/:name/security/reports", Op::Security(SecurityOp::ReportVulnerability), &[]),
355 route("PATCH", "/repos/:owner/:name/security/reports/:id", Op::Security(SecurityOp::ReviewReport), &[]),
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights356 route("GET", "/repos", Op::ListRepos, &[("q", "query")]),
357 route(
358 "GET",
359 "/search",
360 Op::Search,
361 &[("q", "query"), ("type", "type"), ("page", "page"), ("per_page", "per_page")],
362 ),
363 route("POST", "/repos", Op::CreateRepo, &[]),
364 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.365 // A branch, tag or commit as one archive: a redirect to a link good for
366 // ten minutes, as GitHub's go to codeload.
367 route("GET", "/repos/:owner/:name/tarball/:ref", Op::Archives(ArchiveOp::DownloadTarball), &[]),
368 route("GET", "/repos/:owner/:name/zipball/:ref", Op::Archives(ArchiveOp::DownloadZipball), &[]),
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights369 route("PATCH", "/repos/:owner/:name", Op::UpdateRepo, &[]),
370 route("POST", "/repos/:owner/:name/transfer", Op::TransferRepo, &[]),
371 route("DELETE", "/repos/:owner/:name", Op::DeleteRepo, &[]),
372 route(
373 "GET",
374 "/workspaces/:workspace/repos/deleted",
375 Op::ListDeletedRepos,
376 &[],
377 ),
378 route("POST", "/repos/:owner/:name/restore", Op::RestoreRepo, &[]),
379 route("POST", "/repos/:owner/:name/purge", Op::PurgeRepo, &[]),
380 route("POST", "/repos/:owner/:name/rename", Op::RenameRepo, &[]),
381 route("POST", "/repos/:owner/:name/archive", Op::ArchiveRepo, &[]),
382 route("POST", "/repos/:owner/:name/unarchive", Op::UnarchiveRepo, &[]),
383 route(
384 "POST",
385 "/repos/:owner/:name/visibility",
386 Op::SetRepoVisibility,
387 &[],
388 ),
389 route(
390 "POST",
391 "/repos/:owner/:name/branches/:branch/rename",
392 Op::RenameBranch,
393 &[],
394 ),
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.395 route("POST", "/repos/:owner/:name/branches", Op::CreateBranch, &[]),
396 route("DELETE", "/repos/:owner/:name/branches/:branch", Op::DeleteBranch, &[("head", "head")]),
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights397 route(
398 "GET",
399 "/repos/:owner/:name/settings",
400 Op::GetRepoSettings,
401 &[],
402 ),
403 route(
404 "PATCH",
405 "/repos/:owner/:name/settings",
406 Op::UpdateRepoSettings,
407 &[],
408 ),
409 route("GET", "/repos/:owner/:name/check-names", Op::ListCheckNames, &[]),
Merge checks: statuses and check runs on every commit410 // Checks: GitHub's addresses for statuses, check runs and check suites.
411 route("POST", "/repos/:owner/:name/statuses/:sha", Op::Checks(ChecksOp::CreateCommitStatus), &[]),
412 route("GET", "/repos/:owner/:name/commits/:ref/statuses", Op::Checks(ChecksOp::ListCommitStatuses), &[]),
413 route("GET", "/repos/:owner/:name/commits/:ref/status", Op::Checks(ChecksOp::GetCombinedStatus), &[]),
414 route(
415 "GET",
416 "/repos/:owner/:name/commits/:ref/check-runs",
417 Op::Checks(ChecksOp::ListCheckRunsForRef),
418 &[("check_name", "check_name"), ("status", "status"), ("app", "app"), ("filter", "filter")],
419 ),
420 route(
421 "GET",
422 "/repos/:owner/:name/commits/:ref/check-suites",
423 Op::Checks(ChecksOp::ListCheckSuitesForRef),
424 &[("app", "app"), ("check_name", "check_name")],
425 ),
426 route("POST", "/repos/:owner/:name/check-runs", Op::Checks(ChecksOp::CreateCheckRun), &[]),
427 route("GET", "/repos/:owner/:name/check-runs/:id", Op::Checks(ChecksOp::GetCheckRun), &[]),
428 route("PATCH", "/repos/:owner/:name/check-runs/:id", Op::Checks(ChecksOp::UpdateCheckRun), &[]),
429 route("GET", "/repos/:owner/:name/check-runs/:id/annotations", Op::Checks(ChecksOp::ListCheckRunAnnotations), &[]),
430 route("POST", "/repos/:owner/:name/check-runs/:id/rerequest", Op::Checks(ChecksOp::RerequestCheckRun), &[]),
431 route("GET", "/repos/:owner/:name/check-suites/:id", Op::Checks(ChecksOp::GetCheckSuite), &[]),
432 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 merge433 // Rulesets: a repository's, a workspace's, the rules of one branch,
434 // and how they judged pushes and merges.
435 route("GET", "/repos/:owner/:name/rulesets", Op::Rules(RulesOp::ListRepoRulesets), &[("include_parents", "include_parents")]),
436 route("POST", "/repos/:owner/:name/rulesets", Op::Rules(RulesOp::CreateRepoRuleset), &[]),
437 route("GET", "/repos/:owner/:name/rulesets/:id", Op::Rules(RulesOp::GetRepoRuleset), &[]),
438 route("PUT", "/repos/:owner/:name/rulesets/:id", Op::Rules(RulesOp::UpdateRepoRuleset), &[]),
439 route("DELETE", "/repos/:owner/:name/rulesets/:id", Op::Rules(RulesOp::DeleteRepoRuleset), &[]),
440 route("GET", "/repos/:owner/:name/rules/branches/:branch", Op::Rules(RulesOp::GetBranchRules), &[("target", "target")]),
441 route(
442 "GET",
443 "/repos/:owner/:name/rules/evaluations",
444 Op::Rules(RulesOp::ListRuleEvaluations),
445 &[("ruleset_id", "ruleset_id"), ("verdict", "verdict"), ("problems_only", "problems_only"), ("before", "before"), ("limit", "limit")],
446 ),
447 route("GET", "/workspaces/:workspace/rulesets", Op::Rules(RulesOp::ListWorkspaceRulesets), &[]),
448 route("POST", "/workspaces/:workspace/rulesets", Op::Rules(RulesOp::CreateWorkspaceRuleset), &[]),
449 route("GET", "/workspaces/:workspace/rulesets/:id", Op::Rules(RulesOp::GetWorkspaceRuleset), &[]),
450 route("PUT", "/workspaces/:workspace/rulesets/:id", Op::Rules(RulesOp::UpdateWorkspaceRuleset), &[]),
451 route("DELETE", "/workspaces/:workspace/rulesets/:id", Op::Rules(RulesOp::DeleteWorkspaceRuleset), &[]),
452 route(
453 "GET",
454 "/workspaces/:workspace/rules/evaluations",
455 Op::Rules(RulesOp::ListWorkspaceRuleEvaluations),
456 &[("ruleset_id", "ruleset_id"), ("verdict", "verdict"), ("problems_only", "problems_only"), ("before", "before"), ("limit", "limit")],
457 ),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97458 // Deployments wherever they run, their statuses, and environments.
459 route(
460 "GET",
461 "/repos/:owner/:name/deployments",
462 Op::Deployments(DeploymentsOp::ListDeployments),
463 &[("environment", "environment"), ("ref", "ref"), ("sha", "sha"), ("task", "task"), ("state", "state"), ("source", "source"), ("creator", "creator"), ("page", "page"), ("per_page", "per_page")],
464 ),
465 route("POST", "/repos/:owner/:name/deployments", Op::Deployments(DeploymentsOp::CreateDeployment), &[]),
Merge branch 'worktree-agent-a4d35ee70c50683d0' into claude/cool-newton-7nm8r9466 // A g1t Actions job opening a g1t.page build it runs itself.
467 route("POST", "/repos/:owner/:name/deployments/builds", Op::Deployments(DeploymentsOp::StartDeploymentBuild), &[]),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97468 route("GET", "/repos/:owner/:name/deployments/:id", Op::Deployments(DeploymentsOp::GetDeployment), &[]),
469 route("GET", "/repos/:owner/:name/deployments/:id/statuses", Op::Deployments(DeploymentsOp::ListDeploymentStatuses), &[]),
470 route("POST", "/repos/:owner/:name/deployments/:id/statuses", Op::Deployments(DeploymentsOp::CreateDeploymentStatus), &[]),
471 route("GET", "/repos/:owner/:name/environments", Op::Deployments(DeploymentsOp::ListEnvironments), &[]),
472 route("GET", "/repos/:owner/:name/environments/:environment", Op::Deployments(DeploymentsOp::GetEnvironment), &[]),
Merge branch 'worktree-agent-a3abfcce648e87dca'473 // Environments' protection rules, and the runs they hold.
474 route("PUT", "/repos/:owner/:name/environments/:environment", Op::Protection(ProtectionOp::UpdateEnvironment), &[]),
475 route("DELETE", "/repos/:owner/:name/environments/:environment", Op::Protection(ProtectionOp::DeleteEnvironment), &[]),
476 route(
477 "GET",
478 "/repos/:owner/:name/actions/runs/:id/pending_deployments",
479 Op::Protection(ProtectionOp::GetPendingDeployments),
480 &[],
481 ),
482 route(
483 "POST",
484 "/repos/:owner/:name/actions/runs/:id/pending_deployments",
485 Op::Protection(ProtectionOp::ReviewPendingDeployments),
486 &[],
487 ),
488 route("POST", "/repos/:owner/:name/actions/runs/:id/approve", Op::Protection(ProtectionOp::ApproveWorkflowRun), &[]),
489 route("GET", "/repos/:owner/:name/actions/permissions/workflow", Op::Protection(ProtectionOp::GetWorkflowPermissions), &[]),
490 route("PUT", "/repos/:owner/:name/actions/permissions/workflow", Op::Protection(ProtectionOp::SetWorkflowPermissions), &[]),
491 route(
492 "GET",
493 "/repos/:owner/:name/actions/permissions/fork-pr-contributor-approval",
494 Op::Protection(ProtectionOp::GetForkPrApproval),
495 &[],
496 ),
497 route(
498 "PUT",
499 "/repos/:owner/:name/actions/permissions/fork-pr-contributor-approval",
500 Op::Protection(ProtectionOp::SetForkPrApproval),
501 &[],
502 ),
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts503 route("GET", "/repos/:owner/:name/actions/permissions/access", Op::Protection(ProtectionOp::GetActionsAccess), &[]),
504 route("PUT", "/repos/:owner/:name/actions/permissions/access", Op::Protection(ProtectionOp::SetActionsAccess), &[]),
Merge branch 'worktree-agent-a3abfcce648e87dca'505 route("POST", "/repos/:owner/:name/dispatches", Op::Protection(ProtectionOp::CreateRepositoryDispatch), &[]),
506 route(
507 "GET",
508 "/workspaces/:workspace/actions/permissions/workflow",
509 Op::Protection(ProtectionOp::GetWorkspaceWorkflowPermissions),
510 &[],
511 ),
512 route(
513 "PUT",
514 "/workspaces/:workspace/actions/permissions/workflow",
515 Op::Protection(ProtectionOp::SetWorkspaceWorkflowPermissions),
516 &[],
517 ),
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.518 route(
519 "GET",
520 "/workspaces/:workspace/actions/managed-workflows",
521 Op::Protection(ProtectionOp::ListWorkspaceManagedWorkflows),
522 &[],
523 ),
524 route(
525 "POST",
526 "/workspaces/:workspace/actions/managed-workflows",
527 Op::Protection(ProtectionOp::CreateWorkspaceManagedWorkflow),
528 &[],
529 ),
530 route(
531 "PATCH",
532 "/workspaces/:workspace/actions/managed-workflows/:workflow",
533 Op::Protection(ProtectionOp::UpdateWorkspaceManagedWorkflow),
534 &[],
535 ),
536 route(
537 "DELETE",
538 "/workspaces/:workspace/actions/managed-workflows/:workflow",
539 Op::Protection(ProtectionOp::DeleteWorkspaceManagedWorkflow),
540 &[],
541 ),
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights542 route("GET", "/repos/:owner/:name/queue", Op::GetMergeQueue, &[]),
543 route(
544 "POST",
545 "/repos/:owner/:name/pulls/:number/messages",
546 Op::MessageAgent,
547 &[],
548 ),
549 route(
550 "POST",
551 "/repos/:owner/:name/pulls/:number/messages/take",
552 Op::TakeMessages,
553 &[],
554 ),
555 route(
556 "POST",
557 "/repos/:owner/:name/messages/:id/answer",
558 Op::AnswerMessage,
559 &[],
560 ),
561 route("POST", "/repos/:owner/:name/memory", Op::Remember, &[]),
562 route(
563 "GET",
564 "/repos/:owner/:name/memory",
565 Op::Recall,
566 &[("q", "query"), ("limit", "limit")],
567 ),
568 route(
569 "GET",
570 "/repos/:owner/:name/events",
571 Op::ListEvents,
572 &[("before", "before")],
573 ),
574 route("GET", "/repos/:owner/:name/labels", Op::ListLabels, &[]),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar575 route("POST", "/repos/:owner/:name/labels", Op::CreateLabel, &[]),
576 route("POST", "/repos/:owner/:name/labels/defaults", Op::AddDefaultLabels, &[]),
577 route("PATCH", "/repos/:owner/:name/labels/:label", Op::UpdateLabel, &[]),
578 route("DELETE", "/repos/:owner/:name/labels/:label", Op::DeleteLabel, &[]),
579 route("GET", "/repos/:owner/:name/issues/:number/labels", Op::ListIssueLabels, &[]),
580 route("POST", "/repos/:owner/:name/issues/:number/labels", Op::AddIssueLabels, &[]),
581 route("PUT", "/repos/:owner/:name/issues/:number/labels", Op::SetIssueLabels, &[]),
582 route("DELETE", "/repos/:owner/:name/issues/:number/labels", Op::RemoveIssueLabels, &[]),
583 route("DELETE", "/repos/:owner/:name/issues/:number/labels/:label", Op::RemoveIssueLabels, &[]),
584 route("GET", "/repos/:owner/:name/milestones", Op::ListMilestones, &[("state", "state")]),
585 route("POST", "/repos/:owner/:name/milestones", Op::CreateMilestone, &[]),
586 route("GET", "/repos/:owner/:name/milestones/:milestone", Op::GetMilestone, &[]),
587 route("PATCH", "/repos/:owner/:name/milestones/:milestone", Op::UpdateMilestone, &[]),
588 route("DELETE", "/repos/:owner/:name/milestones/:milestone", Op::DeleteMilestone, &[]),
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights589 route(
590 "GET",
591 "/repos/:owner/:name/issues",
592 Op::ListIssues,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar593 &[("state", "state"), ("label", "label"), ("milestone", "milestone")],
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights594 ),
595 route("POST", "/repos/:owner/:name/issues", Op::CreateIssue, &[]),
596 route(
597 "GET",
598 "/repos/:owner/:name/issues/:number",
599 Op::GetIssue,
600 &[],
601 ),
602 route(
603 "PATCH",
604 "/repos/:owner/:name/issues/:number",
605 Op::UpdateIssue,
606 &[],
607 ),
608 route(
609 "POST",
610 "/repos/:owner/:name/issues/:number/close",
611 Op::CloseIssue,
612 &[],
613 ),
614 route(
615 "POST",
616 "/repos/:owner/:name/issues/:number/reopen",
617 Op::ReopenIssue,
618 &[],
619 ),
620 route(
621 "POST",
622 "/repos/:owner/:name/issues/:number/assign",
623 Op::AssignIssue,
624 &[],
625 ),
626 route(
627 "POST",
628 "/repos/:owner/:name/issues/import",
629 Op::ImportIssue,
630 &[],
631 ),
632 route(
633 "POST",
634 "/repos/:owner/:name/issues/delegate",
635 Op::Delegate,
636 &[],
637 ),
638 route(
639 "GET",
640 "/repos/:owner/:name/context",
641 Op::GetContext,
642 &[("reference", "reference")],
643 ),
644 route(
645 "GET",
646 "/workspaces/:workspace/context/search",
647 Op::SearchContext,
648 &[("q", "query"), ("project", "project"), ("kinds", "kinds"), ("limit", "limit")],
649 ),
650 route(
651 "GET",
652 "/workspaces/:workspace/context/:kind/:id",
653 Op::GetEntity,
654 &[],
655 ),
656 route(
657 "GET",
658 "/workspaces/:workspace/integrations",
659 Op::ListIntegrations,
660 &[],
661 ),
662 route(
663 "POST",
664 "/workspaces/:workspace/integrations",
665 Op::ConnectIntegration,
666 &[],
667 ),
668 route(
669 "GET",
670 "/repos/:owner/:name/hooks",
671 Op::ListWebhooks,
672 &[],
673 ),
674 route(
675 "POST",
676 "/repos/:owner/:name/hooks",
677 Op::CreateWebhook,
678 &[],
679 ),
680 route(
681 "PATCH",
682 "/repos/:owner/:name/hooks/:id",
683 Op::UpdateWebhook,
684 &[],
685 ),
686 route(
687 "DELETE",
688 "/repos/:owner/:name/hooks/:id",
689 Op::DeleteWebhook,
690 &[],
691 ),
692 route(
693 "POST",
694 "/repos/:owner/:name/hooks/:id/pings",
695 Op::PingWebhook,
696 &[],
697 ),
698 route(
699 "GET",
700 "/repos/:owner/:name/hooks/:id/deliveries",
701 Op::ListWebhookDeliveries,
702 &[],
703 ),
704 route(
705 "POST",
706 "/repos/:owner/:name/hooks/:id/deliveries/:delivery/redeliver",
707 Op::RedeliverWebhook,
708 &[],
709 ),
710 route(
711 "GET",
712 "/workspaces/:workspace/hooks",
713 Op::ListWebhooks,
714 &[],
715 ),
716 route(
717 "POST",
718 "/workspaces/:workspace/hooks",
719 Op::CreateWebhook,
720 &[],
721 ),
722 route(
723 "PATCH",
724 "/workspaces/:workspace/hooks/:id",
725 Op::UpdateWebhook,
726 &[],
727 ),
728 route(
729 "DELETE",
730 "/workspaces/:workspace/hooks/:id",
731 Op::DeleteWebhook,
732 &[],
733 ),
734 route(
735 "POST",
736 "/workspaces/:workspace/hooks/:id/pings",
737 Op::PingWebhook,
738 &[],
739 ),
740 route(
741 "GET",
742 "/workspaces/:workspace/hooks/:id/deliveries",
743 Op::ListWebhookDeliveries,
744 &[],
745 ),
746 route(
747 "POST",
748 "/workspaces/:workspace/hooks/:id/deliveries/:delivery/redeliver",
749 Op::RedeliverWebhook,
750 &[],
751 ),
752 route(
753 "GET",
754 "/workspaces/:workspace/model-routes",
755 Op::GetModelRoutes,
756 &[],
757 ),
758 route(
759 "PUT",
760 "/workspaces/:workspace/model-routes",
761 Op::SetModelRoutes,
762 &[],
763 ),
764 route(
AI Gateway: OpenAI's format, open models, and your own providers765 "PATCH",
766 "/workspaces/:workspace/integrations/:id",
767 Op::UpdateIntegration,
768 &[],
769 ),
770 route(
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights771 "DELETE",
772 "/workspaces/:workspace/integrations/:id",
773 Op::DisconnectIntegration,
774 &[],
775 ),
776 route(
777 "POST",
778 "/workspaces/:workspace/integrations/:id/test",
779 Op::TestIntegration,
780 &[],
781 ),
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9782 // Customize: marketplaces, Discover, plugins and connectors. A plugin's
783 // ref is `name@marketplace`, URL-encoded (`@` as `%40`).
784 route("GET", "/workspaces/:workspace/customize/marketplaces", Op::Customize(CustomizeOp::ListMarketplaces), &[]),
785 route("POST", "/workspaces/:workspace/customize/marketplaces", Op::Customize(CustomizeOp::AddMarketplace), &[]),
786 route("DELETE", "/workspaces/:workspace/customize/marketplaces/:marketplace", Op::Customize(CustomizeOp::RemoveMarketplace), &[]),
787 route("POST", "/workspaces/:workspace/customize/marketplaces/:marketplace/refresh", Op::Customize(CustomizeOp::RefreshMarketplace), &[]),
788 route("GET", "/workspaces/:workspace/customize/discover", Op::Customize(CustomizeOp::Discover), &[]),
789 route("GET", "/workspaces/:workspace/customize/plugins", Op::Customize(CustomizeOp::ListPlugins), &[]),
790 route("POST", "/workspaces/:workspace/customize/plugins", Op::Customize(CustomizeOp::InstallPlugin), &[]),
791 route("GET", "/workspaces/:workspace/customize/plugins/:ref", Op::Customize(CustomizeOp::GetPlugin), &[]),
792 route("PATCH", "/workspaces/:workspace/customize/plugins/:ref", Op::Customize(CustomizeOp::SetPluginEnabled), &[]),
793 route("DELETE", "/workspaces/:workspace/customize/plugins/:ref", Op::Customize(CustomizeOp::UninstallPlugin), &[]),
794 route("POST", "/workspaces/:workspace/customize/plugins/:ref/update", Op::Customize(CustomizeOp::UpdatePlugin), &[]),
795 route("GET", "/workspaces/:workspace/customize/agents", Op::Customize(CustomizeOp::ListPluginAgents), &[]),
796 route("GET", "/workspaces/:workspace/customize/connectors", Op::Customize(CustomizeOp::ListConnectors), &[]),
797 route("POST", "/workspaces/:workspace/customize/connectors", Op::Customize(CustomizeOp::AddConnector), &[]),
798 route("GET", "/workspaces/:workspace/customize/connectors/:id", Op::Customize(CustomizeOp::GetConnector), &[]),
799 route("PATCH", "/workspaces/:workspace/customize/connectors/:id", Op::Customize(CustomizeOp::SetConnectorKey), &[]),
800 route("DELETE", "/workspaces/:workspace/customize/connectors/:id", Op::Customize(CustomizeOp::RemoveConnector), &[]),
801 route("POST", "/workspaces/:workspace/customize/connectors/:id/refresh", Op::Customize(CustomizeOp::RefreshConnector), &[]),
802 route("POST", "/workspaces/:workspace/customize/connectors/:id/grants", Op::Customize(CustomizeOp::GrantConnector), &[]),
803 route("DELETE", "/workspaces/:workspace/customize/connectors/:id/grants/:grant", Op::Customize(CustomizeOp::RevokeConnector), &[]),
804 route("GET", "/workspaces/:workspace/customize/bundle", Op::Customize(CustomizeOp::Bundle), &[]),
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights805 route(
806 "GET",
807 "/repos/:owner/:name/actions/workflows",
808 Op::ListWorkflows,
809 &[],
810 ),
811 route(
812 "GET",
813 "/repos/:owner/:name/actions/workflows/:workflow/runs",
814 Op::ListWorkflowRuns,
815 &[("branch", "branch"), ("event", "event"), ("per_page", "limit")],
816 ),
817 route(
818 "POST",
819 "/repos/:owner/:name/actions/workflows/:workflow/dispatches",
820 Op::DispatchWorkflow,
821 &[],
822 ),
823 route(
824 "PATCH",
825 "/repos/:owner/:name/actions/workflows/:workflow",
826 Op::UpdateWorkflow,
827 &[],
828 ),
829 route(
830 "PUT",
831 "/repos/:owner/:name/actions/workflows/:workflow/enable",
832 Op::UpdateWorkflow,
833 &[],
834 ),
835 route(
836 "PUT",
837 "/repos/:owner/:name/actions/workflows/:workflow/disable",
838 Op::UpdateWorkflow,
839 &[],
840 ),
841 route(
842 "GET",
843 "/repos/:owner/:name/actions/runs",
844 Op::ListWorkflowRuns,
845 &[("workflow", "workflow"), ("branch", "branch"), ("event", "event"), ("pull", "pull"), ("head_sha", "sha"), ("per_page", "limit")],
846 ),
847 route(
848 "GET",
849 "/repos/:owner/:name/actions/runs/:id",
850 Op::GetWorkflowRun,
851 &[],
852 ),
853 route(
Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009)854 "GET",
855 "/repos/:owner/:name/actions/runs/:id/attempts/:attempt",
856 Op::GetWorkflowRun,
857 &[],
858 ),
859 route(
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights860 "POST",
861 "/repos/:owner/:name/actions/runs/:id/cancel",
862 Op::CancelWorkflowRun,
863 &[],
864 ),
865 route(
866 "POST",
Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009)867 "/repos/:owner/:name/actions/runs/:id/force-cancel",
868 Op::CancelWorkflowRun,
869 &[],
870 ),
871 route(
872 "POST",
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights873 "/repos/:owner/:name/actions/runs/:id/rerun",
874 Op::RerunWorkflowRun,
875 &[],
876 ),
877 route(
878 "POST",
879 "/repos/:owner/:name/actions/runs/:id/rerun-failed-jobs",
880 Op::RerunWorkflowRun,
881 &[],
882 ),
883 route(
Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009)884 "POST",
885 "/repos/:owner/:name/actions/jobs/:job/rerun",
886 Op::RerunWorkflowRun,
887 &[],
888 ),
889 route(
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights890 "GET",
891 "/repos/:owner/:name/actions/jobs/:job/logs",
892 Op::GetJobLogs,
893 &[("after", "after")],
894 ),
Merge packages: roles, Actions access, source label, soft delete, API895 // Packages, at GitHub's addresses with the workspace in place of the
896 // organization. A name with a slash is one URL-encoded segment.
897 route("GET", "/workspaces/:workspace/packages", Op::Packages(PackagesOp::ListPackages), &[("package_type", "package_type"), ("q", "q"), ("state", "state")]),
898 route("GET", "/workspaces/:workspace/packages/:package_type/:package_name", Op::Packages(PackagesOp::GetPackage), &[]),
899 route("PATCH", "/workspaces/:workspace/packages/:package_type/:package_name", Op::Packages(PackagesOp::UpdatePackage), &[]),
900 route("DELETE", "/workspaces/:workspace/packages/:package_type/:package_name", Op::Packages(PackagesOp::DeletePackage), &[]),
901 route("POST", "/workspaces/:workspace/packages/:package_type/:package_name/restore", Op::Packages(PackagesOp::RestorePackage), &[]),
902 route("GET", "/workspaces/:workspace/packages/:package_type/:package_name/versions", Op::Packages(PackagesOp::ListVersions), &[("state", "state")]),
903 route("GET", "/workspaces/:workspace/packages/:package_type/:package_name/versions/:version_id", Op::Packages(PackagesOp::GetVersion), &[]),
904 route("DELETE", "/workspaces/:workspace/packages/:package_type/:package_name/versions/:version_id", Op::Packages(PackagesOp::DeleteVersion), &[]),
905 route("POST", "/workspaces/:workspace/packages/:package_type/:package_name/versions/:version_id/restore", Op::Packages(PackagesOp::RestoreVersion), &[]),
906 route("PUT", "/workspaces/:workspace/packages/:package_type/:package_name/repository", Op::Packages(PackagesOp::LinkPackage), &[]),
907 route("DELETE", "/workspaces/:workspace/packages/:package_type/:package_name/repository", Op::Packages(PackagesOp::UnlinkPackage), &[]),
908 route("GET", "/workspaces/:workspace/packages/:package_type/:package_name/access", Op::Packages(PackagesOp::ListAccess), &[]),
909 route("PUT", "/workspaces/:workspace/packages/:package_type/:package_name/access", Op::Packages(PackagesOp::SetAccess), &[]),
910 route("DELETE", "/workspaces/:workspace/packages/:package_type/:package_name/access", Op::Packages(PackagesOp::RemoveAccess), &[("username", "username"), ("team", "team")]),
911 route("GET", "/workspaces/:workspace/packages/:package_type/:package_name/actions-access", Op::Packages(PackagesOp::ListActionsAccess), &[]),
912 route("PUT", "/workspaces/:workspace/packages/:package_type/:package_name/actions-access", Op::Packages(PackagesOp::SetActionsAccess), &[]),
913 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 R2914 // Artifacts, at GitHub's addresses. `…/zip` answers with a redirect to
915 // a signed link (lib.rs).
916 route("GET", "/repos/:owner/:name/actions/artifacts", Op::Artifacts(ArtifactsOp::ListArtifacts), &[("name", "name"), ("page", "page"), ("per_page", "per_page")]),
917 route("GET", "/repos/:owner/:name/actions/runs/:id/artifacts", Op::Artifacts(ArtifactsOp::ListRunArtifacts), &[("name", "name")]),
918 route("GET", "/repos/:owner/:name/actions/artifacts/:id", Op::Artifacts(ArtifactsOp::GetArtifact), &[]),
919 route("GET", "/repos/:owner/:name/actions/artifacts/:id/zip", Op::Artifacts(ArtifactsOp::DownloadArtifact), &[]),
920 route("DELETE", "/repos/:owner/:name/actions/artifacts/:id", Op::Artifacts(ArtifactsOp::DeleteArtifact), &[]),
921 route("GET", "/repos/:owner/:name/actions/permissions/artifact-and-log-retention", Op::Artifacts(ArtifactsOp::GetArtifactRetention), &[]),
922 route("PUT", "/repos/:owner/:name/actions/permissions/artifact-and-log-retention", Op::Artifacts(ArtifactsOp::SetArtifactRetention), &[]),
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights923 route(
924 "GET",
925 "/repos/:owner/:name/actions/secrets",
926 Op::ListActionsSecrets,
927 &[],
928 ),
929 route(
930 "PUT",
931 "/repos/:owner/:name/actions/secrets/:setting",
932 Op::SetActionsSecret,
933 &[],
934 ),
935 route(
936 "DELETE",
937 "/repos/:owner/:name/actions/secrets/:setting",
938 Op::DeleteActionsSecret,
939 &[],
940 ),
941 route(
942 "GET",
943 "/repos/:owner/:name/actions/variables",
944 Op::ListActionsVariables,
945 &[],
946 ),
947 route(
948 "POST",
949 "/repos/:owner/:name/actions/variables",
950 Op::SetActionsVariable,
951 &[],
952 ),
953 route(
954 "PATCH",
955 "/repos/:owner/:name/actions/variables/:setting",
956 Op::SetActionsVariable,
957 &[],
958 ),
959 route(
960 "DELETE",
961 "/repos/:owner/:name/actions/variables/:setting",
962 Op::DeleteActionsVariable,
963 &[],
964 ),
965 route(
966 "GET",
967 "/workspaces/:workspace/actions/secrets",
968 Op::ListActionsSecrets,
969 &[],
970 ),
971 route(
972 "PUT",
973 "/workspaces/:workspace/actions/secrets/:setting",
974 Op::SetActionsSecret,
975 &[],
976 ),
977 route(
978 "DELETE",
979 "/workspaces/:workspace/actions/secrets/:setting",
980 Op::DeleteActionsSecret,
981 &[],
982 ),
983 route(
984 "GET",
985 "/workspaces/:workspace/actions/variables",
986 Op::ListActionsVariables,
987 &[],
988 ),
989 route(
990 "POST",
991 "/workspaces/:workspace/actions/variables",
992 Op::SetActionsVariable,
993 &[],
994 ),
995 route(
996 "PATCH",
997 "/workspaces/:workspace/actions/variables/:setting",
998 Op::SetActionsVariable,
999 &[],
1000 ),
1001 route(
1002 "DELETE",
1003 "/workspaces/:workspace/actions/variables/:setting",
1004 Op::DeleteActionsVariable,
1005 &[],
1006 ),
1007 // Self-hosted runners: a repository's own, or a workspace's.
1008 route("GET", "/repos/:owner/:name/actions/runners", Op::ListRunners, &[]),
1009 route("POST", "/repos/:owner/:name/actions/runners/registration-token", Op::CreateRunnerRegistrationToken, &[]),
1010 route("DELETE", "/repos/:owner/:name/actions/runners/:id", Op::RemoveRunner, &[]),
1011 route("GET", "/repos/:owner/:name/actions/runner-settings", Op::GetRunnerSettings, &[]),
1012 route("PATCH", "/repos/:owner/:name/actions/runner-settings", Op::UpdateRunnerSettings, &[]),
1013 route("GET", "/workspaces/:workspace/actions/runners", Op::ListRunners, &[]),
1014 route("POST", "/workspaces/:workspace/actions/runners/registration-token", Op::CreateRunnerRegistrationToken, &[]),
1015 route("DELETE", "/workspaces/:workspace/actions/runners/:id", Op::RemoveRunner, &[]),
1016 route("GET", "/workspaces/:workspace/actions/runner-settings", Op::GetRunnerSettings, &[]),
1017 route("PATCH", "/workspaces/:workspace/actions/runner-settings", Op::UpdateRunnerSettings, &[]),
1018 route("GET", "/workspaces/:workspace/actions/runner-groups", Op::ListRunnerGroups, &[]),
1019 route("POST", "/workspaces/:workspace/actions/runner-groups", Op::CreateRunnerGroup, &[]),
1020 route("PATCH", "/workspaces/:workspace/actions/runner-groups/:id", Op::UpdateRunnerGroup, &[]),
1021 route("DELETE", "/workspaces/:workspace/actions/runner-groups/:id", Op::DeleteRunnerGroup, &[]),
Every agent can have its own computer. A session that needs one wakes it: a home of its own on g1t cloud, one per agent and never shared, where it runs commands, reads and writes files and keeps what it made, with each session working in its own folder under a shared home; after ten idle minutes it sleeps, its home kept as a snapshot and restored when it wakes, and Reset wipes the home while memory and artifacts stay. Its shell and files are abilities with the usual choices, Alone, Alone when asked, Ask first or Never, offered only inside sessions and never to a chat reply; every command shows on the session with its output, and the agent's new Computer tab shows the state, the disk used of the five gigabytes included, the recent commands, and Wake, Put to sleep and Reset. Machine time counts only while it is awake, on the sandbox lines of the ledger that name the agent and who asked, held to the same spend caps as the session; the disk itself costs nothing in this version. The runner gained a long-lived supervisor that answers the computer's requests inside the container, and the runner service a computer per agent that keeps its snapshot in the agent homes bucket when one is attached, and says so when none is. The REST API and the agent tool can read a computer, wake it, put it to sleep and reset it. The agents, abilities, sessions, runners, billing and deploy guides say how it works and what an operator sets up; pinning a computer to your own runner, its browser and take-over come next.1022 // A workspace agent's own computer.
1023 route("GET", "/workspaces/:workspace/agents/:agent/computer", Op::GetAgentComputer, &[]),
1024 route("POST", "/workspaces/:workspace/agents/:agent/computer/wake", Op::WakeAgentComputer, &[]),
1025 route("POST", "/workspaces/:workspace/agents/:agent/computer/sleep", Op::SleepAgentComputer, &[]),
1026 route("POST", "/workspaces/:workspace/agents/:agent/computer/reset", Op::ResetAgentComputer, &[]),
1027 route("GET", "/workspaces/:workspace/agents/:agent/computer/commands", Op::ListAgentComputerCommands, &[]),
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1028 route("POST", "/repos/:owner/:name/plans", Op::PlanWork, &[]),
1029 route("GET", "/repos/:owner/:name/plans/:plan", Op::GetPlan, &[]),
1030 route(
1031 "POST",
1032 "/repos/:owner/:name/plans/:plan/apply",
1033 Op::ApplyPlan,
1034 &[],
1035 ),
1036 route(
1037 "POST",
1038 "/repos/:owner/:name/issues/:number/comments",
1039 Op::AddComment,
1040 &[],
1041 ),
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1042 route("PATCH", "/repos/:owner/:name/issues/comments/:comment_id", Op::EditComment, &[]),
1043 route("DELETE", "/repos/:owner/:name/issues/comments/:comment_id", Op::DeleteComment, &[]),
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1044 route(
1045 "GET",
1046 "/repos/:owner/:name/pulls",
1047 Op::ListPullRequests,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1048 &[("state", "state"), ("label", "label"), ("milestone", "milestone"), ("base", "base")],
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1049 ),
1050 route(
1051 "POST",
1052 "/repos/:owner/:name/pulls",
1053 Op::CreatePullRequest,
1054 &[],
1055 ),
1056 route(
1057 "GET",
1058 "/repos/:owner/:name/pulls/:number",
1059 Op::GetPullRequest,
1060 &[],
1061 ),
1062 route(
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1063 "PATCH",
1064 "/repos/:owner/:name/pulls/:number",
1065 Op::UpdatePullRequest,
1066 &[],
1067 ),
1068 route(
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1069 "GET",
1070 "/repos/:owner/:name/pulls/:number/changes",
1071 Op::GetPullRequestChanges,
1072 &[],
1073 ),
1074 route(
1075 "POST",
1076 "/repos/:owner/:name/pulls/:number/reviews",
1077 Op::ReviewPullRequest,
1078 &[],
1079 ),
1080 route(
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1081 "POST",
1082 "/repos/:owner/:name/pulls/:number/requested_reviewers",
1083 Op::RequestReviewers,
1084 &[],
1085 ),
1086 route(
1087 "DELETE",
1088 "/repos/:owner/:name/pulls/:number/requested_reviewers",
1089 Op::RemoveRequestedReviewers,
1090 &[],
1091 ),
1092 route(
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1093 "GET",
1094 "/repos/:owner/:name/pulls/:number/session",
1095 Op::ReadSession,
1096 &[("after", "after")],
1097 ),
1098 route(
1099 "POST",
1100 "/repos/:owner/:name/pulls/:number/session",
1101 Op::RecordSession,
1102 &[],
1103 ),
1104 route(
1105 "POST",
1106 "/repos/:owner/:name/pulls/:number/ready",
1107 Op::MarkPullRequestReady,
1108 &[],
1109 ),
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1110 route("POST", "/repos/:owner/:name/pulls/:number/draft", Op::ConvertPullRequestToDraft, &[]),
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1111 route(
1112 "POST",
1113 "/repos/:owner/:name/pulls/:number/close",
1114 Op::ClosePullRequest,
1115 &[],
1116 ),
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1117 route("POST", "/repos/:owner/:name/pulls/:number/reopen", Op::ReopenPullRequest, &[]),
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1118 route(
1119 "POST",
1120 "/repos/:owner/:name/pulls/:number/merge",
1121 Op::MergePullRequest,
1122 &[],
1123 ),
Merge main into Artifacts Phase 21124 // Artifacts mode's docs, slides, designs and dashboards. Search comes
1125 // before an artifact by id, which it would otherwise match.
1126 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")]),
1127 route("POST", "/workspaces/:workspace/artifacts", Op::Folios(FoliosOp::Create), &[]),
1128 route("GET", "/workspaces/:workspace/artifacts/search", Op::Folios(FoliosOp::Search), &[("q", "q"), ("kind", "kind"), ("space", "space"), ("project", "project"), ("limit", "limit")]),
1129 route("GET", "/workspaces/:workspace/artifacts/:artifact_id", Op::Folios(FoliosOp::Get), &[]),
1130 route("PATCH", "/workspaces/:workspace/artifacts/:artifact_id", Op::Folios(FoliosOp::Update), &[]),
1131 route("DELETE", "/workspaces/:workspace/artifacts/:artifact_id", Op::Folios(FoliosOp::Trash), &[]),
1132 route("POST", "/workspaces/:workspace/artifacts/:artifact_id/restore", Op::Folios(FoliosOp::Restore), &[]),
1133 route("POST", "/workspaces/:workspace/artifacts/:artifact_id/purge", Op::Folios(FoliosOp::Purge), &[]),
1134 route("GET", "/workspaces/:workspace/artifacts/:artifact_id/content", Op::Folios(FoliosOp::GetContent), &[]),
1135 route("PUT", "/workspaces/:workspace/artifacts/:artifact_id/content", Op::Folios(FoliosOp::Edit), &[]),
1136 route("GET", "/workspaces/:workspace/artifacts/:artifact_id/access", Op::Folios(FoliosOp::GetAccess), &[]),
1137 route("PUT", "/workspaces/:workspace/artifacts/:artifact_id/access", Op::Folios(FoliosOp::SetAccess), &[]),
1138 route("GET", "/workspaces/:workspace/artifacts/:artifact_id/versions", Op::Folios(FoliosOp::ListVersions), &[]),
1139 route("POST", "/workspaces/:workspace/artifacts/:artifact_id/versions/:version_id/restore", Op::Folios(FoliosOp::RestoreVersion), &[]),
1140 route("GET", "/workspaces/:workspace/artifact-templates", Op::Folios(FoliosOp::ListTemplates), &[("kind", "kind")]),
1141 route("GET", "/workspaces/:workspace/artifact-spaces", Op::Folios(FoliosOp::ListSpaces), &[]),
1142 route("POST", "/workspaces/:workspace/datasets/query", Op::Folios(FoliosOp::QueryDataset), &[]),
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1143];
1144
1145impl Route {
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1146 /// Whether the route answers success with `204` and no body, as
1147 /// GitHub's address for the same does. MCP still answers `true`.
1148 pub fn no_content(&self) -> bool {
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r91149 matches!(
1150 self.op,
1151 Op::DeleteComment
1152 | Op::Customize(CustomizeOp::RemoveMarketplace | CustomizeOp::UninstallPlugin | CustomizeOp::RemoveConnector)
1153 )
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1154 }
1155
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1156 /// The names of the route's path parameters, in order.
1157 pub fn params(&self) -> impl Iterator<Item = &'static str> {
1158 self.path
1159 .split('/')
1160 .filter_map(|segment| segment.strip_prefix(':'))
1161 }
1162
1163 /// The values of the path parameters, if `path` is this route's.
1164 fn matches<'a>(&self, path: &'a str) -> Option<Vec<(&'static str, &'a str)>> {
1165 let mut values = Vec::new();
1166 let mut actual = path.trim_end_matches('/').split('/');
1167 for expected in self.path.split('/') {
1168 let segment = actual.next()?;
1169 match expected.strip_prefix(':') {
1170 Some(name) if !segment.is_empty() => values.push((name, segment)),
1171 Some(_) => return None,
1172 None if expected == segment => {}
1173 None => return None,
1174 }
1175 }
1176 actual.next().is_none().then_some(values)
1177 }
1178}
1179
1180/// A path segment with its `%XX` escapes decoded; as given when that is not
1181/// UTF-8.
1182fn percent_decoded(segment: &str) -> String {
1183 let bytes = segment.as_bytes();
1184 let mut out = Vec::with_capacity(bytes.len());
1185 let mut i = 0;
1186 while i < bytes.len() {
1187 let escaped = (bytes[i] == b'%')
1188 .then(|| segment.get(i + 1..i + 3))
1189 .flatten()
1190 .filter(|hex| hex.bytes().all(|byte| byte.is_ascii_hexdigit()))
1191 .and_then(|hex| u8::from_str_radix(hex, 16).ok());
1192 match escaped {
1193 Some(byte) => {
1194 out.push(byte);
1195 i += 3;
1196 }
1197 None => {
1198 out.push(bytes[i]);
1199 i += 1;
1200 }
1201 }
1202 }
1203 String::from_utf8(out).unwrap_or_else(|_| segment.to_owned())
1204}
1205
1206/// The route for a request, and the operation input it describes.
1207///
1208/// The input is the JSON body, overlaid with the query parameters the route
1209/// 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 bar1210/// `repo`, as does a team's `repo` with its `workspace`, and `number`
1211/// becomes an integer.
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1212pub fn resolve(
1213 method: &str,
1214 path: &str,
1215 query: &[(String, String)],
1216 body: Value,
1217) -> Option<(&'static Route, Value)> {
1218 let (route, params) = ROUTES
1219 .iter()
1220 .filter(|route| route.method == method)
1221 .find_map(|route| Some((route, route.matches(path)?)))?;
1222
1223 let mut input = match body {
1224 Value::Object(fields) => fields,
1225 _ => Map::new(),
1226 };
1227 for (name, key) in route.query {
1228 if let Some((_, value)) = query.iter().find(|(query_name, _)| query_name == name) {
1229 input.insert((*key).to_owned(), Value::String(value.clone()));
1230 }
1231 }
1232 let param = |wanted: &str| {
1233 params
1234 .iter()
1235 .find(|(name, _)| *name == wanted)
1236 .map(|(_, value)| *value)
1237 };
1238 if let (Some(owner), Some(name)) = (param("owner"), param("name")) {
1239 input.insert("repo".to_owned(), Value::String(format!("{owner}/{name}")));
1240 }
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb971241 // Every other name the path gives, under that name; the ones below that
1242 // need more (a repository, a number, an encoded name) are set after.
1243 for (key, value) in &params {
1244 if !matches!(*key, "owner" | "name") {
1245 input.insert((*key).to_owned(), Value::String(percent_decoded(value)));
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1246 }
1247 }
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1248 // A repository of a team's workspace, named by itself.
1249 if let (Some(workspace), Some(name)) = (param("workspace"), param("repo")) {
1250 input.insert("repo".to_owned(), Value::String(format!("{workspace}/{name}")));
1251 }
1252 // A branch name may hold slashes, sent URL-encoded as one segment, and
1253 // a label's name spaces.
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1254 if let Some(branch) = param("branch") {
1255 input.insert("branch".to_owned(), Value::String(percent_decoded(branch)));
1256 }
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb971257 // An environment's name may hold slashes and spaces, URL-encoded.
1258 if let Some(environment) = param("environment") {
1259 input.insert("environment".to_owned(), Value::String(percent_decoded(environment)));
1260 }
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1261 if let Some(label) = param("label") {
1262 input.insert("label".to_owned(), Value::String(percent_decoded(label)));
1263 }
1264 if let Some(milestone) = param("milestone") {
1265 // Not a number: zero, which no milestone has.
1266 input.insert("milestone".to_owned(), milestone.parse::<u32>().unwrap_or(0).into());
1267 }
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1268 // GitHub says some things with the path alone.
1269 if route.path.ends_with("/enable") || route.path.ends_with("/disable") {
1270 input.insert("enabled".to_owned(), Value::Bool(route.path.ends_with("/enable")));
1271 }
1272 if route.path.ends_with("/rerun-failed-jobs") {
1273 input.insert("failed_only".to_owned(), Value::Bool(true));
1274 }
Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009)1275 if route.path.ends_with("/force-cancel") {
1276 input.insert("force".to_owned(), Value::Bool(true));
1277 }
API: notifications over REST and MCP, with notifications scopes1278 // Unsaving and waking a thread are a DELETE of what PUT made.
1279 if route.method == "DELETE" && route.path.ends_with("/saved") {
1280 input.insert("saved".to_owned(), Value::Bool(false));
1281 }
1282 if route.method == "DELETE" && route.path.ends_with("/snooze") {
1283 input.remove("until");
1284 }
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1285 if let Some(number) = param("number") {
1286 // Not a number: zero, which no issue or pull request has.
1287 input.insert(
1288 "number".to_owned(),
1289 number.parse::<u32>().unwrap_or(0).into(),
1290 );
1291 }
1292 Some((route, Value::Object(input)))
1293}
1294
1295#[cfg(test)]
1296mod tests {
1297 use serde_json::json;
1298
1299 use super::*;
1300
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb971301 /// Every name a route's path gives reaches the operation: a name left
1302 /// off the lists above is dropped, and the operation answers that it
1303 /// was not given (the project routes were, until this test).
1304 #[test]
1305 fn every_path_parameter_reaches_the_input() {
1306 for route in ROUTES.iter() {
1307 let names: Vec<&str> = route.path.split('/').filter_map(|part| part.strip_prefix(':')).collect();
1308 if names.is_empty() {
1309 continue;
1310 }
1311 let path: String = route
1312 .path
1313 .split('/')
1314 .map(|part| match part.strip_prefix(':') {
1315 Some("number" | "milestone") => "7".to_owned(),
1316 Some(name) => format!("{name}-x"),
1317 None => part.to_owned(),
1318 })
1319 .collect::<Vec<_>>()
1320 .join("/");
1321 let (found, input) = resolve(route.method, &path, &[], json!({})).unwrap();
1322 // A path two routes could take is checked under the first.
1323 if found.path != route.path {
1324 continue;
1325 }
1326 for name in names {
1327 let key = match name {
1328 "owner" | "name" => "repo",
1329 "repo" if names_has_workspace(route.path) => "repo",
1330 other => other,
1331 };
1332 assert!(
1333 input.get(key).is_some_and(|value| !value.is_null()),
1334 "{} {}: :{name} does not reach the input",
1335 route.method,
1336 route.path
1337 );
1338 }
1339 }
1340 }
1341
1342 fn names_has_workspace(path: &str) -> bool {
1343 path.split('/').any(|part| part == ":workspace")
1344 }
1345
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1346 #[test]
1347 fn a_path_resolves_to_its_operation_and_input() {
1348 let (route, input) = resolve(
1349 "POST",
1350 "/repos/flagon-io/hello/pulls/14/merge",
1351 &[],
1352 json!({ "keep_issue_open": true, "number": 99, "repo": "someone/else" }),
1353 )
1354 .unwrap();
1355 assert_eq!(route.op, Op::MergePullRequest);
1356 // What the path names wins over the body.
1357 assert_eq!(
1358 input,
1359 json!({ "keep_issue_open": true, "number": 14, "repo": "flagon-io/hello" })
1360 );
1361 }
1362
1363 #[test]
1364 fn a_branch_with_slashes_is_one_encoded_segment() {
1365 let (route, input) = resolve(
1366 "POST",
1367 "/repos/flagon-io/hello/branches/feature%2Flogin/rename",
1368 &[],
1369 json!({ "new_name": "feature/sign-in" }),
1370 )
1371 .unwrap();
1372 assert_eq!(route.op, Op::RenameBranch);
1373 assert_eq!(
1374 input,
1375 json!({ "new_name": "feature/sign-in", "branch": "feature/login", "repo": "flagon-io/hello" })
1376 );
1377 assert_eq!(percent_decoded("100%"), "100%");
1378 assert_eq!(percent_decoded("a%2bb%zz"), "a+b%zz");
1379 }
1380
1381 #[test]
1382 fn a_collaborator_is_named_by_username() {
1383 let (route, input) = resolve(
1384 "PATCH",
1385 "/repos/flagon-io/hello/collaborators/ada",
1386 &[],
1387 json!({ "role": "maintain" }),
1388 )
1389 .unwrap();
1390 assert_eq!(route.op, Op::UpdateCollaborator);
1391 assert_eq!(input, json!({ "role": "maintain", "username": "ada", "repo": "flagon-io/hello" }));
1392 let (route, input) = resolve("DELETE", "/user/repository_invitations/rin_1", &[], Value::Null).unwrap();
1393 assert_eq!(route.op, Op::DeclineRepoInvitation);
1394 assert_eq!(input, json!({ "id": "rin_1" }));
1395 }
1396
1397 #[test]
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1398 fn teams_are_addressed_by_workspace_and_slug() {
1399 let query = [("q".to_owned(), "back".to_owned())];
1400 let (route, input) = resolve("GET", "/workspaces/acme/teams", &query, Value::Null).unwrap();
1401 assert_eq!(route.op, Op::ListTeams);
1402 assert_eq!(input, json!({ "query": "back", "workspace": "acme" }));
1403 let (route, input) = resolve("PATCH", "/workspaces/acme/teams/backend", &[], json!({ "name": "Back end" })).unwrap();
1404 assert_eq!(route.op, Op::UpdateTeam);
1405 assert_eq!(input, json!({ "name": "Back end", "workspace": "acme", "team": "backend" }));
1406 let (route, input) =
1407 resolve("PUT", "/workspaces/acme/teams/backend/members/ana", &[], json!({ "role": "maintainer" })).unwrap();
1408 assert_eq!(route.op, Op::SetTeamMember);
1409 assert_eq!(input, json!({ "role": "maintainer", "workspace": "acme", "team": "backend", "username": "ana" }));
1410 let query = [("include_child_teams".to_owned(), "true".to_owned())];
1411 let (route, input) = resolve("GET", "/workspaces/acme/teams/backend/members", &query, Value::Null).unwrap();
1412 assert_eq!(route.op, Op::ListTeamMembers);
1413 assert_eq!(input, json!({ "include_child_teams": "true", "workspace": "acme", "team": "backend" }));
1414 // A repository is named by itself, in the team's workspace.
1415 let (route, input) =
1416 resolve("PUT", "/workspaces/acme/teams/backend/repos/rocket", &[], json!({ "role": "write" })).unwrap();
1417 assert_eq!(route.op, Op::SetTeamRepo);
1418 assert_eq!(input, json!({ "role": "write", "workspace": "acme", "team": "backend", "repo": "acme/rocket" }));
1419 let op = |method: &str, path: &str| resolve(method, path, &[], Value::Null).unwrap().0.op;
1420 assert_eq!(op("DELETE", "/workspaces/acme/teams/backend/repos/rocket"), Op::RemoveTeamRepo);
1421 assert_eq!(op("GET", "/workspaces/acme/teams/backend/teams"), Op::ListChildTeams);
1422 assert_eq!(op("GET", "/workspaces/acme/teams/backend/repos"), Op::ListTeamRepos);
1423 assert_eq!(op("PUT", "/workspaces/acme/teams/backend/review_assignment"), Op::SetTeamReviewAssignment);
1424 assert_eq!(op("DELETE", "/workspaces/acme/teams/backend"), Op::DeleteTeam);
1425 assert_eq!(op("POST", "/workspaces/acme/teams"), Op::CreateTeam);
1426 assert_eq!(op("GET", "/workspaces/acme/teams/backend"), Op::GetTeam);
1427 assert_eq!(op("DELETE", "/workspaces/acme/teams/backend/members/ana"), Op::RemoveTeamMember);
1428 let (route, input) = resolve("GET", "/workspaces/acme/members/ana/teams", &[], Value::Null).unwrap();
1429 assert_eq!(route.op, Op::ListUserTeams);
1430 assert_eq!(input, json!({ "workspace": "acme", "username": "ana" }));
1431 }
1432
1433 #[test]
1434 fn reviewers_are_requested_and_code_owners_checked_on_a_repository() {
1435 let body = json!({ "reviewers": ["ana"], "team_reviewers": ["backend"] });
1436 let (route, input) = resolve("POST", "/repos/acme/rocket/pulls/7/requested_reviewers", &[], body.clone()).unwrap();
1437 assert_eq!(route.op, Op::RequestReviewers);
1438 assert_eq!(
1439 input,
1440 json!({ "reviewers": ["ana"], "team_reviewers": ["backend"], "repo": "acme/rocket", "number": 7 })
1441 );
1442 let (route, _) = resolve("DELETE", "/repos/acme/rocket/pulls/7/requested_reviewers", &[], body).unwrap();
1443 assert_eq!(route.op, Op::RemoveRequestedReviewers);
1444 let query = [("ref".to_owned(), "main".to_owned())];
1445 let (route, input) = resolve("GET", "/repos/acme/rocket/codeowners/errors", &query, Value::Null).unwrap();
1446 assert_eq!(route.op, Op::GetCodeownersErrors);
1447 assert_eq!(input, json!({ "ref": "main", "repo": "acme/rocket" }));
1448 }
1449
1450 #[test]
API: notifications over REST and MCP, with notifications scopes1451 fn notifications_are_addressed_as_threads_and_by_issue() {
1452 let (route, input) = resolve("DELETE", "/notifications/threads/ntf_1", &[], Value::Null).unwrap();
1453 assert_eq!(route.op, Op::MarkThreadDone);
1454 assert_eq!(input, json!({ "id": "ntf_1" }));
1455 let (route, input) = resolve("DELETE", "/notifications/threads/ntf_1/saved", &[], Value::Null).unwrap();
1456 assert_eq!(route.op, Op::SaveThread);
1457 assert_eq!(input, json!({ "id": "ntf_1", "saved": false }));
1458 let (route, input) = resolve("DELETE", "/notifications/threads/ntf_1/snooze", &[], json!({ "until": "x" })).unwrap();
1459 assert_eq!(route.op, Op::SnoozeThread);
1460 assert_eq!(input, json!({ "id": "ntf_1" }));
1461 let query = [("all".to_owned(), "true".to_owned()), ("per_page".to_owned(), "50".to_owned())];
1462 let (route, input) = resolve("GET", "/repos/acme/rocket/notifications", &query, Value::Null).unwrap();
1463 assert_eq!(route.op, Op::ListNotifications);
1464 assert_eq!(input, json!({ "all": "true", "per_page": "50", "repo": "acme/rocket" }));
1465 let (route, input) = resolve("PUT", "/repos/acme/rocket/issues/7/subscription", &[], json!({ "ignored": true })).unwrap();
1466 assert_eq!(route.op, Op::SetThreadSubscription);
1467 assert_eq!(input, json!({ "ignored": true, "number": 7, "repo": "acme/rocket" }));
1468 assert_eq!(resolve("GET", "/user/subscriptions", &[], Value::Null).unwrap().0.op, Op::ListWatchedRepos);
1469 }
1470
1471 #[test]
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1472 fn labels_and_milestones_are_named_in_the_path() {
1473 let (route, input) = resolve("PATCH", "/repos/acme/web/labels/good%20first%20issue", &[], json!({ "color": "7057ff" })).unwrap();
1474 assert_eq!(route.op, Op::UpdateLabel);
1475 assert_eq!(input, json!({ "color": "7057ff", "label": "good first issue", "repo": "acme/web" }));
1476 let (route, input) = resolve("DELETE", "/repos/acme/web/issues/7/labels/bug", &[], Value::Null).unwrap();
1477 assert_eq!(route.op, Op::RemoveIssueLabels);
1478 assert_eq!(input, json!({ "label": "bug", "number": 7, "repo": "acme/web" }));
1479 let (route, input) = resolve("DELETE", "/repos/acme/web/issues/7/labels", &[], Value::Null).unwrap();
1480 assert_eq!(route.op, Op::RemoveIssueLabels);
1481 assert_eq!(input, json!({ "number": 7, "repo": "acme/web" }));
1482 let (route, _) = resolve("POST", "/repos/acme/web/labels/defaults", &[], Value::Null).unwrap();
1483 assert_eq!(route.op, Op::AddDefaultLabels);
1484 let (route, input) = resolve("PATCH", "/repos/acme/web/milestones/3", &[], json!({ "state": "closed" })).unwrap();
1485 assert_eq!(route.op, Op::UpdateMilestone);
1486 assert_eq!(input, json!({ "state": "closed", "milestone": 3, "repo": "acme/web" }));
1487 let (route, input) = resolve("PATCH", "/repos/acme/web/pulls/9", &[], json!({ "base": "release" })).unwrap();
1488 assert_eq!(route.op, Op::UpdatePullRequest);
1489 assert_eq!(input, json!({ "base": "release", "number": 9, "repo": "acme/web" }));
1490 }
1491
1492 #[test]
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1493 fn pull_requests_reopen_and_turn_draft_and_comments_are_named_by_id() {
1494 let op = |method: &str, path: &str| resolve(method, path, &[], Value::Null).unwrap().0.op;
1495 assert_eq!(op("POST", "/repos/acme/web/pulls/9/reopen"), Op::ReopenPullRequest);
1496 assert_eq!(op("POST", "/repos/acme/web/pulls/9/draft"), Op::ConvertPullRequestToDraft);
1497 assert_eq!(op("POST", "/repos/acme/web/pulls/9/close"), Op::ClosePullRequest);
1498 // Reopening and closing with a PATCH, as `state`.
1499 let (route, input) = resolve("PATCH", "/repos/acme/web/pulls/9", &[], json!({ "state": "open" })).unwrap();
1500 assert_eq!(route.op, Op::UpdatePullRequest);
1501 assert_eq!(input, json!({ "state": "open", "number": 9, "repo": "acme/web" }));
1502 let (route, input) =
1503 resolve("PATCH", "/repos/acme/web/issues/comments/cmt_1", &[], json!({ "body": "Better" })).unwrap();
1504 assert_eq!(route.op, Op::EditComment);
1505 assert_eq!(input, json!({ "body": "Better", "comment_id": "cmt_1", "repo": "acme/web" }));
1506 let (route, input) = resolve("DELETE", "/repos/acme/web/issues/comments/cmt_1", &[], Value::Null).unwrap();
1507 assert_eq!(route.op, Op::DeleteComment);
1508 assert_eq!(input, json!({ "comment_id": "cmt_1", "repo": "acme/web" }));
1509 // Still an issue's comments, labels and subscription.
1510 assert_eq!(op("POST", "/repos/acme/web/issues/7/comments"), Op::AddComment);
1511 assert_eq!(op("DELETE", "/repos/acme/web/issues/7/labels"), Op::RemoveIssueLabels);
1512 assert_eq!(op("DELETE", "/repos/acme/web/issues/7/subscription"), Op::DeleteThreadSubscription);
1513 }
1514
1515 #[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.1516 fn observability_is_addressed_by_workspace_and_project() {
1517 let query = [("level".to_owned(), "error,warn".to_owned()), ("q".to_owned(), "timeout".to_owned())];
1518 let (route, input) = resolve("GET", "/workspaces/acme/projects/web/observability/logs", &query, Value::Null).unwrap();
1519 assert_eq!(route.op, Op::Telemetry(TelemetryOp::ListLogs));
1520 assert_eq!(input, json!({ "level": "error,warn", "q": "timeout", "workspace": "acme", "project": "web" }));
1521 let (route, input) =
1522 resolve("GET", "/workspaces/acme/projects/web/observability/metrics/http.server.request.duration", &[], Value::Null).unwrap();
1523 assert_eq!(route.op, Op::Telemetry(TelemetryOp::GetMetric));
1524 assert_eq!(input["metric"], json!("http.server.request.duration"));
1525 let (route, input) =
1526 resolve("PATCH", "/workspaces/acme/projects/web/observability/errors/f00d", &[], json!({ "state": "resolved" })).unwrap();
1527 assert_eq!(route.op, Op::Telemetry(TelemetryOp::UpdateError));
1528 assert_eq!(input, json!({ "state": "resolved", "workspace": "acme", "project": "web", "fingerprint": "f00d" }));
1529 }
1530
1531 #[test]
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1532 fn billing_is_addressed_by_workspace() {
1533 let query = [("from".to_owned(), "2026-10-01".to_owned()), ("products".to_owned(), "agent,sandboxes".to_owned())];
1534 let (route, input) = resolve("GET", "/workspaces/acme/usage", &query, Value::Null).unwrap();
1535 assert_eq!(route.op, Op::GetUsage);
1536 assert_eq!(input, json!({ "from": "2026-10-01", "products": "agent,sandboxes", "workspace": "acme" }));
1537 let (route, input) = resolve("PUT", "/workspaces/acme/budget", &[], json!({ "alerts": [50] })).unwrap();
1538 assert_eq!(route.op, Op::SetBudget);
1539 assert_eq!(input, json!({ "alerts": [50], "workspace": "acme" }));
1540 let op = |method: &str, path: &str| resolve(method, path, &[], Value::Null).unwrap().0.op;
1541 assert_eq!(op("GET", "/workspaces/acme/budget"), Op::GetBudget);
1542 assert_eq!(op("GET", "/workspaces/acme/ai_credit"), Op::GetAiCredit);
1543 assert_eq!(op("POST", "/workspaces/acme/ai_credit/checkout"), Op::BuyAiCredit);
1544 assert_eq!(op("GET", "/workspaces/acme/invoices"), Op::ListInvoices);
1545 assert_eq!(op("GET", "/workspaces/acme/billing_details"), Op::GetBillingDetails);
Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens1546 assert_eq!(op("GET", "/workspaces/acme/gateway/requests"), Op::ListGatewayRequests);
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1547 }
1548
1549 #[test]
Merge checks: statuses and check runs on every commit1550 fn checks_are_at_githubs_addresses() {
1551 let sha = "a".repeat(40);
1552 let body = json!({ "state": "success", "context": "ci/build" });
1553 let (route, input) = resolve("POST", &format!("/repos/acme/web/statuses/{sha}"), &[], body).unwrap();
1554 assert_eq!(route.op, Op::Checks(ChecksOp::CreateCommitStatus));
1555 assert_eq!(input, json!({ "state": "success", "context": "ci/build", "sha": sha, "repo": "acme/web" }));
1556 let (route, input) = resolve("GET", "/repos/acme/web/commits/release%2F1.x/status", &[], Value::Null).unwrap();
1557 assert_eq!(route.op, Op::Checks(ChecksOp::GetCombinedStatus));
1558 assert_eq!(input, json!({ "ref": "release/1.x", "repo": "acme/web" }));
1559 let query = [("check_name".to_owned(), "lint".to_owned())];
1560 let (route, input) = resolve("GET", "/repos/acme/web/commits/main/check-runs", &query, Value::Null).unwrap();
1561 assert_eq!(route.op, Op::Checks(ChecksOp::ListCheckRunsForRef));
1562 assert_eq!(input, json!({ "check_name": "lint", "ref": "main", "repo": "acme/web" }));
1563 let (route, input) = resolve("PATCH", "/repos/acme/web/check-runs/cr_1", &[], json!({ "conclusion": "success" })).unwrap();
1564 assert_eq!(route.op, Op::Checks(ChecksOp::UpdateCheckRun));
1565 assert_eq!(input, json!({ "conclusion": "success", "id": "cr_1", "repo": "acme/web" }));
1566 let op = |method: &str, path: &str| resolve(method, path, &[], Value::Null).unwrap().0.op;
1567 assert_eq!(op("POST", "/repos/acme/web/check-runs"), Op::Checks(ChecksOp::CreateCheckRun));
1568 assert_eq!(op("GET", "/repos/acme/web/check-runs/cr_1/annotations"), Op::Checks(ChecksOp::ListCheckRunAnnotations));
1569 assert_eq!(op("POST", "/repos/acme/web/check-suites/cs_1/rerequest"), Op::Checks(ChecksOp::RerequestCheckSuite));
1570 assert_eq!(op("GET", "/repos/acme/web/commits/main/check-suites"), Op::Checks(ChecksOp::ListCheckSuitesForRef));
1571 }
1572
1573 #[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.1574 fn archives_are_at_githubs_addresses_and_redirect() {
1575 let sha = "4807077b296e6edbf410d55e72749d3e1170c291";
1576 let (route, input) = resolve("GET", &format!("/repos/acme/web/tarball/{sha}"), &[], Value::Null).unwrap();
1577 assert_eq!(route.op, Op::Archives(ArchiveOp::DownloadTarball));
1578 assert_eq!(input, json!({ "ref": sha, "repo": "acme/web" }));
1579 // A branch with slashes, URL-encoded as octokit sends it.
1580 let (route, input) = resolve("GET", "/repos/acme/web/zipball/release%2F1.x", &[], Value::Null).unwrap();
1581 assert_eq!(route.op, Op::Archives(ArchiveOp::DownloadZipball));
1582 assert_eq!(input, json!({ "ref": "release/1.x", "repo": "acme/web" }));
1583 // Both answer with a redirect to their link, as the artifact download does.
1584 assert!(route.op.redirects());
1585 assert!(Op::Archives(ArchiveOp::DownloadTarball).redirects());
1586 assert!(Op::Artifacts(ArtifactsOp::DownloadArtifact).redirects());
1587 assert!(!Op::GetRepo.redirects());
1588 // Anyone may ask for a public repository's.
1589 assert!(!route.op.needs_user());
1590 }
1591
1592 #[test]
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1593 fn query_parameters_are_renamed() {
1594 let query = [
1595 ("q".to_owned(), "parser".to_owned()),
1596 ("x".to_owned(), "y".to_owned()),
1597 ];
1598 let (route, input) = resolve("GET", "/repos", &query, Value::Null).unwrap();
1599 assert_eq!(route.op, Op::ListRepos);
1600 assert_eq!(input, json!({ "query": "parser" }));
1601 }
1602
1603 #[test]
1604 fn method_and_shape_must_match() {
1605 assert!(resolve("GET", "/repos/a/b/issues/1/close", &[], Value::Null).is_none());
1606 assert!(resolve("GET", "/repos/a", &[], Value::Null).is_none());
1607 assert!(resolve("GET", "/repos/a/b/issues/1/extra", &[], Value::Null).is_none());
1608 assert!(resolve("GET", "/repos/a/b/", &[], Value::Null).is_some());
1609 }
1610
1611 #[test]
1612 fn every_parameter_and_query_name_is_an_input() {
1613 for route in ROUTES {
1614 let properties = route.op.properties();
1615 for (_, key) in route.query {
1616 assert!(properties.contains_key(*key), "{}: {key}", route.path);
1617 }
1618 for name in route.params() {
1619 let covered = matches!(name, "owner" | "name") && properties.contains_key("repo")
1620 || properties.contains_key(name);
1621 assert!(covered, "{}: {name}", route.path);
1622 }
1623 }
1624 }
1625
1626 #[test]
1627 fn every_operation_has_a_route() {
1628 for op in Op::ALL {
1629 assert!(ROUTES.iter().any(|route| route.op == op), "{}", op.name());
1630 }
1631 }
1632}

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