diff --git a/.github/rulesets/main-protection.json b/.github/rulesets/main-protection.json new file mode 100644 index 000000000..c00c76962 --- /dev/null +++ b/.github/rulesets/main-protection.json @@ -0,0 +1,42 @@ +{ + "name": "ARStack Main - Pull Requests and Naming CI", + "target": "branch", + "enforcement": "active", + "conditions": { + "ref_name": { + "include": [ + "refs/heads/main" + ], + "exclude": [] + } + }, + "rules": [ + { + "type": "deletion" + }, + { + "type": "non_fast_forward" + }, + { + "type": "pull_request", + "parameters": { + "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": "tracked-tree-names" + } + ] + } + } + ] +} diff --git a/docs/MAINTAINER_MAIN_PROTECTION.md b/docs/MAINTAINER_MAIN_PROTECTION.md new file mode 100644 index 000000000..164de098d --- /dev/null +++ b/docs/MAINTAINER_MAIN_PROTECTION.md @@ -0,0 +1,27 @@ +# Protect the public `main` branch + +ARStack's public releases come from reviewed `main`. Keeping merges on PRs protects the working downloads, documentation and tested runtime. + +The recommended main ruleset is checked into the repository as [`.github/rulesets/main-protection.json`](../.github/rulesets/main-protection.json). It prevents branch deletion and force-pushes, requires a pull request, requires resolved review discussions, and requires the universal **Product Naming Guard / tracked-tree-names** check against the latest `main` revision. + +The approving-review count is **zero** for a solo-maintainer open-source repository: the PR and required automated check remain mandatory without requiring an outside reviewer for every documentation change. Runtime and installer changes should also show their relevant C++/Qt/package CI green before merge. + +## Activate (repository administrator) + +Using the [GitHub CLI](https://cli.github.com/) authenticated with repository administration permission: + +```bash +bash scripts/apply_main_ruleset.sh --dry-run +bash scripts/apply_main_ruleset.sh --apply +``` + +The script updates a ruleset of the same name or creates it once, then prints GitHub's resulting ruleset. Alternatively, go to **Settings → Rules → Rulesets → New branch ruleset** and apply the JSON equivalent. + +After activation, confirm: + +1. **Settings → Rules → Rulesets** lists `ARStack Main - Pull Requests and Naming CI` as active. +2. A direct push to `main` is refused and new changes go through PRs. +3. A PR without a successful `tracked-tree-names` check cannot merge. +4. A PR built against an outdated `main` must update before merging. + +**Connector access:** The GitHub integration permits source, PR and CI changes but rejects branch-protection administration (HTTP 403). Merely storing this JSON does not turn GitHub protection on; it must be applied by an authorized administrator. Historical release tags and verified binaries are unaffected. diff --git a/scripts/apply_main_ruleset.sh b/scripts/apply_main_ruleset.sh new file mode 100644 index 000000000..3996d5e31 --- /dev/null +++ b/scripts/apply_main_ruleset.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Apply the reviewed, repo-owned main ruleset using an administrator's GitHub CLI session. +set -euo pipefail +repo='masarray/arstack61850' +name='ARStack Main - Pull Requests and Naming CI' +root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +manifest="$root/.github/rulesets/main-protection.json" +python3 -m json.tool "$manifest" >/dev/null +case "${1:---dry-run}" in + --dry-run) + echo "Ruleset validated. This command has not changed GitHub settings." + echo "To activate as a repository admin: bash scripts/apply_main_ruleset.sh --apply" + exit 0 + ;; + --apply) ;; + *) echo 'Usage: bash scripts/apply_main_ruleset.sh [--dry-run|--apply]' >&2; exit 2;; +esac +command -v gh >/dev/null || { echo 'GitHub CLI is required: https://cli.github.com/' >&2; exit 1; } +gh auth status >/dev/null +existing="$(gh api "repos/$repo/rulesets" --jq '.[] | select(.name == "ARStack Main - Pull Requests and Naming CI") | .id' | head -n1)" +if [ -n "$existing" ]; then + gh api --method PUT "repos/$repo/rulesets/$existing" --input "$manifest" >/dev/null + echo "Updated active main ruleset ID: $existing" +else + gh api --method POST "repos/$repo/rulesets" --input "$manifest" >/dev/null + echo "Created active main ruleset." +fi +gh api "repos/$repo/rulesets" --jq '.[] | select(.name == "ARStack Main - Pull Requests and Naming CI") | {id,name,enforcement}' +echo 'Confirm on GitHub: Settings > Rules > Rulesets'