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

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

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