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.
| 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. | 1 | /** |
| 2 | * Who may open a build from a workflow, what a run builds, and whether it | |
| 3 | * is trusted. `@g1t/contracts` imports its modules without extensions, so | |
| 4 | * a resolve hook adds them (as in moves.flow.test.ts). | |
| 5 | */ | |
| 6 | ||
| 7 | import assert from "node:assert/strict"; | |
| 8 | import { registerHooks } from "node:module"; | |
| 9 | import { test } from "node:test"; | |
| 10 | ||
| 11 | import type { User } from "@g1t/contracts"; | |
| 12 | ||
| 13 | registerHooks({ | |
| 14 | resolve(specifier, context, next) { | |
| 15 | try { | |
| 16 | return next(specifier, context); | |
| 17 | } catch (error) { | |
| 18 | if (specifier.startsWith(".")) return next(`${specifier}.ts`, context); | |
| 19 | throw error; | |
| 20 | } | |
| 21 | }, | |
| 22 | }); | |
| 23 | const { commitOf, jobOf, projectNamed, readOnlyAllowed, runTrusted, targetOf } = await import("./workflow-builds.ts"); | |
| 24 | ||
| 25 | const repo = { namespace: "acme", name: "web" }; | |
| 26 | const jobToken = (token: Partial<NonNullable<User["token"]>>): User => | |
| 27 | ({ | |
| 28 | id: "wsp_acme", | |
| 29 | username: "acme", | |
| 30 | kind: "workspace", | |
| 31 | verified: true, | |
| 32 | workspaces: [{ slug: "acme", role: "member" }], | |
| 33 | token: { token_id: "tok_1", repo: "acme/web", job: { run_id: "run_1", job_id: "job_1" }, scopes: ["deployments:write"], ...token }, | |
| 34 | }) as User; | |
| 35 | ||
| 36 | test("only a workflow job's token for the repository, with deployments: write, opens a build", () => { | |
| 37 | assert.deepEqual(jobOf(jobToken({}), repo), { runId: "run_1", jobId: "job_1", writes: true }); | |
| 38 | // write-all, and contents with deployments. | |
| 39 | assert.ok(!("error" in jobOf(jobToken({ scopes: ["contents:read", "deployments:write"] }), repo))); | |
| 40 | // A person's token, however wide, is not a job's. | |
| 41 | const person = { id: "usr_1", username: "ada", kind: "user", verified: true, token: { token_id: "tok_2", scopes: null } } as User; | |
| 42 | assert.match((jobOf(person, repo) as { error: string }).error, /g1t Actions job's token/); | |
| 43 | assert.match((jobOf(null, repo) as { error: string }).error, /g1t Actions job's token/); | |
| 44 | // Read only: what a pull request from outside gets. Its job is named, | |
| 45 | // and only a managed workflow's preview lets it through. | |
| 46 | assert.deepEqual(jobOf(jobToken({ scopes: ["deployments:read", "contents:read"] }), repo), { runId: "run_1", jobId: "job_1", writes: false }); | |
| 47 | // Another repository's job. | |
| 48 | assert.match((jobOf(jobToken({ repo: "acme/api" }), repo) as { error: string }).error, /acme\/api, not acme\/web/); | |
| 49 | // Case does not matter in names. | |
| 50 | assert.ok(!("error" in jobOf(jobToken({ repo: "Acme/Web" }), repo))); | |
| 51 | }); | |
| 52 | ||
| 53 | test("a run on the production branch builds production; another branch, its preview; a pull request, its own", () => { | |
| 54 | for (const event of ["push", "workflow_dispatch", "schedule"]) { | |
| 55 | assert.deepEqual(targetOf({ event, ref: "refs/heads/main", pull: null }, "main"), { kind: "production" }); | |
| 56 | } | |
| 57 | assert.deepEqual(targetOf({ event: "push", ref: "refs/heads/feature/x", pull: null }, "main"), { kind: "branch", branch: "feature/x" }); | |
| 58 | assert.deepEqual(targetOf({ event: "push", ref: "main", pull: null }, "main"), { kind: "production" }); | |
| 59 | assert.deepEqual(targetOf({ event: "pull_request", ref: "refs/pull/9/merge", pull: 9 }, "main"), { kind: "pull", number: 9 }); | |
| 60 | assert.deepEqual(targetOf({ event: "pull_request_target", ref: "refs/heads/main", pull: 9 }, "main"), { kind: "pull", number: 9 }); | |
| 61 | // The production branch is the project's, whatever it is called. | |
| 62 | assert.deepEqual(targetOf({ event: "push", ref: "refs/heads/main", pull: null }, "trunk"), { kind: "branch", branch: "main" }); | |
| 63 | // A tag deploys nothing. | |
| 64 | assert.match((targetOf({ event: "push", ref: "refs/tags/v1.0.0", pull: null }, "main") as { error: string }).error, /refs\/tags\/v1.0.0/); | |
| 65 | }); | |
| 66 | ||
| 67 | test("a run from a fork, from someone who cannot push, or one that needed approval gets no secrets", () => { | |
| 68 | assert.equal(runTrusted({ approval: null, pull: null, insider: true }), true); | |
| 69 | assert.equal(runTrusted({ approval: null, pull: { fork: null }, insider: true }), true); | |
| 70 | assert.equal(runTrusted({ approval: null, pull: { fork: { namespace: "eve", name: "web" } }, insider: true }), false); | |
| 71 | assert.equal(runTrusted({ approval: null, pull: { fork: null }, insider: false }), false); | |
| 72 | assert.equal(runTrusted({ approval: { state: "approved" }, pull: { fork: null }, insider: true }), false); | |
| 73 | assert.equal(runTrusted({ approval: { state: "required" }, pull: null, insider: true }), false); | |
| 74 | }); | |
| 75 | ||
| 76 | test("the commit is the run's unless one is given", () => { | |
| 77 | const run = { sha: "4b8d0f2a6c1e3579bd02468ace13579bdf02468a" }; | |
| 78 | assert.equal(commitOf(undefined, run), run.sha); | |
| 79 | assert.equal(commitOf("", run), run.sha); | |
| 80 | assert.equal(commitOf(" ABCDEF1 ", run), "abcdef1"); | |
| 81 | assert.ok(typeof commitOf("main", run) === "object"); | |
| 82 | assert.ok(typeof commitOf(7, run) === "object"); | |
| 83 | }); | |
| 84 | ||
| 85 | test("the project is the one named, or the only one with Deployments on", () => { | |
| 86 | const web = { slug: "web", on: true }; | |
| 87 | const docs = { slug: "docs", on: false }; | |
| 88 | const api = { slug: "api", on: true }; | |
| 89 | const on = (p: { on: boolean }) => p.on; | |
| 90 | assert.equal(projectNamed([web, docs], on, null), web); | |
| 91 | assert.equal(projectNamed([web, docs], on, "Docs"), docs); | |
| 92 | assert.deepEqual(projectNamed([web, docs], on, "nope"), { error: "This repository has no project nope.", code: "not_found" }); | |
| 93 | assert.deepEqual(projectNamed([docs], on, undefined), { error: "No project of this repository has Deployments on.", code: "conflict" }); | |
| 94 | assert.deepEqual(projectNamed([web, api], on, ""), { error: "Name the project to build: web, api.", code: "invalid" }); | |
| 95 | }); | |
| 96 | ||
| 97 | test("a read-only token opens only a preview, and only from the deploy workflow g1t manages", () => { | |
| 98 | const managed = { path: "managed/deploy-web.yml" }; | |
| 99 | const own = { path: ".g1t/workflows/deploy.yml" }; | |
| 100 | assert.ok(readOnlyAllowed(managed, { kind: "pull", number: 4 })); | |
| 101 | assert.ok(readOnlyAllowed(managed, { kind: "branch", branch: "fix" })); | |
| 102 | assert.ok(!readOnlyAllowed(managed, { kind: "production" })); | |
| 103 | assert.ok(!readOnlyAllowed(own, { kind: "pull", number: 4 })); | |
| 104 | assert.ok(!readOnlyAllowed(managed, { error: "a tag" })); | |
| 105 | }); |
This file's history is long; its oldest lines are credited to the oldest commit read.