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.
| The statement is a month at a time, a line per kind of charge | 1 | //! The actions service: a repository's GitHub Actions workflows, run on |
| 2 | //! g1t as they are. See `g1t_contracts::actions` for the methods and | |
| 3 | //! `g1t_actions` for how workflows, expressions and filters are read. | |
| 4 | //! | |
| 5 | //! - [`sync`] reads workflow files, at the default branch for the list and | |
| 6 | //! at an event's own commit for its runs. | |
| 7 | //! - [`trigger`] turns events, schedules and manual runs into runs. | |
| 8 | //! - [`plan`] moves a run's jobs along: each waits for the jobs it needs, | |
| 9 | //! is skipped or expanded into its matrix, queued, started in a sandbox | |
| 10 | //! when the workspace has room, and finished by the sandbox's report. | |
| 11 | //! - [`payload`] builds the webhook-shaped `github.event`. | |
| 12 | //! - [`settings`] keeps secrets and variables. | |
| Merge branch 'worktree-agent-a3abfcce648e87dca' | 13 | //! - [`protection`] keeps a repository's workflow settings and its |
| 14 | //! environments' protection rules, holds jobs at those rules until they | |
| 15 | //! pass, and lets pull requests' runs from outside wait for approval. | |
| Fast pages, required checks on the branch, self-hosted runners, honest incidents | 16 | //! - [`cache`] lists `actions/cache` entries, which the API keeps in R2. |
| Merge remote-tracking branch 'origin/claude/peaceful-franklin-7qtkwx' | 17 | //! - [`reach`] finds the actions and reusable workflows of other |
| 18 | //! repositories on g1t, and [`registry`] resolves those from GitHub to | |
| 19 | //! commits and keeps their archives, for jobs to download from g1t. | |
| Fast pages, required checks on the branch, self-hosted runners, honest incidents | 20 | //! - [`runners`] keeps self-hosted runners, hands them jobs (and agent |
| 21 | //! work from the runner service) when they ask, and hears back. | |
| The statement is a month at a time, a line per kind of charge | 22 | //! |
| 23 | //! The service acts as the repository's workspace: it reads what the | |
| Merge branch 'worktree-agent-a3abfcce648e87dca' | 24 | //! workspace can read, and a job's `GITHUB_TOKEN` is a token of the |
| 25 | //! workspace's that reaches the job's repository only, with the scopes its | |
| 26 | //! `permissions:` give it, revoked when the job ends. | |
| The statement is a month at a time, a line per kind of charge | 27 | |
| 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. | 28 | mod run_artifacts; |
| Fast pages, required checks on the branch, self-hosted runners, honest incidents | 29 | mod cache; |
| Merge branch 'claude/cool-newton-7nm8r9' (#36): real GitHub Actions on g1t, managed deploy workflows, g1t/deploy and the Sysbox runner 0.2.0. The merge keeps both sides' webhook events and API operations, says agent images need tar, guards the job engine's stop to Unix so the runner builds on Windows again, and keeps the moves tests' previews from going idle as the calendar passes their fixture's date. | 30 | mod managed; |
| Merge branch 'mirroring' into artifacts-mode | 31 | mod mirrored; |
| The statement is a month at a time, a line per kind of charge | 32 | mod payload; |
| 33 | mod plan; | |
| Merge branch 'worktree-agent-a3abfcce648e87dca' | 34 | mod protection; |
| Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts | 35 | mod reach; |
| Merge remote-tracking branch 'origin/claude/peaceful-franklin-7qtkwx' | 36 | mod registry; |
| The statement is a month at a time, a line per kind of charge | 37 | mod rename; |
| Actions: OIDC tokens, the toolkit's cache and artifact services, and artifacts in R2 | 38 | pub mod runtime; |
| Fast pages, required checks on the branch, self-hosted runners, honest incidents | 39 | mod runners; |
| The statement is a month at a time, a line per kind of charge | 40 | mod settings; |
| 41 | mod sync; | |
| 42 | mod trigger; | |
| 43 | mod views; | |
| 44 | ||
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 45 | use g1t_contracts::access::{self, Capability}; |
| The statement is a month at a time, a line per kind of charge | 46 | use g1t_contracts::events::Event; |
| 47 | use g1t_contracts::identity::{SlugArgs, Workspace}; | |
| 48 | use g1t_contracts::repos::{GetArgs, GetByIdArgs, Repo, RepoPath}; | |
| 49 | use g1t_contracts::{FailureCode, Membership, Outcome, PrincipalKind, User, Viewer}; | |
| 50 | use g1t_kit::{args, reply, rpc_method}; | |
| 51 | use g1t_secrets::Sealer; | |
| 52 | use serde::Deserialize; | |
| 53 | use serde_json::Value; | |
| 54 | use worker::wasm_bindgen::JsValue; | |
| 55 | use worker::{Context, D1Database, Env, Fetcher, MessageBatch, MessageExt, Request, Response, Result, ScheduleContext, ScheduledEvent, event}; | |
| 56 | ||
| 57 | /// The most workflow files read from a repository. | |
| 58 | pub const MAX_WORKFLOWS: usize = 50; | |
| 59 | /// Jobs one workspace may have running at once; the rest wait their turn. | |
| 60 | pub const RUNNING_PER_WORKSPACE: u32 = 4; | |
| Merge branch 'claude/cool-newton-7nm8r9' (#36): real GitHub Actions on g1t, managed deploy workflows, g1t/deploy and the Sysbox runner 0.2.0. The merge keeps both sides' webhook events and API operations, says agent images need tar, guards the job engine's stop to Unix so the runner builds on Windows again, and keeps the moves tests' previews from going idle as the calendar passes their fixture's date. | 61 | /// The longest a job on g1t's own machines may run, whatever its |
| 62 | /// `timeout-minutes`. | |
| 63 | pub const MAX_TIMEOUT_MINUTES: u32 = g1t_actions::workflow::HOSTED_MAX_TIMEOUT_MINUTES; | |
| Fast pages, required checks on the branch, self-hosted runners, honest incidents | 64 | /// The longest a job on a self-hosted runner may run: the machine is the |
| 65 | /// workspace's own, and its time costs nothing. | |
| 66 | pub const SELF_HOSTED_MAX_TIMEOUT_MINUTES: u32 = 24 * 60; | |
| The statement is a month at a time, a line per kind of charge | 67 | /// A running job that has said nothing for this long is taken as lost. |
| 68 | pub const SILENT_MS: u64 = 10 * 60 * 1000; | |
| Merge branch 'worktree-agent-ab9543c492a7ed481' into spend-guardrails | 69 | /// Schedules pause in a repository that has had no push for this long; the |
| 70 | /// next push resumes them. | |
| 71 | pub const SCHEDULE_IDLE_MS: u64 = 60 * 24 * 60 * 60 * 1000; | |
| 72 | /// How long a workflow's schedule waits after billing refused to start a | |
| 73 | /// job of one of its scheduled runs, before it tries again. | |
| 74 | pub const SCHEDULE_REFUSED_MS: u64 = 60 * 60 * 1000; | |
| The statement is a month at a time, a line per kind of charge | 75 | pub const SITE: &str = "https://g1t.sh"; |
| 76 | pub const API: &str = "https://api.g1t.sh"; | |
| 77 | ||
| 78 | #[derive(Deserialize)] | |
| 79 | struct Count { | |
| 80 | n: u32, | |
| 81 | } | |
| 82 | ||
| 83 | pub fn optional(value: Option<&str>) -> JsValue { | |
| 84 | value.map_or(JsValue::NULL, JsValue::from) | |
| 85 | } | |
| 86 | ||
| 87 | pub fn fail<T>(code: FailureCode, message: impl Into<String>) -> Outcome<T> { | |
| 88 | Outcome::fail(code, message) | |
| 89 | } | |
| 90 | ||
| 91 | /// `owner/name` as a path. | |
| 92 | pub fn repo_path(full_name: &str) -> RepoPath { | |
| 93 | let (namespace, name) = full_name.split_once('/').unwrap_or((full_name, "")); | |
| 94 | RepoPath { | |
| 95 | namespace: namespace.to_owned(), | |
| 96 | name: name.to_owned(), | |
| 97 | } | |
| 98 | } | |
| 99 | ||
| 100 | pub struct Actions { | |
| 101 | db: D1Database, | |
| 102 | repos: Fetcher, | |
| 103 | work: Fetcher, | |
| 104 | identity: Fetcher, | |
| 105 | runner: Fetcher, | |
| 106 | events: Fetcher, | |
| 107 | /// Projects: a repository's secrets and variables belong to its project. | |
| 108 | projects: Fetcher, | |
| Fast pages, required checks on the branch, self-hosted runners, honest incidents | 109 | /// Billing: self-hosted runners' time, recorded at $0, and the |
| 110 | /// cache's storage. | |
| 111 | billing: Fetcher, | |
| Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97 | 112 | /// Deployments: a job with an `environment:` deploys to it, and its |
| 113 | /// run's deployment is recorded there (plan.rs `report_deployment`). | |
| 114 | deployments: Fetcher, | |
| Fast pages, required checks on the branch, self-hosted runners, honest incidents | 115 | /// Where the API keeps cache entries, for deleting evicted ones. |
| 116 | cache: Option<worker::Bucket>, | |
| The statement is a month at a time, a line per kind of charge | 117 | /// Seals secrets; absent until `ACTIONS_KEY` is set, when secrets |
| 118 | /// cannot be saved. | |
| 119 | sealer: Option<Sealer>, | |
| Actions: OIDC tokens, the toolkit's cache and artifact services, and artifacts in R2 | 120 | /// Signs the toolkit's blob links (runtime.rs), from `ACTIONS_KEY`. |
| 121 | blob_key: Option<String>, | |
| The statement is a month at a time, a line per kind of charge | 122 | } |
| 123 | ||
| 124 | impl Actions { | |
| 125 | fn new(env: &Env) -> Result<Self> { | |
| 126 | Ok(Actions { | |
| 127 | db: env.d1("DB")?, | |
| 128 | repos: env.service("REPOS")?, | |
| 129 | work: env.service("WORK")?, | |
| 130 | identity: env.service("IDENTITY")?, | |
| 131 | runner: env.service("RUNNER")?, | |
| 132 | events: env.service("EVENTS")?, | |
| 133 | projects: env.service("PROJECTS")?, | |
| Fast pages, required checks on the branch, self-hosted runners, honest incidents | 134 | billing: env.service("BILLING")?, |
| Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97 | 135 | deployments: env.service("DEPLOYMENTS")?, |
| Fast pages, required checks on the branch, self-hosted runners, honest incidents | 136 | cache: env.bucket("ACTIONS_CACHE").ok(), |
| The statement is a month at a time, a line per kind of charge | 137 | sealer: env.secret("ACTIONS_KEY").ok().and_then(|key| Sealer::new(&key.to_string())), |
| Actions: OIDC tokens, the toolkit's cache and artifact services, and artifacts in R2 | 138 | blob_key: env.secret("ACTIONS_KEY").ok().map(|key| runtime::blob_key(&key.to_string())), |
| The statement is a month at a time, a line per kind of charge | 139 | }) |
| 140 | } | |
| 141 | ||
| 142 | /// The workspace itself, as the service acts. | |
| 143 | async fn workspace_actor(&self, slug: &str) -> Result<Option<User>> { | |
| 144 | let workspace: Option<Workspace> = g1t_kit::call(&self.identity, "get_workspace", &SlugArgs { slug: slug.to_owned() }).await?; | |
| 145 | Ok(workspace.map(|workspace| User { | |
| 146 | id: workspace.id, | |
| 147 | username: workspace.slug.clone(), | |
| 148 | kind: PrincipalKind::Workspace, | |
| 149 | verified: true, | |
| 150 | workspaces: vec![Membership::member(workspace.slug)], | |
| 151 | ..User::default() | |
| 152 | })) | |
| 153 | } | |
| 154 | ||
| 155 | /// The repository, if the viewer may see it and it is not a pull | |
| 156 | /// request's working copy. | |
| 157 | async fn visible_repo(&self, path: &RepoPath, viewer: &Viewer) -> Result<Option<Repo>> { | |
| 158 | let found: Outcome<Repo> = g1t_kit::call( | |
| 159 | &self.repos, | |
| 160 | "get", | |
| 161 | &GetArgs { | |
| 162 | path: path.clone(), | |
| 163 | viewer: viewer.clone(), | |
| 164 | }, | |
| 165 | ) | |
| 166 | .await?; | |
| 167 | Ok(found.into_result().ok().filter(|repo| repo.fork_of.is_none())) | |
| 168 | } | |
| 169 | ||
| 170 | /// A repository by id, as its workspace sees it. | |
| 171 | async fn repo_by_id(&self, id: &str) -> Result<Option<(Repo, User)>> { | |
| 172 | let path: Option<RepoPath> = g1t_kit::call(&self.repos, "path_by_id", &g1t_contracts::repos::PathByIdArgs { id: id.to_owned() }).await?; | |
| 173 | let Some(path) = path else { return Ok(None) }; | |
| 174 | let Some(actor) = self.workspace_actor(&path.namespace).await? else { | |
| 175 | return Ok(None); | |
| 176 | }; | |
| 177 | let found: Outcome<Repo> = g1t_kit::call( | |
| 178 | &self.repos, | |
| 179 | "get_by_id", | |
| 180 | &GetByIdArgs { | |
| 181 | id: id.to_owned(), | |
| 182 | viewer: Some(actor.clone()), | |
| 183 | }, | |
| 184 | ) | |
| 185 | .await?; | |
| 186 | Ok(found.into_result().ok().filter(|repo| repo.fork_of.is_none()).map(|repo| (repo, actor))) | |
| 187 | } | |
| 188 | ||
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 189 | /// The repository at `path`, when `actor` may do `capability` in it: |
| 190 | /// not found when they cannot read it, forbidden when their role falls | |
| 191 | /// short. Agents never may: people and tokens run and change workflows. | |
| 192 | async fn may(&self, actor: &User, path: &RepoPath, capability: Capability) -> Result<Outcome<Repo>> { | |
| 193 | if actor.kind == PrincipalKind::Agent { | |
| 194 | return Ok(fail(FailureCode::Forbidden, "An agent cannot do that. Ask a person.")); | |
| 195 | } | |
| 196 | let Some(repo) = self.visible_repo(path, &Some(actor.clone())).await? else { | |
| 197 | return Ok(fail(FailureCode::NotFound, "There is no such repository.")); | |
| 198 | }; | |
| 199 | if !access::can(Some(actor), &repo, capability) { | |
| 200 | return Ok(fail( | |
| 201 | FailureCode::Forbidden, | |
| 202 | access::needs(capability, &format!("{}/{}", repo.namespace, repo.name)), | |
| 203 | )); | |
| 204 | } | |
| 205 | Ok(Outcome::Ok(repo)) | |
| The statement is a month at a time, a line per kind of charge | 206 | } |
| 207 | } | |
| 208 | ||
| 209 | /// Unwraps an `Outcome`, or returns its failure from the enclosing method. | |
| 210 | #[macro_export] | |
| 211 | macro_rules! check { | |
| 212 | ($outcome:expr) => { | |
| 213 | match $outcome { | |
| 214 | g1t_contracts::Outcome::Ok(value) => value, | |
| 215 | g1t_contracts::Outcome::Fail(refused) => return Ok(g1t_contracts::Outcome::Fail(refused)), | |
| 216 | } | |
| 217 | }; | |
| 218 | } | |
| 219 | ||
| 220 | #[event(fetch)] | |
| 221 | async fn fetch(mut request: Request, env: Env, _ctx: Context) -> Result<Response> { | |
| 222 | let Some(method) = rpc_method(&request) else { | |
| 223 | return Response::error("Not found", 404); | |
| 224 | }; | |
| 225 | let body: Value = request.json().await?; | |
| 226 | let service = Actions::new(&env)?; | |
| 227 | match method.as_str() { | |
| 228 | "workflows" => reply(&service.workflows(args(body)?).await?), | |
| 229 | "runs" => reply(&service.runs(args(body)?).await?), | |
| 230 | "run" => reply(&service.run(args(body)?).await?), | |
| 231 | "logs" => reply(&service.logs(args(body)?).await?), | |
| Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009) | 232 | "job_log_text" => reply(&service.job_log_text(args(body)?).await?), |
| 233 | "run_logs" => reply(&service.run_logs(args(body)?).await?), | |
| 234 | "summaries" => reply(&service.summaries(args(body)?).await?), | |
| The statement is a month at a time, a line per kind of charge | 235 | "dispatch" => reply(&service.dispatch(args(body)?).await?), |
| Merge branch 'worktree-agent-a3abfcce648e87dca' | 236 | "repository_dispatch" => reply(&service.repository_dispatch(args(body)?).await?), |
| 237 | "approve_run" => reply(&service.approve_run(args(body)?).await?), | |
| 238 | "pending_deployments" => reply(&service.pending_deployments(args(body)?).await?), | |
| 239 | "review_deployments" => reply(&service.review_deployments(args(body)?).await?), | |
| 240 | "actions_settings" => reply(&service.actions_settings(args(body)?).await?), | |
| 241 | "set_actions_settings" => reply(&service.set_actions_settings(args(body)?).await?), | |
| 242 | "workspace_actions_settings" => reply(&service.workspace_actions_settings(args(body)?).await?), | |
| 243 | "set_workspace_actions_settings" => reply(&service.set_workspace_actions_settings(args(body)?).await?), | |
| 244 | "environments" => reply(&service.environments(args(body)?).await?), | |
| 245 | "set_environment" => reply(&service.set_environment(args(body)?).await?), | |
| 246 | "delete_environment" => reply(&service.delete_environment(args(body)?).await?), | |
| The statement is a month at a time, a line per kind of charge | 247 | "merge_group" => reply(&service.merge_group(args(body)?).await?), |
| 248 | "cancel" => reply(&service.cancel(args(body)?).await?), | |
| 249 | "rerun" => reply(&service.rerun(args(body)?).await?), | |
| 250 | "set_workflow_enabled" => reply(&service.set_workflow_enabled(args(body)?).await?), | |
| 251 | "settings" => reply(&service.settings(args(body)?).await?), | |
| 252 | "set_setting" => reply(&service.set_setting(args(body)?).await?), | |
| 253 | "delete_setting" => reply(&service.delete_setting(args(body)?).await?), | |
| 254 | "resolve_settings" => reply(&service.resolve_settings(args(body)?).await?), | |
| Merge branch 'claude/cool-newton-7nm8r9' (#36): real GitHub Actions on g1t, managed deploy workflows, g1t/deploy and the Sysbox runner 0.2.0. The merge keeps both sides' webhook events and API operations, says agent images need tar, guards the job engine's stop to Unix so the runner builds on Windows again, and keeps the moves tests' previews from going idle as the calendar passes their fixture's date. | 255 | // Managed workflows: g1t's services' side only. |
| 256 | "set_managed_workflow" => reply(&service.set_managed_workflow(args(body)?).await?), | |
| 257 | "remove_managed_workflow" => reply(&service.remove_managed_workflow(args(body)?).await?), | |
| 258 | "set_managed_deploy" => reply(&service.set_managed_deploy(args(body)?).await?), | |
| The statement is a month at a time, a line per kind of charge | 259 | "job_spec" => reply(&service.job_spec(args(body)?).await?), |
| 260 | "job_auth" => reply(&service.job_auth(args(body)?).await?), | |
| Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts | 261 | "job_action" => reply(&service.job_action(args(body)?).await?), |
| Merge remote-tracking branch 'origin/claude/peaceful-franklin-7qtkwx' | 262 | "job_action_archive" => reply(&service.job_action_archive(args(body)?).await?), |
| Merge checks: statuses and check runs on every commit | 263 | "check_runs" => reply(&service.check_runs(args(body)?).await?), |
| The statement is a month at a time, a line per kind of charge | 264 | "job_report" => reply(&service.job_report(args(body)?).await?), |
| Fast pages, required checks on the branch, self-hosted runners, honest incidents | 265 | // actions/cache, through the API with the job's token. |
| 266 | "cache_lookup" => reply(&service.cache_lookup(args(body)?).await?), | |
| 267 | "cache_reserve" => reply(&service.cache_reserve(args(body)?).await?), | |
| 268 | "cache_commit" => reply(&service.cache_commit(args(body)?).await?), | |
| 269 | "cache_abort" => reply(&service.cache_abort(args(body)?).await?), | |
| Actions: OIDC tokens, the toolkit's cache and artifact services, and artifacts in R2 | 270 | "cache_upload" => reply(&service.cache_upload(args(body)?).await?), |
| 271 | // Artifacts: a job's side, with its token or its runtime token. | |
| 272 | "artifact_reserve" => reply(&service.artifact_reserve(args(body)?).await?), | |
| 273 | "artifact_commit" => reply(&service.artifact_commit(args(body)?).await?), | |
| 274 | "artifact_abort" => reply(&service.artifact_abort(args(body)?).await?), | |
| 275 | "job_artifacts" => reply(&service.job_artifacts(args(body)?).await?), | |
| 276 | "job_artifact" => reply(&service.job_artifact(args(body)?).await?), | |
| 277 | "job_delete_artifact" => reply(&service.job_delete_artifact(args(body)?).await?), | |
| 278 | // Artifacts: people's side. | |
| 279 | "artifacts" => reply(&service.artifacts(args(body)?).await?), | |
| 280 | "artifact" => reply(&service.artifact(args(body)?).await?), | |
| 281 | "artifact_download" => reply(&service.artifact_download(args(body)?).await?), | |
| 282 | "delete_artifact" => reply(&service.delete_artifact(args(body)?).await?), | |
| 283 | "artifact_retention" => reply(&service.artifact_retention(args(body)?).await?), | |
| 284 | // The toolkit's protocols: the runtime token, OIDC, blob links. | |
| 285 | "runtime_auth" => reply(&service.runtime_auth(args(body)?).await?), | |
| 286 | "oidc_claims" => reply(&service.oidc_claims(args(body)?).await?), | |
| 287 | "blob_sign" => reply(&service.blob_sign(args(body)?).await?), | |
| 288 | "blob_open" => reply(&service.blob_open(args(body)?).await?), | |
| 289 | "blob_part" => reply(&service.blob_part(args(body)?).await?), | |
| 290 | "blob_parts" => reply(&service.blob_parts(args(body)?).await?), | |
| 291 | "blob_done" => reply(&service.blob_done(args(body)?).await?), | |
| Fast pages, required checks on the branch, self-hosted runners, honest incidents | 292 | // Self-hosted runners: people's side. |
| 293 | "runners" => reply(&service.runners(args(body)?).await?), | |
| 294 | "create_registration_token" => reply(&service.create_registration_token(args(body)?).await?), | |
| 295 | "remove_runner" => reply(&service.remove_runner(args(body)?).await?), | |
| 296 | "runner_groups" => reply(&service.runner_groups(args(body)?).await?), | |
| 297 | "set_runner_group" => reply(&service.set_runner_group(args(body)?).await?), | |
| 298 | "delete_runner_group" => reply(&service.delete_runner_group(args(body)?).await?), | |
| 299 | "runner_settings" => reply(&service.runner_settings(args(body)?).await?), | |
| 300 | "set_runner_settings" => reply(&service.set_runner_settings(args(body)?).await?), | |
| 301 | // The runner's own side, through the API with its credential. | |
| 302 | "runner_register" => reply(&service.runner_register(args(body)?).await?), | |
| 303 | "runner_poll" => reply(&service.runner_poll(args(body)?).await?), | |
| 304 | "runner_finished" => reply(&service.runner_finished(args(body)?).await?), | |
| 305 | "runner_remove_self" => reply(&service.runner_remove_self(args(body)?).await?), | |
| 306 | // Agent work, from the runner service. | |
| 307 | "runner_route" => reply(&service.runner_route(args(body)?).await?), | |
| 308 | "stuck_jobs" => reply(&service.stuck_jobs(args(body)?).await?), | |
| The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how. | 309 | "runner_activity" => reply(&service.runner_activity(args(body)?).await?), |
| Fast pages, required checks on the branch, self-hosted runners, honest incidents | 310 | "enqueue_task" => reply(&service.enqueue_task(args(body)?).await?), |
| 311 | "cancel_task" => reply(&service.cancel_task(args(body)?).await?), | |
| The statement is a month at a time, a line per kind of charge | 312 | _ => Response::error("Unknown method", 404), |
| 313 | } | |
| 314 | } | |
| 315 | ||
| 316 | /// Events from the bus, on this service's own queue. | |
| 317 | #[event(queue)] | |
| 318 | async fn queue(batch: MessageBatch<Event>, env: Env, _ctx: Context) -> Result<()> { | |
| 319 | let service = Actions::new(&env)?; | |
| 320 | for message in batch.messages()? { | |
| 321 | // A workspace renamed: its rows move to the slug it has now. | |
| 322 | if g1t_kit::rename::on_event(&env, &env.d1("DB")?, message.body(), rename::STATEMENTS).await? { | |
| 323 | message.ack(); | |
| 324 | continue; | |
| 325 | } | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 326 | // A repository renamed or transferred: its rows follow its new path. |
| 327 | if g1t_kit::transfer::on_event(&env, &env.d1("DB")?, message.body(), rename::TRANSFERRED).await? { | |
| 328 | message.ack(); | |
| 329 | continue; | |
| 330 | } | |
| 331 | // A workspace deleted: what it kept for itself goes. | |
| 332 | if g1t_kit::deleted::on_event(&env.d1("DB")?, message.body(), rename::DELETED).await? { | |
| 333 | message.ack(); | |
| 334 | continue; | |
| 335 | } | |
| 336 | // A repository purged: every row kept for it goes. | |
| 337 | if g1t_kit::lifecycle::on_purged(&env.d1("DB")?, message.body(), rename::PURGED).await? { | |
| 338 | message.ack(); | |
| 339 | continue; | |
| 340 | } | |
| 341 | // A repository deleted or archived: its runs stop. | |
| 342 | if let Some(repo_id) = plan::stops_runs(message.body()) { | |
| 343 | if let Err(error) = service.stop_runs(&repo_id).await { | |
| 344 | worker::console_error!("actions: event {} failed: {error}", message.body().id); | |
| 345 | message.retry(); | |
| 346 | continue; | |
| 347 | } | |
| 348 | message.ack(); | |
| 349 | continue; | |
| 350 | } | |
| The statement is a month at a time, a line per kind of charge | 351 | if let Err(error) = service.on_event(message.body()).await { |
| 352 | worker::console_error!("actions: event {} failed: {error}", message.body().id); | |
| 353 | message.retry(); | |
| 354 | continue; | |
| 355 | } | |
| 356 | message.ack(); | |
| 357 | } | |
| 358 | Ok(()) | |
| 359 | } | |
| 360 | ||
| 361 | /// Every minute: schedules that fire, jobs waiting for room, and jobs | |
| 362 | /// whose sandbox went quiet. | |
| 363 | #[event(scheduled)] | |
| 364 | async fn scheduled(_event: ScheduledEvent, env: Env, _ctx: ScheduleContext) { | |
| 365 | match Actions::new(&env) { | |
| 366 | Ok(service) => { | |
| 367 | if let Err(error) = service.on_minute(g1t_kit::now_ms()).await { | |
| 368 | worker::console_error!("actions: the sweep failed: {error}"); | |
| 369 | } | |
| 370 | } | |
| 371 | Err(error) => worker::console_error!("actions: could not start: {error}"), | |
| 372 | } | |
| 373 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.