pr_merge is an independent merge path with no risk-tier gate (follow-up to #63) #74

Closed
opened 2026-10-07 17:57:31 +00:00 by mathias · 1 comment
Owner

Source: adversarial review of PR #73 (the tbd_ship tier gate), 2026-10-07.

#63/#73 gates tbd_ship only. internal/tools/pr_merge.go:67 merges any PR it is handed, with no CI, protection or risk-tier check, so an agent that wants to merge a risk/medium change can still do it by calling pr_merge directly. Local-dev rule 5's reviewed lane therefore stays a convention for that path.

Decide (Mathias): (a) gate pr_merge on the same tier + green CI (needs an issue or a label on the PR, since a PR number alone carries no classification), (b) remove pr_merge from the agent-facing tool set and keep merging a human act, or (c) accept and state the limit in assessor-loop §5 (done by local-dev#27's text). Also open: branch protection on main (local-dev#27 option 2), which would hold for every tool.

Related: gitea-mcp#63, PR #73, local-dev#27.


Created via git-mcp on behalf of @mathias

Source: adversarial review of PR #73 (the tbd_ship tier gate), 2026-10-07. #63/#73 gates `tbd_ship` only. `internal/tools/pr_merge.go:67` merges any PR it is handed, with no CI, protection or risk-tier check, so an agent that wants to merge a `risk/medium` change can still do it by calling `pr_merge` directly. Local-dev rule 5's reviewed lane therefore stays a convention for that path. Decide (Mathias): (a) gate `pr_merge` on the same tier + green CI (needs an `issue` or a label on the PR, since a PR number alone carries no classification), (b) remove `pr_merge` from the agent-facing tool set and keep merging a human act, or (c) accept and state the limit in assessor-loop §5 (done by local-dev#27's text). Also open: branch protection on `main` (local-dev#27 option 2), which would hold for every tool. Related: gitea-mcp#63, PR #73, local-dev#27. --- _Created via git-mcp on behalf of @mathias_
Author
Owner

Done and live 2026-10-08 (PR #76, squash d1e501c, tag v0.18.0). Mathias decided: gate pr_merge.

  • What: pr_merge runs the same gate as tbd_ship (shared evaluateMergeGate): CI fully green on the PR's head commit, base not review-protected, exactly one risk/low label on the issue named in issue; otherwise merged:false with reason, ci_status, risk. Not-open and draft PRs are refused. An unreadable PR or protection probe is an error; a merge conflict still is. Both tools now bind the merge to the evaluated head commit (head_commit_id, present in Gitea 1.27.1's MergePullRequestOption) so a push after the check cannot slip in.
  • Adversarial review, 2 rounds: round 1 found a real bypass (an empty head sha unscoped the CI query and dropped the merge binding, also affecting the already-live tbd_ship); fixed with evaluateMergeGate + a pollRuns guard + tests; round 2 PASS. Two caller-wiring mutants survive as equivalent (documented). Whitespace-only sha is not rejected (not a bypass).
  • Deployed: infra pinned localhost:5000/gitea-mcp:d1e501c; rollout 1/1, 0 restarts; rollback target d3faffc.
  • Verified live through the production MCP: pr_merge descriptor now has issue and ci_timeout_seconds and mentions risk/low. On a throwaway PR (#78, green CI, closed, branch deleted, nothing on main): issue=63 (risk/medium) → merged:false, ci_status:success, risk:medium; no issue → merged:false, risk:none; again with squash → refused; the PR stayed open and unmerged.
  • Not live-tested: the positive case (a risk/low issue merging) and Gitea's server-side refusal of a moved head.
  • Remaining, tracked: UI merges and file_write_branch commits to main are outside the gate; the platform-level control is branch protection on main (decision filed in local-dev). merge_message_title was never a Gitea field → #77.
  • Behaviour change: pr_merge without issue, or on a PR without green CI (or in a repo with no CI), merges nothing; merge those in the Gitea UI.

Created via git-mcp on behalf of @mathias

Done and live 2026-10-08 (PR #76, squash d1e501c, tag v0.18.0). Mathias decided: gate `pr_merge`. - **What:** `pr_merge` runs the same gate as `tbd_ship` (shared `evaluateMergeGate`): CI fully green on the PR's head commit, base not review-protected, exactly one `risk/low` label on the issue named in `issue`; otherwise `merged:false` with `reason`, `ci_status`, `risk`. Not-open and draft PRs are refused. An unreadable PR or protection probe is an error; a merge conflict still is. Both tools now bind the merge to the evaluated head commit (`head_commit_id`, present in Gitea 1.27.1's `MergePullRequestOption`) so a push after the check cannot slip in. - **Adversarial review, 2 rounds:** round 1 found a real bypass (an empty head sha unscoped the CI query and dropped the merge binding, also affecting the already-live `tbd_ship`); fixed with `evaluateMergeGate` + a `pollRuns` guard + tests; round 2 PASS. Two caller-wiring mutants survive as equivalent (documented). Whitespace-only sha is not rejected (not a bypass). - **Deployed:** infra pinned `localhost:5000/gitea-mcp:d1e501c`; rollout 1/1, 0 restarts; rollback target `d3faffc`. - **Verified live through the production MCP:** `pr_merge` descriptor now has `issue` and `ci_timeout_seconds` and mentions risk/low. On a throwaway PR (#78, green CI, closed, branch deleted, nothing on main): `issue=63` (`risk/medium`) → `merged:false, ci_status:success, risk:medium`; no issue → `merged:false, risk:none`; again with squash → refused; the PR stayed open and unmerged. - **Not live-tested:** the positive case (a `risk/low` issue merging) and Gitea's server-side refusal of a moved head. - **Remaining, tracked:** UI merges and `file_write_branch` commits to `main` are outside the gate; the platform-level control is branch protection on `main` (decision filed in local-dev). `merge_message_title` was never a Gitea field → #77. - **Behaviour change:** `pr_merge` without `issue`, or on a PR without green CI (or in a repo with no CI), merges nothing; merge those in the Gitea UI. --- _Created via git-mcp on behalf of @mathias_
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mathias/gitea-mcp#74