diff --git a/.github/rulesets/README.md b/.github/rulesets/README.md new file mode 100644 index 0000000..56588fe --- /dev/null +++ b/.github/rulesets/README.md @@ -0,0 +1,51 @@ +# Repository Rulesets + +These JSON files are version-controlled copies of the GitHub repository rulesets that protect this repo. They cannot be +applied while the repository is private on a free-tier account — GitHub limits rulesets to public repos or paid (GitHub +Pro / Team / Enterprise) tiers. + +The repo ships **PRIVATE** through v0.1.0 by design (see the master plan). Rulesets must be applied immediately after +the visibility flip, before any post-flip pushes. + +## Files + +| File | Target | Effect | +| ------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `protect-main.json` | `refs/heads/main` | Block direct creation/deletion/force-push; require PR with CODEOWNERS review, signed commits, linear history, and three required status checks: `markdownlint`, `shellcheck`, `guard-docs / check-forbidden-docs`. Squash-only merge. | +| `protect-dev.json` | `refs/heads/dev` | Block deletion/force-push; require signed commits. Light-touch by design — `dev` is the integration branch. | +| `protect-tags.json` | `refs/tags/v*` | Block deletion, force-push (re-tag), and updates of release tags. Tags are immutable historical anchors that the site's `install.json` pins to. | + +## Apply (after visibility flip) + +```bash +gh api repos/brettdavies/agentnative-skill/rulesets -X POST \ + --input .github/rulesets/protect-main.json + +gh api repos/brettdavies/agentnative-skill/rulesets -X POST \ + --input .github/rulesets/protect-dev.json + +gh api repos/brettdavies/agentnative-skill/rulesets -X POST \ + --input .github/rulesets/protect-tags.json +``` + +## Verify + +```bash +gh api repos/brettdavies/agentnative-skill/rulesets --jq '.[].name' +# expected: Protect main, Protect dev, Protect release tags + +# negative test — force-push to main must be refused +git checkout main +git commit --allow-empty -m "test: should be rejected" +git push origin main # expected: refused by ruleset +git reset --hard HEAD~1 # back out the local test commit + +# negative test — re-tagging an existing release tag must be refused +git tag -f v0.1.0 +git push --force origin v0.1.0 # expected: refused by ruleset +``` + +## Bypass + +`actor_id: 5` (`RepositoryRole`) corresponds to repo admins, who retain bypass for emergency hotfixes and the visibility +flip itself. diff --git a/.github/rulesets/protect-dev.json b/.github/rulesets/protect-dev.json new file mode 100644 index 0000000..33bbb1e --- /dev/null +++ b/.github/rulesets/protect-dev.json @@ -0,0 +1,31 @@ +{ + "name": "Protect dev", + "target": "branch", + "enforcement": "active", + "conditions": { + "ref_name": { + "exclude": [], + "include": [ + "refs/heads/dev" + ] + } + }, + "rules": [ + { + "type": "deletion" + }, + { + "type": "non_fast_forward" + }, + { + "type": "required_signatures" + } + ], + "bypass_actors": [ + { + "actor_id": 5, + "actor_type": "RepositoryRole", + "bypass_mode": "always" + } + ] +} diff --git a/.github/rulesets/protect-main.json b/.github/rulesets/protect-main.json new file mode 100644 index 0000000..74f2fc6 --- /dev/null +++ b/.github/rulesets/protect-main.json @@ -0,0 +1,69 @@ +{ + "name": "Protect main", + "target": "branch", + "enforcement": "active", + "conditions": { + "ref_name": { + "exclude": [], + "include": [ + "refs/heads/main" + ] + } + }, + "rules": [ + { + "type": "creation" + }, + { + "type": "deletion" + }, + { + "type": "non_fast_forward" + }, + { + "type": "pull_request", + "parameters": { + "required_approving_review_count": 0, + "dismiss_stale_reviews_on_push": false, + "required_reviewers": [], + "require_code_owner_review": true, + "require_last_push_approval": false, + "required_review_thread_resolution": false, + "allowed_merge_methods": [ + "squash" + ] + } + }, + { + "type": "required_signatures" + }, + { + "type": "required_status_checks", + "parameters": { + "strict_required_status_checks_policy": true, + "do_not_enforce_on_create": false, + "required_status_checks": [ + { + "context": "markdownlint" + }, + { + "context": "shellcheck" + }, + { + "context": "guard-docs / check-forbidden-docs" + } + ] + } + }, + { + "type": "required_linear_history" + } + ], + "bypass_actors": [ + { + "actor_id": 5, + "actor_type": "RepositoryRole", + "bypass_mode": "always" + } + ] +} diff --git a/.github/rulesets/protect-tags.json b/.github/rulesets/protect-tags.json new file mode 100644 index 0000000..9f087b0 --- /dev/null +++ b/.github/rulesets/protect-tags.json @@ -0,0 +1,31 @@ +{ + "name": "Protect release tags", + "target": "tag", + "enforcement": "active", + "conditions": { + "ref_name": { + "exclude": [], + "include": [ + "refs/tags/v*" + ] + } + }, + "rules": [ + { + "type": "deletion" + }, + { + "type": "non_fast_forward" + }, + { + "type": "update" + } + ], + "bypass_actors": [ + { + "actor_id": 5, + "actor_type": "RepositoryRole", + "bypass_mode": "always" + } + ] +}