You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@magpie.apache.org by po...@apache.org on 2026/10/24 12:08:18 UTC

(magpie-site) branch main updated: Require one ci-passed check on main, with a committer bypass and auto-merge (#251)

This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/magpie-site.git


The following commit(s) were added to refs/heads/main by this push:
     new 93041a5  Require one ci-passed check on main, with a committer bypass and auto-merge (#251)
93041a5 is described below

commit 93041a595c893de8b4af5cebc8728ff7cf6cd3f0
Author: Jarek Potiuk <ja...@potiuk.com>
AuthorDate: Sat Oct 24 14:08:13 2026 +0200

    Require one ci-passed check on main, with a committer bypass and auto-merge (#251)
    
    * Require one ci-passed check on main, and let PRs auto-merge
    
    #246's "main checks" ruleset was never created: GitHub refused the
    magpie-committers team as a bypass actor, so main has had no required check
    since. Go back to classic protection, as in apache/magpie, requiring a new
    ci-passed job that always runs and fails when any job before it failed or was
    cancelled; build alone could not be required, as a job skipped after a
    failed need counts as passing. Remove the rulesets, and allow auto-merge.
    
    Generated-by: Claude Opus 5
    
    * Let committers merge past ci-passed, as in infrastructure-actions
    
    Require ci-passed through a ruleset whose bypass actors are repository
    roles, not a team: write access may merge a pull request past the checks
    (never push past them), admins always. GitHub refused the magpie-committers
    team; apache/infrastructure-actions uses the same roles, and its ruleset
    reports a committer can bypass it on pull requests. Classic protection stays
    only to keep main from being force-pushed or deleted.
    
    Generated-by: Claude Opus 5
---
 .asf.yaml                          | 101 ++++++++++++++++++++++---------------
 .github/workflows/build.yml        |  21 ++++++++
 scripts/preview-workflows.test.mjs |  12 +++++
 3 files changed, 92 insertions(+), 42 deletions(-)

diff --git a/.asf.yaml b/.asf.yaml
index 51b930a..c0f4aaa 100644
--- a/.asf.yaml
+++ b/.asf.yaml
@@ -30,50 +30,67 @@ github:
     merge: false
     rebase: false
 
-  # Branch protection for main, as rulesets rather than classic protection,
-  # which has no way to let committers override a check. Classic protection
-  # and rulesets would both apply, so classic protection is removed.
-  protected_branches: ~
+  # Branch protection for main, as in apache/infrastructure-actions: classic
+  # protection keeps main from being force-pushed or deleted, and the checks
+  # every PR must pass are a ruleset, because only a ruleset can name who may
+  # bypass it.
+  protected_branches:
+    main: {}
+
+  # Only checks that run on EVERY pull request may be listed here: a required
+  # context whose workflow does not trigger is never reported, and the pull
+  # request blocks forever with nothing to re-run. ci-passed is build.yml's
+  # last job, which always runs and fails when any job before it failed or was
+  # cancelled. A job skipped because a need failed would otherwise count as
+  # passing, and so would the browser suites a PR skips when it changes
+  # neither the site nor the tests.
+  #
+  # Deliberately NOT required:
+  #   asf-allowlist-check - path-filtered to .github/**, so it is absent on
+  #                         any pull request that does not touch a workflow.
+  #   deploy              - gated to refs/heads/main; never runs on a PR.
+  #   publish             - PR previews: workflow_run / issue_comment /
+  #                         workflow_dispatch; never a pull request check.
+  #   signal              - preview-signal.yml; runs only on some PR events.
+  #
+  # The bypass names repository roles, not a team: GitHub refused the
+  # magpie-committers team ("must be part of the ruleset source or owner
+  # organization"). Raw payload syntax, as roles have no convenience key.
   rulesets:
-    # Nobody bypasses this one: main is never force-pushed or deleted.
-    - name: "main history"
-      branches:
-        includes:
-          - "main"
-      restrict_deletion: true
-      restrict_force_push: true
+    - name: "Required checks"
+      target: branch
+      enforcement: active
+      conditions:
+        ref_name:
+          include:
+            - "~DEFAULT_BRANCH"
+          exclude: []
+      bypass_actors:
+        # Repository admins (ASF Infra), always.
+        - actor_id: 5
+          actor_type: RepositoryRole
+          bypass_mode: always
+        # Write access, on pull requests only: a committer can merge past the
+        # checks, e.g. when a browser shard timed out on a slow runner, but
+        # never push past them (gh pr merge --admin).
+        - actor_id: 4
+          actor_type: RepositoryRole
+          bypass_mode: pull_request
+      rules:
+        - type: required_status_checks
+          parameters:
+            # Not strict: main moves quickly, and requiring every branch to be
+            # up to date would mean rebasing each PR whenever anything merges.
+            strict_required_status_checks_policy: false
+            # No integration_id: leaving it out means any source, and a
+            # string one, which is what .asf.yaml would pass, is rejected.
+            required_status_checks:
+              - context: ci-passed
 
-    # Only checks that run on EVERY pull request may be listed here: a
-    # required context whose workflow does not trigger is never reported, and
-    # the pull request blocks forever with nothing to re-run.
-    #
-    # Deliberately NOT required:
-    #   asf-allowlist-check - path-filtered to .github/**, so it is absent on
-    #                         any pull request that does not touch a workflow.
-    #   deploy              - gated to refs/heads/main; never runs on a PR.
-    #   publish             - PR previews: workflow_run / issue_comment /
-    #                         workflow_dispatch; never a pull request check.
-    #   signal              - preview-signal.yml; runs only on some PR events.
-    #
-    # A committer can merge a pull request whose checks have not passed, e.g.
-    # when a browser shard timed out on the Ubuntu mirror (gh pr merge
-    # --admin). The bypass works through pull requests only, so nobody pushes
-    # to main directly.
-    - name: "main checks"
-      branches:
-        includes:
-          - "main"
-      bypass_teams:
-        - "magpie-committers"
-      bypass_mode: pull_request
-      restrict_deletion: false
-      restrict_force_push: false
-      required_status_checks:
-        - lint
-        - build
-      # Not strict: main moves quickly, and requiring every branch to be up to
-      # date would mean rebasing each PR whenever anything merges.
-      required_status_checks_strict: false
+  pull_requests:
+    # A pull request merges itself once ci-passed is green, or is bypassed.
+    allow_auto_merge: true
+    allow_update_branch: true
 
 notifications:
   commits: commits@magpie.apache.org
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 66b7c3e..1b5e95c 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -222,6 +222,27 @@ jobs:
           # dropped on the round-trip through the artifact.
           include-hidden-files: true
 
+  # The one required check (.asf.yaml). It always runs, and fails when any job
+  # before it failed or was cancelled: build is skipped when a need fails, and
+  # a skipped job counts as passing, so build alone cannot be required. Jobs
+  # skipped on purpose (lint and the browser suites on a push, the suites on a
+  # PR that changes neither the site nor the tests) are fine.
+  ci-passed:
+    needs: [changes, lint, browser, build]
+    if: ${{ always() }}
+    runs-on: ubuntu-slim
+    timeout-minutes: 2
+    permissions: {}
+    steps:
+      - name: Check that no job failed or was cancelled
+        env:
+          RESULTS: ${{ join(needs.*.result, ' ') }}
+        run: |
+          echo "changes, lint, browser, build: ${RESULTS}"
+          case " ${RESULTS} " in
+            *" failure "*|*" cancelled "*) exit 1 ;;
+          esac
+
   deploy:
     needs: build
     # The skipped lint and browser jobs upstream of build would otherwise skip
diff --git a/scripts/preview-workflows.test.mjs b/scripts/preview-workflows.test.mjs
index 920f6aa..7cb72c0 100644
--- a/scripts/preview-workflows.test.mjs
+++ b/scripts/preview-workflows.test.mjs
@@ -94,3 +94,15 @@ test("the browser suites run on what `changes` decided, and on every other event
     "skipped only on a pull request that changes neither the site nor the suites",
   );
 });
+
+test("ci-passed, the one required check, waits for every job and always runs", async () => {
+  // A job left out of its needs could fail without blocking a merge.
+  const build = await workflow("build.yml");
+  const jobs = [...build.slice(build.indexOf("\njobs:\n")).matchAll(/^ {2}([a-z][\w-]*):$/gm)].map((m) => m[1]);
+  const job = /^ {2}ci-passed:\n((?: *\n| {4}.*\n)+)/m.exec(build)?.[1] ?? "";
+  const needs = /^ {4}needs: \[(.*)\]$/m.exec(job)?.[1].split(/,\s*/) ?? [];
+  assert.deepEqual(needs.sort(), jobs.filter((j) => !["ci-passed", "deploy"].includes(j)).sort());
+  assert.match(job, /^ {4}if: \$\{\{ always\(\) \}\}$/m);
+  const asf = await readFile(new URL("../.asf.yaml", import.meta.url), "utf8");
+  assert.match(asf, /^ {12}required_status_checks:\n {14}- context: ci-passed\n(?! {14}-)/m, ".asf.yaml requires ci-passed, and only it");
+});