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

github-merge-queue[bot] pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/texera.git


The following commit(s) were added to refs/heads/main by this push:
     new 069cd208c6 ci: give GitHub Actions bypass on the release-branch 
ruleset (#8379)
069cd208c6 is described below

commit 069cd208c68d3f3a8e50d583a0a0ff7e00a4e536
Author: Meng Wang <[email protected]>
AuthorDate: Fri Sep 11 14:11:59 2026 +0000

    ci: give GitHub Actions bypass on the release-branch ruleset (#8379)
    
    ### What changes were proposed in this PR?
    
    The Merge Queue ruleset requires every change into `release/*` to arrive
    as a PR with one approving review, green required checks, and a pass
    through the merge queue. Right for people — but it also blocks
    `direct-backport-push.yml`, whose fast path pushes clean cherry-picks;
    every such push has been rejected since 2026-07-24, and five backports
    were silently lost (#8377).
    
    This splits the ruleset in two, rule-for-rule identical: `Merge Queue`
    keeps `~DEFAULT_BRANCH`, and a new `Merge Queue (release)` carries the
    three release branches plus a `bypass_actors` entry for the GitHub
    Actions app (`actor_id: 15368`). The split exists because a bypass is
    ruleset-wide — kept in one ruleset, it would let workflows push `main`
    too.
    
    Scope, stated precisely: the bypass exempts actions performed as the
    Actions app — any workflow's `GITHUB_TOKEN`, not just the backport
    workflow, since rulesets cannot scope a bypass to one workflow. People
    and PATs still face every rule on every branch; `main` gets no bypass;
    force pushes and branch deletion stay blocked for everyone, Actions
    included, by `Default Branch Protection`.
    
    Ordering inside the file is load-bearing: asfyaml applies rulesets in
    file order, so `Merge Queue (release)` is created before `Merge Queue`
    stops covering the release branches. If GitHub rejects the new ruleset,
    the apply aborts with today's protections fully intact — no failure path
    leaves the release branches uncovered.
    
    The bypass alone would not revive the fast path: since #4676 the push
    job checked out with `AUTO_MERGE_TOKEN`, so GitHub evaluated its pushes
    as that PAT's owner — every pre-ruleset direct push shows a person as
    the pusher — and an Actions-app bypass would not cover them. The push
    job now uses the default `GITHUB_TOKEN`, which the bypass does cover,
    and dispatches `Required Checks` on the pushed branch explicitly, since
    a `GITHUB_TOKEN` push starts no push-triggered runs while
    `workflow_dispatch` is the documented exception that always creates one.
    The conflict path keeps the PAT: it pushes unprotected `backport/*`
    branches, where the opened PR's CI must still trigger.
    
    ### Any related issues, documentation, discussions?
    
    Closes #8377. #8378 took the PR-plus-auto-merge route to the same
    problem and is closed in favor of trying the bypass first. What lands on
    a release branch through this path is still only a cherry-pick of a
    commit that passed main's full CI and, once #8096 lands, its release
    manager's approving review.
    
    ### How was this PR tested?
    
    `.asf.yaml` and the workflows parse, and the structural check is now
    committed instead of run once: `.github/scripts/test_asf_rulesets.sh`
    (picked up by build.yml's glob-discovered infra tests) asserts the two
    rulesets' `rules` blocks stay deep-equal and that `.asf.yaml` and every
    workflow parse under a duplicate-key-strict loader, with PyYAML pinned
    in `amber/dev-requirements.txt` — the file the infra job installs; every
    failure path (duplicate key, rules drift, bypass on main, bypass
    tampered, ruleset reorder, missing PyYAML) was verified red before
    trusting the green. asfyaml treats a ruleset carrying
    `target`/`rules`/`bypass_actors` as a raw payload and forwards it
    verbatim (`_RAW_RULESET_KEYS` in `feature/github/rulesets.py`; its
    upstream tests assert the POST payload carries `bypass_actors`).
    
    What cannot be proven before merge is GitHub accepting the Actions app
    as a bypass actor on this org: the same payload on a personal repository
    is rejected with "Actor GitHub Actions integration must be part of the
    ruleset source or owner organization", and no ASF repository uses an
    Integration bypass actor yet — hence the fail-safe ordering above. After
    Infra applies the merged file, `GET /repos/apache/texera/rulesets`
    should list `Merge Queue (release)`; if it does not, the apply failed
    closed and nothing changed. The next clean backport is the end-to-end
    test.
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (claude-fable-5)
---
 .asf.yaml                                  |  60 +++++++++++++-
 .github/scripts/test_asf_rulesets.sh       | 122 +++++++++++++++++++++++++++++
 .github/workflows/direct-backport-push.yml |  36 +++++++--
 .github/workflows/required-checks.yml      |   2 +
 amber/dev-requirements.txt                 |   1 +
 5 files changed, 214 insertions(+), 7 deletions(-)

diff --git a/.asf.yaml b/.asf.yaml
index 42316db44a..7e4f86a375 100644
--- a/.asf.yaml
+++ b/.asf.yaml
@@ -69,20 +69,76 @@ github:
     rebase: false
 
   rulesets:
-    - name: Merge Queue
+    # Rule-for-rule identical to "Merge Queue" below; split out so the bypass
+    # here stays off main. The bypass exempts actions performed as the GitHub
+    # Actions app — i.e. any workflow's GITHUB_TOKEN, which is what
+    # direct-backport-push.yml's fast path pushes with (#8377). It cannot be
+    # scoped to a single workflow. People and PATs still face every rule.
+    #
+    # Listed BEFORE "Merge Queue" deliberately: asfyaml applies rulesets in
+    # file order, so this one is created before that one stops covering the
+    # release branches. If GitHub rejects this ruleset, the apply aborts with
+    # the old protections fully intact; the failure order never leaves the
+    # release branches uncovered.
+    - name: "Merge Queue (release)"
       target: branch
       enforcement: active
       conditions:
         ref_name:
           exclude: []
           include:
-            - "~DEFAULT_BRANCH"
             # Merge queue rules do NOT support wildcard ref patterns, so
             # release branches must be listed explicitly (not release/*).
             # Add each release line here as it is cut.
             - "refs/heads/release/v1.1"
             - "refs/heads/release/v1.2"
             - "refs/heads/release/v1.3"
+      bypass_actors:
+        # The GitHub Actions app.
+        - actor_id: 15368
+          actor_type: Integration
+          bypass_mode: always
+      rules:
+        - type: deletion
+        - type: non_fast_forward
+        - type: merge_queue
+          parameters:
+            merge_method: SQUASH
+            max_entries_to_build: 2
+            min_entries_to_merge: 2
+            max_entries_to_merge: 5
+            min_entries_to_merge_wait_minutes: 3
+            grouping_strategy: HEADGREEN
+            check_response_timeout_minutes: 45
+        - type: pull_request
+          parameters:
+            allowed_merge_methods:
+              - squash
+            dismiss_stale_reviews_on_push: false
+            require_code_owner_review: false
+            require_last_push_approval: false
+            required_approving_review_count: 1
+            required_review_thread_resolution: true
+        - type: required_linear_history
+        - type: required_status_checks
+          parameters:
+            strict_required_status_checks_policy: false
+            required_status_checks:
+              - context: Required Checks
+              - context: Check License Headers
+              - context: Validate PR title
+
+    - name: Merge Queue
+      target: branch
+      enforcement: active
+      conditions:
+        ref_name:
+          exclude: []
+          include:
+            # Release branches carry these same rules in "Merge Queue
+            # (release)" above — a separate ruleset because its Actions
+            # bypass must not extend to main.
+            - "~DEFAULT_BRANCH"
       rules:
         - type: deletion
         - type: non_fast_forward
diff --git a/.github/scripts/test_asf_rulesets.sh 
b/.github/scripts/test_asf_rulesets.sh
new file mode 100755
index 0000000000..66bd29c3dc
--- /dev/null
+++ b/.github/scripts/test_asf_rulesets.sh
@@ -0,0 +1,122 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Invariants over the CI configuration that a plain YAML parse cannot see.
+#
+# 1. "Merge Queue" and "Merge Queue (release)" in .asf.yaml must carry
+#    identical rules: they are one policy split across two rulesets only so
+#    the release half can hold an Actions bypass that must not reach main.
+#    Nothing else keeps the copies from drifting apart.
+# 2. .asf.yaml and every workflow must parse with a duplicate-key-strict
+#    loader. PyYAML silently keeps the last duplicate, but GitHub's loader
+#    rejects the file, so a duplicated trigger key passes local checks and
+#    then stops the workflow from ever starting.
+
+set -uo pipefail
+
+command -v python3 >/dev/null || { echo "python3 is required to run these 
tests" >&2; exit 1; }
+# Runners ship python3 but not necessarily PyYAML (see release_branches.py);
+# CI installs it via amber/dev-requirements.txt.
+python3 -c 'import yaml' 2>/dev/null || { echo "PyYAML is required (pip 
install pyyaml)" >&2; exit 1; }
+
+cd "$(git rev-parse --show-toplevel)"
+
+python3 - <<'EOF'
+import glob
+import sys
+
+import yaml
+
+
+class StrictLoader(yaml.SafeLoader):
+    pass
+
+
+def no_duplicates(loader, node, deep=False):
+    seen = set()
+    for key_node, _ in node.value:
+        key = loader.construct_object(key_node, deep=deep)
+        if key in seen:
+            raise yaml.YAMLError(
+                f"duplicate key {key!r} at line {key_node.start_mark.line + 1}"
+            )
+        seen.add(key)
+    return yaml.SafeLoader.construct_mapping(loader, node, deep)
+
+
+StrictLoader.add_constructor(
+    yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, no_duplicates
+)
+
+failures = []
+
+files = sorted(glob.glob(".github/workflows/*.yml")) + [".asf.yaml"]
+for path in files:
+    with open(path) as fh:
+        try:
+            yaml.load(fh, StrictLoader)
+        except yaml.YAMLError as exc:
+            failures.append(f"{path}: {exc}")
+
+with open(".asf.yaml") as fh:
+    ruleset_list = [
+        r for r in yaml.safe_load(fh)["github"]["rulesets"] if isinstance(r, 
dict)
+    ]
+rulesets = {r.get("name"): r for r in ruleset_list}
+main_rs = rulesets.get("Merge Queue")
+release_rs = rulesets.get("Merge Queue (release)")
+if main_rs is None or release_rs is None:
+    failures.append(
+        ".asf.yaml: expected rulesets named 'Merge Queue' and 'Merge Queue 
(release)'"
+    )
+elif main_rs["rules"] != release_rs["rules"]:
+    failures.append(
+        ".asf.yaml: 'Merge Queue' and 'Merge Queue (release)' rules differ -- "
+        "these are one policy in two rulesets; change both or neither"
+    )
+if main_rs is not None and "bypass_actors" in main_rs:
+    failures.append(
+        ".asf.yaml: 'Merge Queue' must not carry bypass_actors -- "
+        "keeping the bypass off main is what the split exists for"
+    )
+ACTIONS_APP = [{"actor_id": 15368, "actor_type": "Integration", "bypass_mode": 
"always"}]
+if release_rs is not None and release_rs.get("bypass_actors") != ACTIONS_APP:
+    failures.append(
+        ".asf.yaml: 'Merge Queue (release)' bypass_actors must be exactly the "
+        "GitHub Actions app -- widen this list and the test together, 
deliberately"
+    )
+names = [r.get("name") for r in ruleset_list]
+if main_rs is not None and release_rs is not None and names.index(
+    "Merge Queue (release)"
+) > names.index("Merge Queue"):
+    failures.append(
+        ".asf.yaml: 'Merge Queue (release)' must be listed before 'Merge 
Queue' -- "
+        "asfyaml applies rulesets in file order, and creating the release 
ruleset "
+        "before shrinking the main one is what keeps a rejected run fail-safe"
+    )
+
+for failure in failures:
+    print(f"FAIL: {failure}")
+if failures:
+    sys.exit(1)
+print(
+    f"OK: {len(files)} files duplicate-key clean; "
+    "Merge Queue rules identical; bypass only on the release ruleset; "
+    "release ruleset listed first"
+)
+EOF
diff --git a/.github/workflows/direct-backport-push.yml 
b/.github/workflows/direct-backport-push.yml
index b67fcc90fc..149c608556 100644
--- a/.github/workflows/direct-backport-push.yml
+++ b/.github/workflows/direct-backport-push.yml
@@ -342,6 +342,15 @@ jobs:
     needs: discover
     if: ${{ needs.discover.outputs.has_push == 'true' }}
     runs-on: ubuntu-latest
+    permissions:
+      # Everything this job's steps call, and nothing more: push the
+      # cherry-pick and comment on the commit (contents), dispatch Required
+      # Checks (actions), set the per-target commit status (statuses),
+      # annotate the original PR (issues).
+      actions: write
+      contents: write
+      issues: write
+      statuses: write
     name: "backport #${{ matrix.pr_number }} to ${{ matrix.target }}"
     strategy:
       fail-fast: false
@@ -356,11 +365,12 @@ jobs:
         uses: actions/checkout@v7
         with:
           fetch-depth: 0
-          # Use AUTO_MERGE_TOKEN (fine-grained PAT) so the push to the release
-          # branch retriggers workflows on that branch. GITHUB_TOKEN-authored
-          # pushes are excluded from triggering downstream workflows, which
-          # silences post-merge CI on backport commits.
-          token: ${{ secrets.AUTO_MERGE_TOKEN || secrets.GITHUB_TOKEN }}
+          # Push with the default GITHUB_TOKEN: the release rulesets admit the
+          # GitHub Actions app as a bypass actor, while a PAT-authored push is
+          # evaluated as that person and rejected. A GITHUB_TOKEN push starts
+          # no downstream workflows, so the step after the cherry-pick
+          # dispatches Required Checks itself — workflow_dispatch runs are the
+          # documented exception that GITHUB_TOKEN may create.
       - name: Cherry-pick merge commit onto target branch
         id: cherry_pick
         env:
@@ -571,6 +581,22 @@ jobs:
           log "new_sha=${new_sha}"
           echo "new_sha=${new_sha}" >> "$GITHUB_OUTPUT"
 
+      - name: Run Required Checks on the pushed release branch
+        if: success()
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          TARGET_BRANCH: ${{ matrix.target }}
+        run: |
+          # The GITHUB_TOKEN push above starts no push-triggered workflows;
+          # dispatch the run the release branch would otherwise have gotten.
+          # Best-effort: the backport itself has landed, so a dispatch
+          # failure must not demote this job to failed -- the failure
+          # reporter below would then claim a landed backport was lost.
+          if ! gh workflow run required-checks.yml \
+              --repo "${GITHUB_REPOSITORY}" --ref "${TARGET_BRANCH}"; then
+            echo "::warning::Could not start Required Checks on 
${TARGET_BRANCH}; start it manually from the Actions tab."
+          fi
+
       - name: Annotate original PR and commit on success
         if: success()
         uses: actions/github-script@v9
diff --git a/.github/workflows/required-checks.yml 
b/.github/workflows/required-checks.yml
index 1db8a52668..42b31d37fd 100644
--- a/.github/workflows/required-checks.yml
+++ b/.github/workflows/required-checks.yml
@@ -30,6 +30,8 @@ on:
       - labeled
       - unlabeled
   merge_group:
+  # Also dispatched by direct-backport-push.yml after its GITHUB_TOKEN push
+  # to a release branch, which starts no push-triggered runs.
   workflow_dispatch:
 
 permissions:
diff --git a/amber/dev-requirements.txt b/amber/dev-requirements.txt
index 848104e776..593c70d130 100644
--- a/amber/dev-requirements.txt
+++ b/amber/dev-requirements.txt
@@ -42,4 +42,5 @@ textual==8.2.8
 
 # Reads bin/k8s/values.yaml in bin/k8s/tests/test_helm_values.sh. That check 
fails
 # rather than skipping when this is missing, so the suite cannot go green by 
accident.
+# Also read by .github/scripts/test_asf_rulesets.sh (infra job shell tests).
 PyYAML==6.0.2

Reply via email to