diff --git a/.github/scripts/tests/ci-routing.test.mjs b/.github/scripts/tests/ci-routing.test.mjs index fedc277482a..6d9e3d7ec0c 100644 --- a/.github/scripts/tests/ci-routing.test.mjs +++ b/.github/scripts/tests/ci-routing.test.mjs @@ -10,7 +10,12 @@ const jobs = workflow.split(/^jobs:\n/m)[1]; test("routing uses reserved capacity and runs no PR code or actions", () => { assert.deepEqual([...jobs.matchAll(/^ {2}([a-z_]+):$/gm)].map((match) => match[1]), ["gate"]); - assert.match(jobs, /^ {4}runs-on: runs-on\/fleet=paperclip-ci-routing-x64\/env=public-ci$/m); + const placement = jobs.match(/^ {4}runs-on: (.+)$/m)?.[1]; + assert.ok(placement, "routing placement must be defined"); + assert.ok(placement.includes("vars.AWS_CI_ENABLED == 'true' && github.repository == 'paperclipai/paperclip' && github.repository_id == '1170821064'")); + assert.ok(placement.includes("github.event_name == 'pull_request' && contains(fromJSON(vars.AWS_CI_TRUSTED_USER_IDS || '[]'), github.event.pull_request.user.id) && contains(fromJSON(vars.AWS_CI_TRUSTED_USER_IDS || '[]'), github.event.sender.id)")); + assert.ok(placement.includes("github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/master' && contains(fromJSON(vars.AWS_CI_TRUSTED_USER_IDS || '[]'), fromJSON(github.actor_id))")); + assert.ok(placement.endsWith("&& 'runs-on/fleet=paperclip-ci-routing-x64/env=public-ci' || 'ubuntu-latest' }}")); assert.doesNotMatch(jobs, /uses:|actions\/checkout|secrets\./); assert.doesNotMatch(workflow, /^ {2}(?:actions|contents|pull-requests): write$/m); }); diff --git a/.github/workflows/ci-routing.yml b/.github/workflows/ci-routing.yml index b8d2e93fa5c..8baba75a70e 100644 --- a/.github/workflows/ci-routing.yml +++ b/.github/workflows/ci-routing.yml @@ -17,7 +17,7 @@ permissions: jobs: gate: name: Select trusted runner - runs-on: runs-on/fleet=paperclip-ci-routing-x64/env=public-ci + runs-on: ${{ vars.AWS_CI_ENABLED == 'true' && github.repository == 'paperclipai/paperclip' && github.repository_id == '1170821064' && ((github.event_name == 'pull_request' && contains(fromJSON(vars.AWS_CI_TRUSTED_USER_IDS || '[]'), github.event.pull_request.user.id) && contains(fromJSON(vars.AWS_CI_TRUSTED_USER_IDS || '[]'), github.event.sender.id)) || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/master' && contains(fromJSON(vars.AWS_CI_TRUSTED_USER_IDS || '[]'), fromJSON(github.actor_id)))) && 'runs-on/fleet=paperclip-ci-routing-x64/env=public-ci' || 'ubuntu-latest' }} timeout-minutes: 5 outputs: runner: ${{ steps.route.outputs.runner }} diff --git a/doc/DEVELOPING.md b/doc/DEVELOPING.md index 50d4b568f78..4eedd722bb1 100644 --- a/doc/DEVELOPING.md +++ b/doc/DEVELOPING.md @@ -38,8 +38,10 @@ master. Runner selection calls `ci-routing.yml@master` on the dedicated `paperclip-ci-routing-x64` AWS Fleet. The `paperclip-ci-routing` runner group admits only `paperclipai/paperclip/.github/workflows/ci-routing.yml@refs/heads/master` -and repository ID `1170821064`. The routing workflow reads GitHub metadata -without checking out or executing PR code. Its author, sender, rerun actor, +and repository ID `1170821064`. GitHub evaluates the canonical repository, author, and sender checks before +allocating reserved capacity. Unapproved PRs use hosted metadata routing; +approved manual canaries on master can use the reserved pool. The routing +workflow reads GitHub metadata without checking out or executing PR code. Its author, sender, rerun actor, repository, head, and merge validation still decide whether tests can use AWS. Unapproved test workloads continue to use GitHub-hosted runners. diff --git a/scripts/__tests__/e2e-shard.test.mjs b/scripts/__tests__/e2e-shard.test.mjs index 24d233fbaf6..28bf3926f3c 100644 --- a/scripts/__tests__/e2e-shard.test.mjs +++ b/scripts/__tests__/e2e-shard.test.mjs @@ -212,7 +212,7 @@ test("runner selection has reserved AWS capacity and executes metadata checks on const workflow = readFileSync(routingWorkflow, "utf8"); const jobs = readWorkflowJobs(workflow.split(/^jobs:\n/m)[1]); assert.deepEqual([...jobs.keys()], ["gate"]); - assert.match(jobs.get("gate"), /^ {4}runs-on: runs-on\/fleet=paperclip-ci-routing-x64\/env=public-ci$/m); + assert.match(jobs.get("gate"), /^ {4}runs-on: \$\{\{ vars\.AWS_CI_ENABLED[\s\S]*paperclip-ci-routing-x64/m); assert.doesNotMatch(jobs.get("gate"), /uses:|actions\/checkout|secrets\./); assert.match(workflow, /^ {6}runner:\n {8}value: \$\{\{ jobs\.gate\.outputs\.runner \}\}$/m); assert.match(workflow, /^ {6}full_ci:\n {8}value: \$\{\{ jobs\.gate\.outputs\.full_ci \}\}$/m);