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");
+});