fix(merge gate): bind pr_merge and tbd_ship merges to the head commit the gate checked (TOCTOU)
CD / Lint / Test / Vet (pull_request) Successful in 12s
CD / Build & Import (pull_request) Skipped
CD / Deploy via GitOps (pull_request) Skipped

The gate evaluates CI for one head sha; a push to the PR branch between that check and the merge would have merged
unchecked code. MergePRArgs now carries head_commit_id (documented in Gitea 1.27.1's MergePullRequestOption) and both
tools send the sha they evaluated. Tests for both tools; each removal flip-tested red.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SZkhi49hfeSqBp5aZJNXsM
This commit is contained in:
2026-10-08 08:12:45 +02:00
co-authored by Claude Sonnet 5.5
parent c3d8401604
commit 6efd309159
6 changed files with 42 additions and 4 deletions
+2
View File
@@ -133,6 +133,8 @@ all fail closed: nothing is merged, the result is `merged:false` with a `reason`
Unclassified is not low. Above `risk/low` a human reviews and merges in the Gitea UI (which this gate does not touch).
`pr_merge` additionally fails with an error, merging nothing, when the PR or the base's branch-protection state cannot
be read. A merge conflict is still an error.
Both tools bind the merge to the head commit whose CI the gate evaluated (`head_commit_id`), so a push to the PR
branch after the check makes Gitea refuse the merge instead of merging unchecked code.
Limits: the gate enforces the tier of the issue the caller names; it cannot prove that issue is the one the change
belongs to, so the link is the caller's claim (local-dev rule 5's convention still applies). Not gated: merging in the