forgejo-mcp/openspec/specs/branch-protection/spec.md
Christoph Görn 88cd866d0f
docs: 📝 archive branch-protection-management + co-locate demo (uc6)
Completes the OpenSpec lifecycle for the implementation merged in #196:
- openspec archive branch-protection-management -> live spec
  openspec/specs/branch-protection/spec.md (7 requirements).
- Change moved to changes/archive/2026-06-02-branch-protection-management/.
- Showboat demo moved next to the live spec
  (openspec/specs/branch-protection/branch-protection.demo.md); spec link fixed.

openspec validate --all --strict: 17/17. check-demos: 0 errors.

Closes forgejo-mcp-uc6.
2026-06-02 16:37:06 +02:00

104 lines
5.9 KiB
Markdown

# branch-protection Specification
## Purpose
TBD - created by archiving change branch-protection-management. Update Purpose after archive.
## Requirements
### Requirement: List branch protection rules (bounded)
The server SHALL provide a `list_branch_protections` tool that returns the branch protection rules of a repository given `owner` and `repo`. The tool SHALL expose caller-controlled `page` and `limit` bounds and the response SHALL be resumable by echoing the page that was returned, satisfying `docs/design/output-bounding.md`.
#### Scenario: List returns the repository's rules
- **WHEN** `list_branch_protections` is called with a valid `owner` and `repo`
- **THEN** the result SHALL contain the repository's branch protection rules as returned by `GET /repos/{owner}/{repo}/branch_protections`
- **AND** each rule SHALL include at least its `rule_name`, `branch_name`, `enable_status_check`, `status_check_contexts`, and `required_approvals`
#### Scenario: Caller bounds the page size
- **WHEN** `list_branch_protections` is called with `limit` set to N
- **THEN** the request to Forgejo SHALL carry a page size of N
- **AND** the response SHALL indicate the page returned so the caller can request the next page
### Requirement: Get a branch protection rule
The server SHALL provide a `get_branch_protection` tool that returns a single rule given `owner`, `repo`, and the rule identifier (`rule`).
#### Scenario: Existing rule is returned
- **WHEN** `get_branch_protection` is called with an `owner`, `repo`, and a `rule` that exists
- **THEN** the result SHALL contain that rule's full protection state
#### Scenario: Missing rule yields an error
- **WHEN** `get_branch_protection` is called with a `rule` that does not exist
- **THEN** the tool SHALL return an error result rather than an empty success
### Requirement: Create a branch protection rule
The server SHALL provide a `create_branch_protection` tool that creates a rule via `POST /repos/{owner}/{repo}/branch_protections`. The tool SHALL require `owner`, `repo`, and `branch_name`; `rule_name` SHALL be optional (Forgejo defaults it to `branch_name`). The tool SHALL round-trip `status_check_contexts` exactly.
#### Scenario: Create enforces status checks
- **WHEN** `create_branch_protection` is called with `enable_status_check` true and `status_check_contexts` set to a list of contexts
- **THEN** the request body sent to Forgejo SHALL contain `enable_status_check: true` and the exact `status_check_contexts` list
- **AND** the result SHALL reflect the created rule with those contexts
#### Scenario: Create requires a branch name
- **WHEN** `create_branch_protection` is called without `branch_name`
- **THEN** the tool SHALL return an error result and SHALL NOT call Forgejo
### Requirement: Edit a branch protection rule with PATCH semantics
The server SHALL provide an `edit_branch_protection` tool that updates a rule via `PATCH /repos/{owner}/{repo}/branch_protections/{rule}`. The tool SHALL only send fields the caller explicitly provides; fields the caller omits SHALL be left unchanged on the server.
#### Scenario: Editing one field leaves others untouched
- **WHEN** `edit_branch_protection` is called with only `required_approvals` set to 2
- **THEN** the request body sent to Forgejo SHALL set `required_approvals` to 2
- **AND** the other protection fields the caller did not pass SHALL be sent as `null` (leave-unchanged), never as a concrete value such as `false` that would silently relax protection
#### Scenario: Editing status checks round-trips the contexts
- **WHEN** `edit_branch_protection` is called with `status_check_contexts` set to a list
- **THEN** the request body SHALL contain that exact list
### Requirement: Delete a branch protection rule
The server SHALL provide a `delete_branch_protection` tool that removes a rule via `DELETE /repos/{owner}/{repo}/branch_protections/{rule}` and reports success.
#### Scenario: Existing rule is deleted
- **WHEN** `delete_branch_protection` is called for a rule that exists
- **THEN** Forgejo SHALL receive a delete request for that rule
- **AND** the tool SHALL return a success result confirming removal
### Requirement: Branch protections collection resource (bounded)
The server SHALL expose a resource-template `forgejo://repo/{owner}/{repo}/branch_protections` that returns the repository's protection rules as a read-only JSON document. The embedded list SHALL be bounded at `EmbeddedListCap` using the shared `resource.Bounded` helper, and when truncated SHALL carry the truncation sentinel and name `list_branch_protections` as the tool to fetch the remainder.
#### Scenario: Collection resource returns bounded rules
- **WHEN** the resource `forgejo://repo/{owner}/{repo}/branch_protections` is read for a repo with rules
- **THEN** the payload SHALL list the rules up to `EmbeddedListCap`
- **AND** when more than `EmbeddedListCap` rules exist, the payload SHALL set a truncation indicator naming `list_branch_protections`
#### Scenario: Unknown repository maps to a not-found resource error
- **WHEN** the collection resource is read for a repository that does not exist
- **THEN** the handler SHALL return a resource error mapped from the Forgejo 404 response
### Requirement: Single branch protection resource
The server SHALL expose a resource-template `forgejo://repo/{owner}/{repo}/branch_protection/{rule}` that returns one rule's protection state as a read-only JSON document.
#### Scenario: Single rule resource returns its state
- **WHEN** the resource `forgejo://repo/{owner}/{repo}/branch_protection/{rule}` is read for an existing rule
- **THEN** the payload SHALL contain that rule's protection state including `status_check_contexts` and `required_approvals`
#### Scenario: Malformed URI maps to an invalid-params error
- **WHEN** a `forgejo://repo/...` branch-protection URI is read that does not match the template
- **THEN** the handler SHALL return a resource error with the invalid-params code