Skip to content
Draft
151 changes: 151 additions & 0 deletions .github/workflows/core-approval.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

name: Core Approval

on:
merge_group:
types: [checks_requested]
pull_request_target:
types: [opened, reopened, synchronize, ready_for_review]
pull_request_review:
types: [submitted, dismissed]
workflow_dispatch:
inputs:
pr_number:
description: Pull request number to re-evaluate
required: true
type: string

permissions:
contents: read
pull-requests: read
statuses: write

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || inputs.pr_number || github.sha || github.run_id }}
cancel-in-progress: true

jobs:
core-approval:
name: Publish core approval status
if: github.repository_owner == 'NVIDIA'
runs-on: ubuntu-latest
env:
STATUS_CONTEXT: OpenShell / Core Approval
steps:
# Check out the default branch, never the pull request head. This job runs
# with a write-capable token, so it must not fetch or execute contributor
# code. Only the approval helper is needed. "main" is hardcoded here and
# in the MAINTAINERS.md fetch below; both must change together if the
# default branch is ever renamed.
- name: Check out the approval helper
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: main
sparse-checkout: tasks/scripts/core_approval.py
sparse-checkout-cone-mode: false
persist-credentials: false

- name: Publish core approval status
id: publish
timeout-minutes: 10
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
EVENT_NAME: ${{ github.event_name }}
PR_NUMBER_FROM_EVENT: ${{ github.event.pull_request.number }}
PR_NUMBER_FROM_INPUT: ${{ inputs.pr_number }}
MERGE_GROUP_SHA: ${{ github.event.merge_group.head_sha }}
shell: bash
run: |
set -euo pipefail

RUN_URL="https://github.com/$GH_REPO/actions/runs/$GITHUB_RUN_ID"

post_status() {
local sha="$1" state="$2" description="$3" target_url="$4"
echo "$STATUS_CONTEXT: $state - $description"
gh api --method POST "repos/$GH_REPO/statuses/$sha" \
-f "state=$state" \
-f "context=$STATUS_CONTEXT" \
-f "description=$description" \
-f "target_url=$target_url" >/dev/null
# A status was published, so the guard step has nothing to add.
# Step outputs are collected after the step finishes, including
# when it fails, so this reaches the guard either way.
echo "posted=true" >> "$GITHUB_OUTPUT"
}

# A merge group only forms after the pull request satisfied this gate,
# and approvals cannot change while an entry sits in the queue. Publish
# success so the queue's required-check evaluation resolves instead of
# waiting out check_response_timeout_minutes.
if [ "$EVENT_NAME" = "merge_group" ]; then
post_status "$MERGE_GROUP_SHA" success \
"Approval enforced at pull request" "$RUN_URL"
exit 0
fi

PR_NUMBER="${PR_NUMBER_FROM_EVENT:-$PR_NUMBER_FROM_INPUT}"
PR=$(gh api "repos/$GH_REPO/pulls/$PR_NUMBER")

if [ "$(jq -r '.state' <<< "$PR")" != "open" ]; then
echo "PR #$PR_NUMBER is not open; nothing to publish."
exit 0
fi

HEAD_SHA=$(jq -r '.head.sha' <<< "$PR")
echo "head_sha=$HEAD_SHA" >> "$GITHUB_OUTPUT"
AUTHOR=$(jq -r '.user.login' <<< "$PR")
TARGET_URL="https://github.com/$GH_REPO/pull/$PR_NUMBER"

# Pinned to main on purpose. Reading this file from the pull request
# ref would let a contributor add themselves and self-approve.
gh api -H "Accept: application/vnd.github.raw" \
"repos/$GH_REPO/contents/MAINTAINERS.md?ref=main" > maintainers.md

gh api --paginate "repos/$GH_REPO/pulls/$PR_NUMBER/reviews" --jq '.[]' \
| jq -s '.' > reviews.json

if ! RESULT=$(python3 tasks/scripts/core_approval.py decide \
--maintainers maintainers.md \
--reviews reviews.json \
--author "$AUTHOR"); then
post_status "$HEAD_SHA" failure \
"Could not evaluate maintainer approval" "$RUN_URL"
exit 1
fi

post_status "$HEAD_SHA" "${RESULT%%$'\t'*}" "${RESULT#*$'\t'}" "$TARGET_URL"

# If the step above aborted before publishing anything — a transient API
# failure, a failed checkout, the step timing out — the required check
# would otherwise sit at "Expected" forever. Publish red so the state is
# visible and the job can be re-run.
- name: Publish a failure status if none was published
if: failure() && steps.publish.outputs.posted != 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
# The step output covers a failure after the pull request was looked
# up, including on workflow_dispatch. The event payload covers a
# failure before that — a failed checkout, or a transient API error.
STATUS_SHA: ${{ steps.publish.outputs.head_sha || github.event.pull_request.head.sha || github.event.merge_group.head_sha }}
shell: bash
run: |
set -euo pipefail

# workflow_dispatch aborting before the lookup leaves no SHA to
# address. Nothing can be published; say so rather than failing here.
if [ -z "$STATUS_SHA" ]; then
echo "::warning::No head SHA resolved; cannot publish a failure status."
exit 0
fi

gh api --method POST "repos/$GH_REPO/statuses/$STATUS_SHA" \
-f "state=failure" \
-f "context=$STATUS_CONTEXT" \
-f "description=Could not evaluate maintainer approval" \
-f "target_url=https://github.com/$GH_REPO/actions/runs/$GITHUB_RUN_ID" \
>/dev/null
98 changes: 98 additions & 0 deletions .github/workflows/maintainers-change-alert.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

name: Maintainers Change Alert

on:
pull_request_target:
types: [opened, reopened, synchronize]
paths:
- MAINTAINERS.md

permissions:
contents: read
pull-requests: write

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
describe-change:
name: Comment on the approver set change
if: github.repository_owner == 'NVIDIA'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
# Default branch only. The helper must be the reviewed version, not
# whatever the pull request happens to contain.
- name: Check out the approval helper
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: main
sparse-checkout: tasks/scripts/core_approval.py
sparse-checkout-cone-mode: false
persist-credentials: false

- name: Post the maintainer delta
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
shell: bash
run: |
set -euo pipefail

# Fetching file contents is reading data, not executing it. The head
# revision is never checked out or run.
#
# A 404 means the file genuinely does not exist at that revision — a
# pull request that adds or deletes MAINTAINERS.md — and yields an
# empty side of the comparison. Every other failure is fatal: an empty
# file from a rate limit or a 5xx would render as "every maintainer
# was just added" or "nothing changed", both of which mislead the
# reviewer about who can merge code.
fetch_maintainers() {
local ref="$1" out="$2" err
err="$(mktemp)"
if gh api -H "Accept: application/vnd.github.raw" \
"repos/$GH_REPO/contents/MAINTAINERS.md?ref=$ref" > "$out" 2>"$err"; then
rm -f "$err"
return 0
fi
if grep -q 'HTTP 404' "$err"; then
rm -f "$err"
: > "$out"
return 0
fi
echo "::error::Could not fetch MAINTAINERS.md at $ref"
cat "$err" >&2
rm -f "$err"
return 1
}

fetch_maintainers "$BASE_SHA" before.md
fetch_maintainers "$HEAD_SHA" after.md

python3 tasks/scripts/core_approval.py diff \
--before before.md --after after.md > body.md
cat body.md >> "$GITHUB_STEP_SUMMARY"

# Update the existing comment rather than stacking one per push.
# This marker must stay identical to COMMENT_MARKER in
# tasks/scripts/core_approval.py, which emits it as the first line of
# the body. If they drift, the lookup below silently stops matching
# and every push stacks another comment.
COMMENT_ID=$(gh api --paginate "repos/$GH_REPO/issues/$PR_NUMBER/comments" \
--jq '.[] | select(.body | startswith("<!-- core-approval-maintainer-delta -->")) | .id' \
| head -n 1)

if [ -n "$COMMENT_ID" ]; then
gh api --method PATCH "repos/$GH_REPO/issues/comments/$COMMENT_ID" \
-F "body=@body.md" >/dev/null
else
gh api --method POST "repos/$GH_REPO/issues/$PR_NUMBER/comments" \
-F "body=@body.md" >/dev/null
fi
2 changes: 2 additions & 0 deletions .github/zizmor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ rules:
ignore:
# These base-branch workflows never check out or execute pull request
# head code. Keep each suppression scoped to its reviewed trigger block.
- core-approval.yml:6
- dco.yml:3
- e2e-label-help.yml:13
- maintainers-change-alert.yml:6
- release-canary.yml:3
- required-ci-gates.yml:3
- vouch-check.yml:3
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ Do not start substantial issue-backed work until a maintainer has accepted the i

Use agents and the repository skills as needed to understand the affected code, evaluate tradeoffs, implement the smallest coherent change, and verify it. The pull request should explain what changed and how it was tested; it should not substitute an agent transcript for the contributor's understanding.

Every pull request must be approved by someone listed in [MAINTAINERS.md](MAINTAINERS.md) before it can merge. This is enforced by the `OpenShell / Core Approval` status check, which turns green once one of those reviewers approves. Reviews from other contributors are welcome and count toward the general approval requirement, but they do not satisfy this check.

Maintainers are not requested automatically. If your pull request has been idle, ask for a reviewer in the pull request or in the CNCF Slack channel rather than waiting.

## Agent Skills

OpenShell keeps skills for using the product separate from skills for developing the repository.
Expand Down
Loading
Loading