Add merge feasibility preflight and stale approval shadow gate - #714
Add merge feasibility preflight and stale approval shadow gate#714justin808 wants to merge 1 commit into
Conversation
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueThanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| puts " require_last_push_approval: #{feasibility.fetch(:require_last_push_approval).nil? ? 'UNKNOWN' : feasibility.fetch(:require_last_push_approval)}" | ||
| puts " permissions.admin: #{feasibility.fetch(:permissions_admin).nil? ? 'UNKNOWN' : feasibility.fetch(:permissions_admin)}" | ||
| puts " enforce_admins: #{feasibility.fetch(:enforce_admins).nil? ? 'UNKNOWN' : feasibility.fetch(:enforce_admins)}" | ||
| admin_bypass_available = feasibility.fetch(:admin_bypass_available) |
There was a problem hiding this comment.
Bug: KeyError crash whenever branch protection can't be fully resolved.
feasibility.fetch(:admin_bypass_available) assumes the key always exists, but merge_feasibility_unknown (lines 1532-1560) never includes admin_bypass_available: in the hash it returns. Any of these very common conditions will hit that path and then blow up here with an uncaught KeyError:
- the base branch has no branch protection configured at all (
gh api .../branches/<branch>/protectionreturns 404, whichrun_gh_jsonturns into aStandardErrorcaught atpr-security-preflight:414) required_pull_request_reviewsisnull(protection exists but doesn't require reviews)- any of
required_approving_review_count/dismiss_stale_reviews/require_last_push_approval/enforce_adminsis missing
Since this is a security preflight script, crashing here means the tool aborts entirely instead of degrading to the UNKNOWN status it was designed to report. The only test added (test_pr_targets_report_merge_feasibility_branch_protection_and_stale_approval_defect) only exercises the fully-populated "infeasible" path, so this gap isn't caught.
Fix: add admin_bypass_available: nil to merge_feasibility_unknown's returned hash (or use feasibility[:admin_bypass_available] here instead of .fetch).
| end | ||
|
|
||
| branch_protection = normalize_branch_protection( | ||
| api.call("#{prefix}/branches/#{initial_base_ref}/protection") |
There was a problem hiding this comment.
This breaks nearly every existing test in autonomous-merge-evidence-test.rb.
This adds an unconditional api.call("#{prefix}/branches/#{initial_base_ref}/protection") inside collect, but the test doubles in skills/pr-batch/bin/autonomous-merge-evidence-test.rb (not touched by this PR) were never updated to stub that path. Both the shared complete_api helper and most individual test lambdas end their case with else raise "unexpected API path #{path}".
Since this new call happens after the head/base-movement check but before the later checks (final_force_push_watermark, changed_files/commits equality, commit SHAs must be unique, and all of normalize_file/normalize_review/normalize_comment, which only run when building the final returned hash), essentially every test whose expected CollectionError is raised at or after that point — including the plain happy-path test test_collects_every_page_and_rechecks_exact_head_and_base — will instead hit the unstubbed branch and raise a bare RuntimeError, which assert_raises(AutonomousMergeEvidence::CollectionError) won't catch (and the happy-path test isn't expecting a raise at all).
The PR's own "Checks" list only runs specific test names via -n on other files and never runs autonomous-merge-evidence-test.rb, which is presumably how this slipped through. Please add a branches/.../protection stub to the shared fakes in that file (and ideally a case for a repo with no branch protection configured, i.e. a 404, since that's a very common real-world state).
| eligible_distinct_approver_logins.uniq! | ||
| eligible_distinct_approver_logins.sort! | ||
| stale_approving_reviews = reviews.select do |review| | ||
| review["state"] == "APPROVED" && review["commit_id"].is_a?(String) && review["commit_id"] != head_sha |
There was a problem hiding this comment.
Minor consistency nit: this stale-approval comparison (and the current-head-approval one at line 1636) compares commit_id to head_sha case-sensitively, whereas the equivalent shadow-gate logic in autonomous-merge-eligibility (line 456) downcases both sides before comparing. GitHub SHAs are conventionally already lowercase so this likely isn't triggering in practice, but the two nearly-identical "is this approval stale" implementations should probably share the same normalization (or better, share the same code) so they can't silently drift.
Review summaryFocused review of the merge-feasibility preflight / stale-approval shadow gate. Left inline comments with details; the two bugs below are the ones worth blocking on. 1. 2. The 3. (minor) Stale-approval "is this commit_id the head?" comparisons are case-sensitive in Design-wise, making the new |
|
🤖 Codex Closing this stale implementation so issue #477 can be rebuilt narrowly from current The branch is 77 commits behind, conflicts across eight of its nine paths, and its Validate run was cancelled. Two unresolved review findings are consequential: an ordinary Issue #477 already preserves the replacement requirements: per-target typed feasible/infeasible/ The remote branch remains available as reference. Closing this PR does not close #477 or authorize a replacement merge. |
References #477
deferred_to_update_changelog
Rationale:
stale-approval-satisfiable) toautonomous-merge-eligibilitywhen stale approvals can still satisfy branch protection.Checks:
ruby skills/pr-batch/bin/pr-security-preflight-test.rb -n test_pr_targets_report_merge_feasibility_branch_protection_and_stale_approval_defectruby skills/pr-batch/bin/autonomous-merge-eligibility-test.rb -n test_stale_approval_satisfiable_is_reported_as_a_shadow_gateruby skills/pr-batch/bin/autonomous-merge-eligibility-test.rb -n test_live_collection_returns_a_verdict_with_non_ascii_payload_in_a_c_localeruby skills/pr-batch/bin/autonomous-merge-closeout-test.rb -n test_shadow_gate_signals_render_the_stale_approval_advisoryruby skills/pr-batch/bin/merge-assurance-test.rb -n test_autonomous_stale_approval_shadow_gate_survives_the_receipt_pathgit diff --checkbin/lint(rubocop and shellcheck passed; markdownlint passed; the command stopped only becauseyamllintis not installed in this environment)Residual:
skills/plan-pr-batch/SKILL.mdandworkflows/pr-processing.mdstill does not carry an explicitexpected-terminalfield. That is the narrow follow-up surface.