fix(merge gate): an unknown head commit never merges; pr_merge refuses closed and draft PRs (review of PR #76)
Review found a bypass: a PR reporting an empty head sha made the runs query lose its head_sha filter (the repo's 50 most recent runs of any commit were classified) and dropped head_commit_id (omitempty), so the merge was unbound. Both tools now go through evaluateMergeGate, which refuses without a head sha, and pollRuns makes no query without one. pr_merge also reports merged:false for a PR that is not open or is a draft (Gitea refuses closed ones itself; the gate no longer depends on that). Tests: unit (gate, counting-server pollRuns with positive control), end to end for both tools, CI scoped to head_sha, closed/merged/draft. The two caller-wiring mutants are equivalent to the pollRuns guard and survive by design. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SZkhi49hfeSqBp5aZJNXsM
This commit is contained in:
@@ -77,13 +77,21 @@ func (t *PRMerge) Call(ctx context.Context, raw json.RawMessage) (json.RawMessag
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read PR #%d: %w", args.Number, err)
|
||||
}
|
||||
// Gitea refuses closed and merged PRs itself; the gate does not depend on that.
|
||||
if pr.State != "open" || pr.Draft {
|
||||
reason := fmt.Sprintf("the PR is not open (state %q) — nothing to merge", pr.State)
|
||||
if pr.State == "open" {
|
||||
reason = "the PR is a draft — not merged"
|
||||
}
|
||||
return textOK(map[string]any{"merged": false, "pr_number": args.Number, "reason": reason})
|
||||
}
|
||||
bp, err := t.c.GetBranchProtection(ctx, args.Owner, args.Repo, pr.Base.Ref)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("branch protection probe: %w", err)
|
||||
}
|
||||
runs := pollRuns(ctx, t.c, args.Owner, args.Repo, pr.Head.Sha, args.CITimeoutSec)
|
||||
risk := riskTierOf(ctx, t.c, args.Owner, args.Repo, args.Issue)
|
||||
gate := evaluateShipGate(runs, bp, risk)
|
||||
gate := evaluateMergeGate(pr.Head.Sha, runs, bp, risk)
|
||||
|
||||
result := map[string]any{
|
||||
"merged": false,
|
||||
|
||||
Reference in New Issue
Block a user