@@ -0,0 +1,120 @@ |
| 1 | +# Rulesets |
| 2 | + |
| 3 | +Read-only rulesets surface. Mirrors GitHub's |
| 4 | +[`/repos/{owner}/{repo}/rulesets`](https://docs.github.com/en/rest/repos/rules) |
| 5 | +response shape. shithub synthesizes ruleset rows from the |
| 6 | +`branch_protection_rules` table — one branch-protection row |
| 7 | +becomes one ruleset, named after its pattern, with each |
| 8 | +configured field projected as a typed rule object. |
| 9 | + |
| 10 | +All endpoints require `Authorization: Bearer <pat>` with |
| 11 | +`repo:read` and gate on `ActionRepoRead`. The |
| 12 | +[common API conventions](overview.md) apply. |
| 13 | + |
| 14 | +## Ruleset shape |
| 15 | + |
| 16 | +```json |
| 17 | +{ |
| 18 | + "id": 17, |
| 19 | + "name": "Pattern: main", |
| 20 | + "target": "branch", |
| 21 | + "source_type": "Repository", |
| 22 | + "source": "alice/demo", |
| 23 | + "enforcement": "active", |
| 24 | + "conditions": { |
| 25 | + "ref_name": { |
| 26 | + "include": ["refs/heads/main"], |
| 27 | + "exclude": [] |
| 28 | + } |
| 29 | + }, |
| 30 | + "rules": [ |
| 31 | + { |
| 32 | + "type": "pull_request", |
| 33 | + "parameters": { |
| 34 | + "required_approving_review_count": 1, |
| 35 | + "dismiss_stale_reviews_on_push": false, |
| 36 | + "require_code_owner_review": true |
| 37 | + } |
| 38 | + }, |
| 39 | + {"type": "non_fast_forward"}, |
| 40 | + {"type": "deletion"}, |
| 41 | + {"type": "required_signatures"}, |
| 42 | + { |
| 43 | + "type": "required_status_checks", |
| 44 | + "parameters": { |
| 45 | + "required_status_checks": [{"context": "ci/build"}], |
| 46 | + "strict_required_status_checks_policy": false |
| 47 | + } |
| 48 | + } |
| 49 | + ], |
| 50 | + "created_at": "2026-05-12T04:00:00Z", |
| 51 | + "updated_at": "2026-05-13T04:00:00Z" |
| 52 | +} |
| 53 | +``` |
| 54 | + |
| 55 | +### Rule types |
| 56 | + |
| 57 | +Each `rules[].type` maps to one or more columns on the underlying |
| 58 | +protection row: |
| 59 | + |
| 60 | +| `type` | Columns | Notes | |
| 61 | +|--------|---------|-------| |
| 62 | +| `pull_request` | `require_pr_for_push`, `required_review_count`, `require_code_owner_review`, `dismiss_stale_reviews_on_push` | Emitted when any of these is non-default. `parameters.required_approving_review_count` is the headline value. | |
| 63 | +| `non_fast_forward` | `prevent_force_push` | Force-push gate. No parameters. | |
| 64 | +| `deletion` | `prevent_deletion` | Branch-delete gate. No parameters. | |
| 65 | +| `required_signatures` | `require_signed_commits` | Verified-signature gate. No parameters. | |
| 66 | +| `required_status_checks` | `status_checks_required`, `dismiss_stale_status_checks_on_push` | `parameters.required_status_checks` is an array of `{context}` objects. `integration_id` is omitted (we don't track per-app integrations). | |
| 67 | + |
| 68 | +Rules an admin hasn't configured are absent from the array — clients should treat missing as "rule not configured" rather than assuming defaults. |
| 69 | + |
| 70 | +### Field notes |
| 71 | + |
| 72 | +- `target` — always `"branch"`. Tag rulesets ship in a future sprint when tag protection lands. |
| 73 | +- `source_type` / `source` — always `"Repository"` / `"<owner>/<repo>"`. Org-scoped and enterprise-scoped rulesets are out of scope until shithub grows enterprise-tier accounts. |
| 74 | +- `enforcement` — always `"active"`. We don't model `"disabled"` or `"evaluate"` modes; every row in the table is enforced by the pre-receive hook. |
| 75 | +- `conditions.ref_name.include` — single-entry array with the pattern prefixed by `refs/heads/`. shithub's patterns use `filepath.Match` semantics (`*` doesn't cross `/`), not gh's fnmatch. |
| 76 | +- `bypass_actors` — not emitted. Bypass / multi-actor exemptions ship in a future sprint. |
| 77 | + |
| 78 | +## List rulesets |
| 79 | + |
| 80 | +``` |
| 81 | +GET /api/v1/repos/{owner}/{repo}/rulesets |
| 82 | +``` |
| 83 | + |
| 84 | +Required scope: `repo:read`. |
| 85 | + |
| 86 | +Returns every ruleset on the repo, ordered by id. Empty repos and repos with no configured protection rules return `[]`. |
| 87 | + |
| 88 | +## Get a single ruleset |
| 89 | + |
| 90 | +``` |
| 91 | +GET /api/v1/repos/{owner}/{repo}/rulesets/{id} |
| 92 | +``` |
| 93 | + |
| 94 | +Required scope: `repo:read`. |
| 95 | + |
| 96 | +Returns the single ruleset whose id matches. 404 when the id doesn't belong to this repo — same status as "doesn't exist" so the response doesn't leak existence across repo boundaries. |
| 97 | + |
| 98 | +## Rules applying to a branch |
| 99 | + |
| 100 | +``` |
| 101 | +GET /api/v1/repos/{owner}/{repo}/rules/branches/{branch} |
| 102 | +``` |
| 103 | + |
| 104 | +Required scope: `repo:read`. |
| 105 | + |
| 106 | +Returns every ruleset whose pattern matches the given branch name. **All** matching patterns are returned, not just the longest-match — the longest-match heuristic shithub's pre-receive enforcer uses is an internal precedence detail, not a contract surface. |
| 107 | + |
| 108 | +Branch names that contain `/` (`feature/x`, `release/v1.0`) are matched against patterns using `filepath.Match`: `*` does not cross path separators, so `release/*` matches `release/v1.0` but not `release/v1.0/sub`. |
| 109 | + |
| 110 | +## Errors |
| 111 | + |
| 112 | +| Status | When | |
| 113 | +|-------:|-------------------------------------------------| |
| 114 | +| 401 | PAT missing/invalid/expired/revoked. | |
| 115 | +| 403 | PAT lacks `repo:read` scope. | |
| 116 | +| 404 | Repo not found, ruleset id not found, or cross-repo lookup. | |
| 117 | + |
| 118 | +## Mutating rulesets |
| 119 | + |
| 120 | +Creating, updating, and deleting rulesets via REST is not yet shipped. Use the web UI at `Settings → Branches → Add rule` for now. The shape of the future POST/PATCH/DELETE endpoints will mirror gh's documented surface and will require `repo:write` + `ActionRepoAdmin`. |