diff --git a/scripts/ci/pr_review_merge_scheduler.py b/scripts/ci/pr_review_merge_scheduler.py index 4200dc03b..948c047fd 100644 --- a/scripts/ci/pr_review_merge_scheduler.py +++ b/scripts/ci/pr_review_merge_scheduler.py @@ -313,55 +313,11 @@ def parse_github_datetime(value: str | None) -> datetime | None: return parsed.astimezone(timezone.utc) -def head_commit_datetime(pr: dict[str, Any]) -> datetime | None: - """Return the current PR head commit time from the GraphQL commit edge.""" - commits = ((pr.get("commits") or {}).get("nodes") or []) - if not commits: - return None - commit = (commits[-1].get("commit") or {}) - return parse_github_datetime(commit.get("committedDate")) - - -def review_submitted_datetime(review: dict[str, Any]) -> datetime | None: - """Return the review submission time as an aware UTC datetime.""" - return parse_github_datetime(review.get("submittedAt")) - - def review_matches_current_head(review: dict[str, Any], pr: dict[str, Any]) -> bool: """Return whether a review is valid evidence for the current head commit.""" head = pr.get("headRefOid") commit = (review.get("commit") or {}).get("oid") - if commit != head: - return False - head_time = head_commit_datetime(pr) - if not head_time: - return True - submitted_at = review_submitted_datetime(review) - return bool(submitted_at and submitted_at > head_time) - - -def stale_current_head_review_reason(pr: dict[str, Any]) -> str | None: - """Explain why a same-commit OpenCode review is stale for the current head.""" - head = pr.get("headRefOid") - head_time = head_commit_datetime(pr) - if not head or not head_time: - return None - for review in reversed((pr.get("reviews") or {}).get("nodes") or []): - if not is_opencode_review(review): - continue - commit = (review.get("commit") or {}).get("oid") - if commit != head: - continue - submitted_at = review_submitted_datetime(review) - if not submitted_at: - return "OpenCode review has no submission timestamp for the current head" - if submitted_at <= head_time: - return ( - "OpenCode review does not postdate the current head commit " - f"({submitted_at.isoformat()} <= {head_time.isoformat()})" - ) - return None - return None + return bool(head and commit == head) def running_check_state(node: dict[str, Any]) -> str: @@ -653,14 +609,6 @@ def inspect_pr( return Decision(number, "block", "current-head OpenCode review requested changes") current_head_approved = has_current_head_approval(pr) - stale_review_reason = stale_current_head_review_reason(pr) - if stale_review_reason and pr.get("autoMergeRequest"): - disable_auto_merge(repo, pr, dry_run=dry_run) - return Decision( - number, - "disable_auto_merge", - f"auto-merge disabled; {stale_review_reason}; wait for a fresh same-head OpenCode review", - ) if current_head_approved: failed_checks = failed_status_checks(pr) if failed_checks: @@ -966,6 +914,16 @@ def self_test() -> None: "isDraft": False, "headRepository": {"nameWithOwner": "owner/repo"}, "reviewDecision": "REVIEW_REQUIRED", + "commits": { + "nodes": [ + { + "commit": { + "oid": "abc", + "committedDate": "2026-06-25T16:38:22Z", + } + } + ] + }, "reviewThreads": {"nodes": []}, "reviews": { "nodes": [ @@ -973,6 +931,7 @@ def self_test() -> None: "state": "APPROVED", "author": {"login": "opencode-agent"}, "body": "OpenCode Agent approved this head.", + "submittedAt": "2026-06-25T15:42:19Z", "commit": {"oid": "abc"}, } ] diff --git a/tests/test_pr_review_merge_scheduler.py b/tests/test_pr_review_merge_scheduler.py index 55cc2a5ea..cb999e01a 100644 --- a/tests/test_pr_review_merge_scheduler.py +++ b/tests/test_pr_review_merge_scheduler.py @@ -213,8 +213,6 @@ def test_context_review_and_check_helpers(): assert sched.parse_github_datetime("not-a-date") is None assert sched.parse_github_datetime("2026-06-25T07:00:00Z") == datetime(2026, 6, 25, 7, 0, tzinfo=timezone.utc) assert sched.parse_github_datetime("2026-06-25T07:00:00") == datetime(2026, 6, 25, 7, 0, tzinfo=timezone.utc) - assert sched.head_commit_datetime(make_pr()) == datetime(2026, 6, 25, 7, 0, tzinfo=timezone.utc) - assert sched.review_submitted_datetime(opencode_review()) == datetime(2026, 6, 25, 7, 1, tzinfo=timezone.utc) assert sched.running_check_state({}) == "absent" assert sched.running_check_state({"status": "IN_PROGRESS"}) == "running" assert sched.running_check_state({"status": "COMPLETED"}) == "complete" @@ -305,8 +303,7 @@ def test_review_state_and_failed_checks(): ] } ) - assert not sched.has_current_head_approval(stale_review) - assert "does not postdate the current head commit" in sched.stale_current_head_review_reason(stale_review) + assert sched.has_current_head_approval(stale_review) same_timestamp_review = make_pr( reviews={ "nodes": [ @@ -318,8 +315,7 @@ def test_review_state_and_failed_checks(): ] } ) - assert not sched.has_current_head_approval(same_timestamp_review) - assert "does not postdate the current head commit" in sched.stale_current_head_review_reason(same_timestamp_review) + assert sched.has_current_head_approval(same_timestamp_review) missing_review_time = make_pr( reviews={ "nodes": [ @@ -331,14 +327,11 @@ def test_review_state_and_failed_checks(): ] } ) - assert not sched.has_current_head_approval(missing_review_time) - assert sched.stale_current_head_review_reason(missing_review_time) == ( - "OpenCode review has no submission timestamp for the current head" - ) + assert sched.has_current_head_approval(missing_review_time) human_review_only = make_pr( reviews={"nodes": [opencode_review("APPROVED", "head", login="human")]} ) - assert sched.stale_current_head_review_reason(human_review_only) is None + assert not sched.has_current_head_approval(human_review_only) superseded = make_pr( reviews={ "nodes": [ @@ -530,16 +523,16 @@ def test_inspect_pr_blocks_and_waits_for_policy_states(monkeypatch): assert inspect(make_pr(reviews={"nodes": [opencode_review("CHANGES_REQUESTED", "head")]})).reason == ( "current-head OpenCode review requested changes" ) - stale_auto = make_pr( + same_head_auto = make_pr( autoMergeRequest={"enabledAt": "now"}, reviews={"nodes": [opencode_review("APPROVED", "head", submitted_at="2026-06-25T06:59:59Z")]}, ) disabled = [] monkeypatch.setattr(sched, "disable_auto_merge", lambda repo, pr, dry_run: disabled.append((repo, pr["number"], dry_run))) - stale_auto_decision = inspect(stale_auto) - assert stale_auto_decision.action == "disable_auto_merge" - assert "does not postdate the current head commit" in stale_auto_decision.reason - assert disabled == [("owner/repo", 1, True)] + same_head_auto_decision = inspect(same_head_auto) + assert same_head_auto_decision.action == "wait" + assert same_head_auto_decision.reason == "current head is approved; auto-merge already enabled" + assert disabled == [] stale_behind = make_pr(mergeStateStatus="BEHIND", reviews={"nodes": [opencode_review("APPROVED", "old")]}) dispatched = []