Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{
"name": "protect-main-release-only",
"target": "branch",
"enforcement": "disabled",
"conditions": {
"ref_name": {
"include": [
"refs/heads/main"
],
"exclude": []
}
},
"rules": [
{
"type": "pull_request",
"parameters": {
"allowed_merge_methods": [
"squash",
"rebase"
],
"dismiss_stale_reviews_on_push": true,
"require_code_owner_review": false,
"require_last_push_approval": false,
"required_approving_review_count": 0,
"required_review_thread_resolution": true
}
},
{
"type": "required_status_checks",
"parameters": {
"strict_required_status_checks_policy": true,
"required_status_checks": [
{
"context": "admin-branch-sync-guard"
},
{
"context": "main-release-guard"
},
{
"context": "do-not-merge-yet"
},
{
"context": "gate"
},
{
"context": "workflow-lint"
},
{
"context": "build"
}
]
}
},
{
"type": "required_linear_history"
},
{
"type": "non_fast_forward"
},
{
"type": "deletion"
}
],
"bypass_actors": []
}
59 changes: 59 additions & 0 deletions .github-stars/control-plane/rulesets/protect-next.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{
"name": "protect-next",
"target": "branch",
"enforcement": "disabled",
"conditions": {
"ref_name": {
"include": [
"refs/heads/next"
],
"exclude": []
}
},
"rules": [
{
"type": "pull_request",
"parameters": {
"allowed_merge_methods": [
"squash",
"rebase"
],
"dismiss_stale_reviews_on_push": true,
"require_code_owner_review": false,
"require_last_push_approval": false,
"required_approving_review_count": 0,
"required_review_thread_resolution": true
}
},
{
"type": "required_status_checks",
"parameters": {
"strict_required_status_checks_policy": true,
"required_status_checks": [
{
Comment on lines +29 to +36

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Require only checks that run on next-targeted PRs

This protect-next ruleset makes gate, workflow-lint, and build mandatory for refs/heads/next, but those jobs are configured to run only for pull requests into main (.github/workflows/00-ci.yml:4-6 and .github/workflows/00b-web-ci.yml:8-10). Once this ruleset is upserted with enforcement=active, PRs targeting next will be blocked waiting for checks that never start, effectively preventing merges into next.

Useful? React with 👍 / 👎.

"context": "do-not-merge-yet"
},
{
"context": "gate"
},
{
"context": "workflow-lint"
},
{
"context": "build"
}
]
}
},
{
"type": "required_linear_history"
},
{
"type": "non_fast_forward"
},
{
"type": "deletion"
}
],
"bypass_actors": []
}
2 changes: 2 additions & 0 deletions .github/workflows/00-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ on:
pull_request:
branches:
- main
- next
push:
branches:
- main
- next

permissions:
contents: read
Expand Down
31 changes: 31 additions & 0 deletions .github/workflows/00a-do-not-merge-yet.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: DoNotMergeYet label gate

on:
pull_request:
types:
- opened
- reopened
- synchronize
- edited
- labeled
- unlabeled
- ready_for_review

permissions:
contents: read

jobs:
do-not-merge-yet:
name: do-not-merge-yet
runs-on: ubuntu-latest
steps:
- name: Fail when PR has DoNotMergeYet label
run: |
set -euo pipefail

if jq -e '.pull_request.labels[]? | select(.name == "DoNotMergeYet")' "$GITHUB_EVENT_PATH" >/dev/null; then
echo "::error::This pull request has the DoNotMergeYet label. Remove the label before merging."
exit 1
fi

echo "No DoNotMergeYet label present."
8 changes: 5 additions & 3 deletions .github/workflows/00b-web-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@
name: 'Web CI'

on:
# Run on EVERY PR to main (no paths filter) so this can be a required
# status check without leaving non-web PRs stuck on "pending". The
# build is ~15s; the cost is negligible. See `.sisyphus/proofs/02-AUDIT-on-main.md` G2.
# Run on EVERY PR to main/next (no paths filter) so this can be a
# required status check without leaving non-web PRs stuck on "pending".
# The build is ~15s; the cost is negligible. See `.sisyphus/proofs/02-AUDIT-on-main.md` G2.
pull_request:
branches:
- main
- next
push:
branches:
- main
- next
paths:
- 'web/**'
- 'repos.yml'
Expand Down
40 changes: 40 additions & 0 deletions .github/workflows/00c-main-release-guard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: main-release-guard

on:
pull_request:
branches:
- main
types:
- opened
- reopened
- synchronize
- edited
- ready_for_review

permissions:
contents: read

jobs:
main-release-guard:
name: main-release-guard
runs-on: ubuntu-latest
steps:
- name: Require repo-owned next as source branch for main
env:
BASE_REF: ${{ github.event.pull_request.base.ref }}
HEAD_REF: ${{ github.event.pull_request.head.ref }}
HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
BASE_REPO: ${{ github.repository }}
run: |
set -euo pipefail
echo "base=$BASE_REF"
echo "head=$HEAD_REF"
echo "head_repo=$HEAD_REPO"
echo "base_repo=$BASE_REPO"

if [ "$BASE_REF" = "main" ] && { [ "$HEAD_REF" != "next" ] || [ "$HEAD_REPO" != "$BASE_REPO" ]; }; then
echo "::error::PRs into main must come from the repo-owned next branch. Retarget feature/chore/fork PRs to next first."
exit 1
fi

echo "main release guard passed: repo-owned next is the source branch."
103 changes: 103 additions & 0 deletions .github/workflows/00d-admin-branch-sync-guard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
name: admin-branch-sync-guard

# Per the protection-stage brief (rule #9): admin/control-plane
# branches must not silently drift behind `main`. This workflow runs
# on every PR that targets `main` from an `admin/*` head branch and
# fails the PR if the head branch is behind main.
#
# Mechanism: GitHub's
# `GET /repos/{owner}/{repo}/compare/{base}...{head}` returns
# `behind_by` — the number of commits in `base` that are not present
# in `head`. Anything > 0 means the admin PR is stale; the operator
# must rebase or use the GitHub UI's "Update branch" button (which
# calls `PUT /repos/{owner}/{repo}/pulls/{pull_number}/update-branch`
# with the documented `expected_head_sha` for the safe path) before
# the PR can merge.
#
# The 00f-sync-next-with-main.yml workflow uses the same
# update-branch API for the `next -> main` release PR flow; this
# workflow is the analogous read-only check for the admin/* PR
# class.

on:
pull_request:
branches:
- main
types:
- opened
- reopened
- synchronize
- ready_for_review
- edited

permissions:
contents: read
pull-requests: read

jobs:
admin-branch-sync-guard:
name: admin-branch-sync-guard
runs-on: ubuntu-latest
steps:
- name: Compare head to base (admin/* only)
# Pass-through for non-admin heads so this check name remains
# available as a required status check on every PR to main —
# required checks that never run leave the PR perpetually
# pending.
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
BASE_REF: ${{ github.event.pull_request.base.ref }}
HEAD_REF: ${{ github.event.pull_request.head.ref }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail

echo "base=${BASE_REF}"
echo "head=${HEAD_REF}"
echo "head_sha=${HEAD_SHA}"

case "${HEAD_REF}" in
admin/*)
echo "Admin/* head detected. Running behind-base check."
;;
*)
echo "Non-admin head (${HEAD_REF}); admin sync guard does not apply. Pass."
exit 0
;;
esac

# `compare/{base}...{head}` per
# https://docs.github.com/en/rest/commits/commits#compare-two-commits
# — `behind_by` counts commits in base that are not in head.
response=$(gh api \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"/repos/${REPO}/compare/${BASE_REF}...${HEAD_SHA}")

behind_by=$(jq -r '.behind_by' <<<"${response}")
ahead_by=$(jq -r '.ahead_by' <<<"${response}")
status=$(jq -r '.status' <<<"${response}")

echo "ahead_by=${ahead_by}"
echo "behind_by=${behind_by}"
echo "status=${status}"

if [ "${behind_by}" -gt 0 ]; then
echo "::error::admin/* PR head is ${behind_by} commit(s) behind ${BASE_REF}. Rebase or use the GitHub UI 'Update branch' button before this PR can merge. The update-branch API takes expected_head_sha=${HEAD_SHA} for the safe path."
exit 1
fi

echo "admin/* PR head is up to date with ${BASE_REF} (ahead_by=${ahead_by}, behind_by=0)."

- name: Sync guard summary
if: always()
run: |
{
echo "# admin-branch-sync-guard"
echo ""
echo "- Trigger: \`pull_request\` to \`main\` from \`admin/*\` head"
echo "- Mechanism: GitHub REST \`GET /repos/{owner}/{repo}/compare/{base}...{head}\`"
echo "- Block condition: \`behind_by > 0\`"
echo "- Unblock: rebase against \`${{ github.event.pull_request.base.ref }}\` or click \"Update branch\" in the PR UI (calls update-branch API with expected_head_sha)"
} >> "$GITHUB_STEP_SUMMARY"
Loading
Loading