diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 9aae3c6d06..0f234d2553 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -37,3 +37,6 @@ updates: day: monday time: "06:00" open-pull-requests-limit: 5 + ignore: + # This first-party workflow follows CODEOWNERS-protected master. + - dependency-name: "paperclipai/paperclip/.github/workflows/pr-trusted.yml" diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 41125c3331..388c13838c 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -10,5 +10,6 @@ permissions: jobs: ci: - # Pin: #13457 merge — restore-only Rust dependency cache on the PR runner lane. - uses: paperclipai/paperclip/.github/workflows/pr-trusted.yml@f97a3f886eb4baf19ea8eefaa53a44cb1c2d4636 + # Master requires CODEOWNERS review for .github/**, including this workflow. + # The AWS runner group permits pr-trusted.yml@refs/heads/master. + uses: paperclipai/paperclip/.github/workflows/pr-trusted.yml@master diff --git a/doc/DEVELOPING.md b/doc/DEVELOPING.md index dc1eaa5fbd..0e4de36e84 100644 --- a/doc/DEVELOPING.md +++ b/doc/DEVELOPING.md @@ -23,6 +23,24 @@ GitHub Actions owns `pnpm-lock.yaml`. - Pull request CI validates dependency resolution when manifests change. - Pushes to `master` regenerate `pnpm-lock.yaml` with `pnpm install --lockfile-only --no-frozen-lockfile`, commit it back if needed, and then run verification with `--frozen-lockfile`. +## Trusted PR Workflow + +The PR caller uses `paperclipai/paperclip/.github/workflows/pr-trusted.yml@master`. +The AWS runner group `paperclip-public-pr` must allow +`paperclipai/paperclip/.github/workflows/pr-trusted.yml@refs/heads/master`. +New workflow versions merged into master then receive runner access without a +separate SHA allowlist update. Dependabot leaves this first-party reference on +master. + +Keep the `.github/**` rule in `.github/CODEOWNERS` and the active master ruleset's +code-owner review requirement enabled. This covers the caller, the trusted +workflow, and CODEOWNERS itself. Existing administrator pull-request bypasses +remain governed by the repository ruleset. + +When changing the workflow path or branch, authorize the new reference before +updating the caller. Retain older authorized SHA references while queued runs or +supported reruns still use them. + ## Start Dev From repo root: diff --git a/scripts/__tests__/e2e-shard.test.mjs b/scripts/__tests__/e2e-shard.test.mjs index 2fa156fc2f..d90021e114 100644 --- a/scripts/__tests__/e2e-shard.test.mjs +++ b/scripts/__tests__/e2e-shard.test.mjs @@ -25,19 +25,15 @@ function runShard(args) { return result.stdout.trim().split(/\s+/).filter(Boolean); } -function readPinnedTrustedPrWorkflow() { +function readTrustedPrWorkflow() { const caller = readFileSync(prCallerWorkflow, "utf8"); - const pin = caller.match( - /uses: paperclipai\/paperclip\/\.github\/workflows\/pr-trusted\.yml@([0-9a-f]{40})/, + assert.match( + caller, + /^\s+uses: paperclipai\/paperclip\/\.github\/workflows\/pr-trusted\.yml@master\s*$/m, + "pr.yml must call the trusted workflow from CODEOWNERS-protected master", ); - assert.ok(pin, "pr.yml must call the trusted workflow at a full commit SHA"); - - const result = spawnSync("git", ["show", `${pin[1]}:${trustedPrWorkflowPath}`], { - cwd: repoRoot, - encoding: "utf8", - }); - assert.equal(result.status, 0, `cannot read the pinned trusted workflow: ${result.stderr}`); - return result.stdout; + // Validate proposed workflow changes locally; CI executes the merged master version. + return readFileSync(trustedPrWorkflow, "utf8"); } function readWorkflowJobs(workflow) { @@ -161,15 +157,15 @@ test("shard arguments are validated", () => { } }); -test("pr.yml calls the trusted PR workflow at an immutable SHA", () => { - assert.ok(readPinnedTrustedPrWorkflow().length > 0); +test("pr.yml calls the trusted PR workflow from master", () => { + assert.ok(readTrustedPrWorkflow().length > 0); }); test("the trusted PR workflow keeps a stable aggregate check named e2e over the shard matrix", () => { // Branch protection requires a check literally named `e2e`. The shards run // as `e2e shard (n/3)`, so the aggregate job below is what keeps the // required-check contract intact — same pattern as the `verify` aggregate. - const workflow = readPinnedTrustedPrWorkflow(); + const workflow = readTrustedPrWorkflow(); const jobs = readWorkflowJobs(workflow); const aggregate = jobs.get("e2e"); @@ -293,7 +289,7 @@ test("the stacked PR scope selector runs full CI only where intended", () => { test("the trusted PR workflow passes the shard's spec filter to Playwright without a literal --", () => { // `pnpm run test:e2e -- $specs` forwards the literal separator to Playwright, // so the specs after it are not applied as file filters. - const workflow = readPinnedTrustedPrWorkflow(); + const workflow = readTrustedPrWorkflow(); assert.ok( !/pnpm run test:e2e --\s/.test(workflow), "pr-trusted.yml must not insert a literal `--` between `pnpm run test:e2e` and the spec filter", @@ -306,10 +302,8 @@ test("the trusted PR workflow passes the shard's spec filter to Playwright witho }); test("the trusted PR workflow regenerates stale stacked lockfiles", () => { - // Implementation PRs validate the workflow under development here. The - // caller remains pinned to the last merged trusted SHA until a separate - // activation PR advances it, so unmerged PR code never runs on trusted - // infrastructure. + // Validate the proposed workflow here. The caller executes the merged master + // workflow; edits to this workflow take effect after code-owner review and merge. const workflow = readFileSync(trustedPrWorkflow, "utf8"); assert.match( workflow,