Skip to content

ci: add main branch protection ruleset and docs #405 - #529

Open
xtep103 wants to merge 1 commit into
Stellar-Ecosystem:mainfrom
xtep103:fix/branch-protection-main
Open

ci: add main branch protection ruleset and docs #405#529
xtep103 wants to merge 1 commit into
Stellar-Ecosystem:mainfrom
xtep103:fix/branch-protection-main

Conversation

@xtep103

@xtep103 xtep103 commented Aug 1, 2026

Copy link
Copy Markdown

Overview

This PR enables durable, reviewable branch protection for main by checking in a GitHub repository ruleset that requires all four CI jobs, one approving review, and up-to-date branches — and blocks force pushes — plus docs so contributors know the merge gate is real.

Related Issue

Closes #405

Changes

🔒 Main Branch Protection

  • [ADD] .github/rulesets/main-protection.json

    • Targets main / default branch with enforcement: active.
    • Requires all four CI status checks: Contract build & test, Backend test, Frontend type-check & build, Agent test.
    • Requires at least one approving pull request review.
    • Enforces strict up-to-date branches before merge.
    • Blocks force pushes (non_fast_forward) and branch deletion.
  • [ADD] scripts/apply-main-branch-protection.sh

    • Creates or updates the Protect main ruleset via the GitHub API (gh).
    • Verifies active rules on main after apply.
    • Requires repository admin access (maintainer one-liner).
  • [MODIFY] CONTRIBUTING.md

    • Removes the "planned follow-up" note for branch protection.
    • Documents required checks, review, up-to-date policy, and the apply script.

Verification Results

Ruleset JSON validation:
✅ Valid JSON
✅ All 4 CI job contexts required
✅ required_approving_review_count = 1
✅ strict_required_status_checks_policy = true
✅ non_fast_forward (+ deletion) present

Apply attempt (contributor account):
⚠️ Creating ruleset on Stellar-Ecosystem/lodestar → HTTP 404 (no admin)
✅ Confirms only maintainers can activate the ruleset

Maintainer apply (required to finish acceptance):
./scripts/apply-main-branch-protection.sh
Acceptance Criteria Status
main requires all CI jobs to pass before merge ✅ Encoded in ruleset; ⚠️ maintainer must run apply script
At least one approving review is required ✅ Encoded (required_approving_review_count: 1); ⚠️ apply script
Force pushes to main are blocked ✅ Encoded (non_fast_forward); ⚠️ apply script
CONTRIBUTING updated to remove the "planned" note ✅ Done

Maintainer action required

Because repository rulesets cannot be created without admin rights, please run once after merge (or before):

./scripts/apply-main-branch-protection.sh

Then confirm with:

gh api repos/Stellar-Ecosystem/lodestar/rules/branches/main

Summary by CodeRabbit

  • Chores

    • Added protection requirements for the main branch, including one approval and four passing CI checks.
    • Prevented force pushes, non-fast-forward updates, and branch deletion.
    • Added tooling to apply or refresh branch protection settings and verify they are active.
  • Documentation

    • Updated contribution guidance with merge requirements and branch protection details.

Encode required CI checks, review, and no-force-push rules for main,
and document how maintainers apply the ruleset. Closes Stellar-Ecosystem#405.
@xtep103
xtep103 requested a review from ritik4ever as a code owner August 1, 2026 01:31
@drips-wave

drips-wave Bot commented Aug 1, 2026

Copy link
Copy Markdown

@xtep103 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The pull request adds an active Protect main ruleset, a Bash script to apply and verify it through the GitHub API, and documentation for the required merge conditions.

Changes

Main branch protection

Layer / File(s) Summary
Ruleset definition and validation
.github/rulesets/main-protection.json, scripts/apply-main-branch-protection.sh
Defines pull-request approval, four required status checks, strict checks, non-fast-forward protection, and deletion protection. The script validates the GitHub CLI and ruleset file.
Ruleset application and verification
scripts/apply-main-branch-protection.sh
Updates an existing Protect main ruleset or creates it through the GitHub API. It verifies the active rules for main.
Contributor guidance
CONTRIBUTING.md
Documents the merge requirements and the command to apply or refresh the ruleset.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Maintainer
  participant apply-main-branch-protection.sh
  participant GitHub API
  Maintainer->>apply-main-branch-protection.sh: Run the protection command
  apply-main-branch-protection.sh->>GitHub API: Find Protect main ruleset
  GitHub API-->>apply-main-branch-protection.sh: Return ruleset status
  apply-main-branch-protection.sh->>GitHub API: Update or create ruleset
  apply-main-branch-protection.sh->>GitHub API: Verify active main branch rules
  GitHub API-->>apply-main-branch-protection.sh: Return checks and review settings
Loading

Suggested reviewers: ritik4ever

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main branch protection ruleset and documentation changes.
Linked Issues check ✅ Passed The ruleset and documentation satisfy issue #405 by requiring four CI checks, one approval, up-to-date branches, and blocking force pushes.
Out of Scope Changes check ✅ Passed The ruleset, application script, and documentation directly support the linked issue's branch protection objectives.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/apply-main-branch-protection.sh`:
- Around line 21-25: Update the ruleset lookup assigned to EXISTING_ID to
request all pages from the repos/${OWNER_REPO}/rulesets API, ensuring matches
beyond the first page are found. Remove the error suppression and || true so gh
API lookup failures propagate and stop the script instead of falling through to
creation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d9059d11-5ffa-4068-92a3-51d9ea074862

📥 Commits

Reviewing files that changed from the base of the PR and between 0546692 and 7b0cf67.

📒 Files selected for processing (3)
  • .github/rulesets/main-protection.json
  • CONTRIBUTING.md
  • scripts/apply-main-branch-protection.sh

Comment on lines +21 to +25
EXISTING_ID="$(
gh api "repos/${OWNER_REPO}/rulesets" --jq \
".[] | select(.name == \"${RULESET_NAME}\") | .id" \
2>/dev/null || true
)"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

OWNER_REPO="${OWNER_REPO:-Stellar-Ecosystem/lodestar}"
gh api --paginate "repos/${OWNER_REPO}/rulesets?per_page=100" --jq \
  '.[] | select(.name == "Protect main") | .id'

Repository: Stellar-Ecosystem/lodestar

Length of output: 164


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "Tracked file:"
git ls-files | grep -Fx 'scripts/apply-main-branch-protection.sh' || true

if [ -f scripts/apply-main-branch-protection.sh ]; then
  echo "--- file outline/stats ---"
  wc -l scripts/apply-main-branch-protection.sh
  echo "--- relevant script section ---"
  cat -n scripts/apply-main-branch-protection.sh | sed -n '1,140p'
  
  echo "--- gh docs/options hints (local gh only) ---"
  gh help api 2>/dev/null | sed -n '1,120p' || true
  gh api -h 2>/dev/null | sed -n '1,120p' || true
fi

echo "--- repository ruleset count/page metadata ---"
OWNER_REPO="${OWNER_REPO:-Stellar-Ecosystem/lodestar}"
python3 - <<'PY' "$OWNER_REPO"
import subprocess, sys, json
repo=sys.argv[1]
for path in ["repos/%s/rulesets?per_page=2" % repo, "repos/%s/rulesets?per_page=30" % repo, "repos/%s/rulesets?per_page=100" % repo]:
    out=subprocess.run(["gh","api",path], capture_output=True, text=True)
    print("##", path)
    print("status:", out.returncode)
    print("headers X-Complete:", out.headers.get("X-GitHub-Media-Type") or out.headers.get("", ""), out.headers.get("Link") or "")
    data=json.loads(out.stdout) if out.returncode==0 else None
    if data is None:
        print("json parse failed")
        continue
    print("count:", len(data), "names:", [d.get("name") for d in data])
PY

Repository: Stellar-Ecosystem/lodestar

Length of output: 14912


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail
OWNER_REPO="${OWNER_REPO:-Stellar-Ecosystem/lodestar}"

for per_page in 1 2 30 31 100; do
  echo "## rulesets?per_page=${per_page}"
  gh api "repos/${OWNER_REPO}/rulesets?per_page=${per_page}" \
    --include --jq \
      '{total_count: length, page_count: (.headers | keys | join(",")), first_name: .body[0].name, first_id: .body[0].id}' \
    2>/tmp/coderabbit-${per_page}.stderr || true
  if [ -s /tmp/coderabbit-${per_page}.stderr ]; then
    sed -n '1,80p' /tmp/coderabbit-${per_page}.stderr
  fi
done

echo "--- all pages names ---"
gh api --paginate "repos/${OWNER_REPO}/rulesets?per_page=2" --jq '.[] | "\(.id)=\(.name)"' | \
  sort -n -t= -k1

Repository: Stellar-Ecosystem/lodestar

Length of output: 7102


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail
OWNER_REPO="${OWNER_REPO:-Stellar-Ecosystem/lodestar}"

echo "--- rulesets list full response counts ---"
python3 - <<'PY' "$OWNER_REPO"
import subprocess, json, sys
repo=sys.argv[1]
resp=subprocess.run(["gh","api","repos/"+repo+"/rulesets?per_page=2"], text=True,capture_output=True)
data=json.loads(resp.stdout) if resp.returncode==0 else []
print("status", resp.returncode)
print("full length", len(data))
for i, item in enumerate(data[:5], 1):
    print(i, item.get("id"), item.get("name"))
PY

echo "--- one page with include headers ---"
gh api repos/${OWNER_REPO}/rulesets?per_page=1 --include --jq '.body' 2>&1 | sed -n '1,120p'

echo "--- jq predicate with include output ---"
gh api repos/${OWNER_REPO}/rulesets?per_page=1 --include --jq '
  .body as $body |
  .headers["Content-Type"] as $ct |
  {headers_len: (.headers|length), ruleset_count: ($body|length), jq_matched_ids: ([$body[] | select(.name=="Protect main") | .id])}
' 2>&1 | sed -n '1,120p'

Repository: Stellar-Ecosystem/lodestar

Length of output: 1627


🌐 Web query:

GitHub REST API List repository rulesets default per_page pagination

💡 Result:

When listing repository rulesets using the GitHub REST API (specifically the GET /repos/{owner}/{repo}/rulesets endpoint), the per_page parameter is used to control the number of results returned per page [1][2][3]. The default value for per_page is 30, and the maximum allowed value is 100 [1][2][3]. You can manage the pagination of results by using the per_page and page query parameters. If you need to retrieve more data, you should use the link header provided in the API response, which contains the URLs for subsequent pages of results [4].

Citations:


Paginate the ruleset lookup and stop on lookup failures.

repos/{owner}/{repo}/rulesets is paginated, so a matching ruleset beyond the first 30 results can make this script POST instead of PUT. 2>/dev/null || true also converts API failures into a create operation; let lookup failures exit instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/apply-main-branch-protection.sh` around lines 21 - 25, Update the
ruleset lookup assigned to EXISTING_ID to request all pages from the
repos/${OWNER_REPO}/rulesets API, ensuring matches beyond the first page are
found. Remove the error suppression and || true so gh API lookup failures
propagate and stop the script instead of falling through to creation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CI: no branch protection on main

1 participant