From 093275b0e4b200405627bae6f45a5666c77d9e86 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 24 Jul 2026 09:08:50 +0900 Subject: [PATCH 01/10] Handle stacked PR retarget re-evaluation Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/pr-review-merge-scheduler.yml | 4 +++- tests/test_required_workflow_queue_contract.py | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-review-merge-scheduler.yml b/.github/workflows/pr-review-merge-scheduler.yml index 4ab0c1b8..4643ff9a 100644 --- a/.github/workflows/pr-review-merge-scheduler.yml +++ b/.github/workflows/pr-review-merge-scheduler.yml @@ -4,7 +4,9 @@ on: push: branches: [main, develop, master] pull_request_target: - types: [opened, synchronize, reopened, ready_for_review, auto_merge_enabled, closed] + # Include retarget/queue/auto-merge state changes so stacked PR chains are + # re-evaluated immediately when merge readiness shifts without a new head push. + types: [opened, synchronize, reopened, edited, ready_for_review, enqueued, dequeued, auto_merge_enabled, auto_merge_disabled, closed] pull_request_review: types: [submitted, dismissed] workflow_run: diff --git a/tests/test_required_workflow_queue_contract.py b/tests/test_required_workflow_queue_contract.py index 8e66277f..56cbabc2 100644 --- a/tests/test_required_workflow_queue_contract.py +++ b/tests/test_required_workflow_queue_contract.py @@ -38,6 +38,15 @@ def test_merge_scheduler_dispatches_one_review_by_default() -> None: ) +def test_merge_scheduler_pull_request_target_includes_retarget_and_queue_events() -> None: + workflow = workflow_text("pr-review-merge-scheduler.yml") + + assert ( + "types: [opened, synchronize, reopened, edited, ready_for_review, enqueued, dequeued, auto_merge_enabled, auto_merge_disabled, closed]" + in workflow + ) + + def test_merge_scheduler_provides_same_repository_dispatch_credential() -> None: """Guard the runner-token dispatch credential for central review workflows. From 77582a4547629a7b06fcf1392e167c644f3f4351 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 24 Jul 2026 09:17:00 +0900 Subject: [PATCH 02/10] Make scheduler trigger contract order-insensitive Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../test_required_workflow_queue_contract.py | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/tests/test_required_workflow_queue_contract.py b/tests/test_required_workflow_queue_contract.py index 56cbabc2..fc91378e 100644 --- a/tests/test_required_workflow_queue_contract.py +++ b/tests/test_required_workflow_queue_contract.py @@ -40,11 +40,28 @@ def test_merge_scheduler_dispatches_one_review_by_default() -> None: def test_merge_scheduler_pull_request_target_includes_retarget_and_queue_events() -> None: workflow = workflow_text("pr-review-merge-scheduler.yml") - - assert ( - "types: [opened, synchronize, reopened, edited, ready_for_review, enqueued, dequeued, auto_merge_enabled, auto_merge_disabled, closed]" - in workflow + trigger_block = workflow.split("pull_request_target:", 1)[1].split( + "pull_request_review:", + 1, + )[0] + types_line = next( + line.strip() for line in trigger_block.splitlines() if line.strip().startswith("types:") ) + listed = types_line.split("[", 1)[1].split("]", 1)[0] + trigger_types = {item.strip() for item in listed.split(",") if item.strip()} + + assert trigger_types == { + "opened", + "synchronize", + "reopened", + "edited", + "ready_for_review", + "enqueued", + "dequeued", + "auto_merge_enabled", + "auto_merge_disabled", + "closed", + } def test_merge_scheduler_provides_same_repository_dispatch_credential() -> None: From bed1acd1085b7940dc02d71c5c47a7fe63252678 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 24 Jul 2026 16:55:43 +0900 Subject: [PATCH 03/10] Match active review runs by dispatch title Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- scripts/ci/pr_review_merge_scheduler.py | 11 ++++----- tests/test_pr_review_merge_scheduler.py | 31 +++++++++++++++++++++---- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/scripts/ci/pr_review_merge_scheduler.py b/scripts/ci/pr_review_merge_scheduler.py index c13adfe9..3bc3387f 100644 --- a/scripts/ci/pr_review_merge_scheduler.py +++ b/scripts/ci/pr_review_merge_scheduler.py @@ -1901,23 +1901,20 @@ def active_review_run_refs( for run_repo in repositories: for run_data in active_workflow_runs(run_repo, statuses): - run_name = str(run_data.get("name") or "") - if run_name != workflow and run_name not in workflow_aliases: - continue run_id = run_data.get("id") if not run_id: continue run_ref = (run_repo, str(run_id)) display_title = str(run_data.get("display_title") or "") - if ( - run_data.get("event") == "repository_dispatch" - and display_title.startswith(dispatch_title_prefix) - ): + if display_title.startswith(dispatch_title_prefix): dispatched_head = display_title.removeprefix(dispatch_title_prefix).lower() if not GIT_SHA_RE.fullmatch(dispatched_head): continue (current if dispatched_head == head else stale).append(run_ref) continue + run_name = str(run_data.get("name") or "") + if run_name != workflow and run_name not in workflow_aliases: + continue if run_repo != target_repo: continue run_head = str(run_data.get("head_sha") or "").lower() diff --git a/tests/test_pr_review_merge_scheduler.py b/tests/test_pr_review_merge_scheduler.py index 1fe10da7..bf3c6e59 100644 --- a/tests/test_pr_review_merge_scheduler.py +++ b/tests/test_pr_review_merge_scheduler.py @@ -2078,7 +2078,7 @@ def test_dispatch_opencode_review_deduplicates_current_head_repository_dispatch( head_sha = "a" * 40 current_dispatch = { "id": 9100, - "name": "Required OpenCode Review", + "name": f"Required OpenCode Review owner/repo#1@{head_sha}", "event": "repository_dispatch", "head_sha": "default-branch-sha", "display_title": f"Required OpenCode Review owner/repo#1@{head_sha}", @@ -2132,7 +2132,7 @@ def test_dispatch_strix_cancels_stale_central_run_and_keeps_current(monkeypatch, central_runs = [ { "id": 9300, - "name": "Strix Security Scan", + "name": f"Strix Security Scan owner/repo#1@{stale_sha}", "event": "repository_dispatch", "head_sha": "default-branch-sha", "display_title": f"Strix Security Scan owner/repo#1@{stale_sha}", @@ -2140,7 +2140,7 @@ def test_dispatch_strix_cancels_stale_central_run_and_keeps_current(monkeypatch, }, { "id": 9301, - "name": "Strix Security Scan", + "name": f"Strix Security Scan owner/repo#1@{head_sha}", "event": "repository_dispatch", "head_sha": "default-branch-sha", "display_title": f"Strix Security Scan owner/repo#1@{head_sha}", @@ -2200,7 +2200,7 @@ def test_central_run_filter_ignores_malformed_and_non_dispatch_titles(monkeypatc central_runs = [ { "id": 9400, - "name": "Required OpenCode Review", + "name": "Required OpenCode Review owner/repo#1@not-a-sha", "event": "repository_dispatch", "display_title": "Required OpenCode Review owner/repo#1@not-a-sha", "pull_requests": [], @@ -2232,6 +2232,29 @@ def fake_active_runs(repo, statuses=("queued", "in_progress")): assert sched.force_cancel_workflow_runs("owner/repo", []) == {} +def test_active_review_run_refs_match_dynamic_run_names_from_actions_api(monkeypatch): + head_sha = "a" * 40 + current_run = { + "id": 9500, + "name": f"Required OpenCode Review owner/repo#1@{head_sha}", + "event": "pull_request_target", + "display_title": f"Required OpenCode Review owner/repo#1@{head_sha}", + "head_sha": head_sha, + "pull_requests": [{"number": 1}], + } + monkeypatch.setattr( + sched, + "active_workflow_runs", + lambda repo, statuses=("queued", "in_progress"): [current_run], + ) + + assert sched.active_opencode_run_refs( + "owner/repo", + "OpenCode Review", + make_pr(headRefOid=head_sha), + ) == ([("owner/repo", "9500")], []) + + def test_active_run_filters_and_stale_opencode_dry_run(monkeypatch): runs = [ { From 3183d159373d5a3dcfd2e5cffce85d5ad73a6a29 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 24 Jul 2026 20:39:24 +0900 Subject: [PATCH 04/10] Ignore pull_request_target runs in OpenCode dispatch dedupe Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- scripts/ci/pr_review_merge_scheduler.py | 3 ++- tests/test_pr_review_merge_scheduler.py | 36 ++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/scripts/ci/pr_review_merge_scheduler.py b/scripts/ci/pr_review_merge_scheduler.py index 3bc3387f..af2bf346 100644 --- a/scripts/ci/pr_review_merge_scheduler.py +++ b/scripts/ci/pr_review_merge_scheduler.py @@ -1905,8 +1905,9 @@ def active_review_run_refs( if not run_id: continue run_ref = (run_repo, str(run_id)) + run_event = str(run_data.get("event") or "").lower() display_title = str(run_data.get("display_title") or "") - if display_title.startswith(dispatch_title_prefix): + if run_event == "repository_dispatch" and display_title.startswith(dispatch_title_prefix): dispatched_head = display_title.removeprefix(dispatch_title_prefix).lower() if not GIT_SHA_RE.fullmatch(dispatched_head): continue diff --git a/tests/test_pr_review_merge_scheduler.py b/tests/test_pr_review_merge_scheduler.py index bf3c6e59..818a7ae4 100644 --- a/tests/test_pr_review_merge_scheduler.py +++ b/tests/test_pr_review_merge_scheduler.py @@ -2237,6 +2237,40 @@ def test_active_review_run_refs_match_dynamic_run_names_from_actions_api(monkeyp current_run = { "id": 9500, "name": f"Required OpenCode Review owner/repo#1@{head_sha}", + "event": "repository_dispatch", + "display_title": f"Required OpenCode Review owner/repo#1@{head_sha}", + "head_sha": "default-branch-sha", + "pull_requests": [], + } + + def fake_active_runs(repo, statuses=("queued", "in_progress")): + del statuses + if repo == "ContextualWisdomLab/.github": + return [current_run] + return [] + + monkeypatch.setattr( + sched, + "active_workflow_runs", + fake_active_runs, + ) + monkeypatch.setenv( + "SCHEDULER_REQUIRED_WORKFLOW_REPOSITORY", + "ContextualWisdomLab/.github", + ) + + assert sched.active_opencode_run_refs( + "owner/repo", + "OpenCode Review", + make_pr(headRefOid=head_sha), + ) == ([("ContextualWisdomLab/.github", "9500")], []) + + +def test_active_review_run_refs_ignore_dynamic_titles_from_pull_request_target(monkeypatch): + head_sha = "a" * 40 + current_run = { + "id": 9501, + "name": f"Required OpenCode Review owner/repo#1@{head_sha}", "event": "pull_request_target", "display_title": f"Required OpenCode Review owner/repo#1@{head_sha}", "head_sha": head_sha, @@ -2252,7 +2286,7 @@ def test_active_review_run_refs_match_dynamic_run_names_from_actions_api(monkeyp "owner/repo", "OpenCode Review", make_pr(headRefOid=head_sha), - ) == ([("owner/repo", "9500")], []) + ) == ([], []) def test_active_run_filters_and_stale_opencode_dry_run(monkeypatch): From 7701ff0fb1d5519d9669ead82145ab324aa5b344 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 24 Jul 2026 20:45:36 +0900 Subject: [PATCH 05/10] Scope dispatch-title dedupe to central repository_dispatch runs Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- scripts/ci/pr_review_merge_scheduler.py | 6 ++++- tests/test_pr_review_merge_scheduler.py | 30 +++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/scripts/ci/pr_review_merge_scheduler.py b/scripts/ci/pr_review_merge_scheduler.py index af2bf346..86a33d2f 100644 --- a/scripts/ci/pr_review_merge_scheduler.py +++ b/scripts/ci/pr_review_merge_scheduler.py @@ -1907,7 +1907,11 @@ def active_review_run_refs( run_ref = (run_repo, str(run_id)) run_event = str(run_data.get("event") or "").lower() display_title = str(run_data.get("display_title") or "") - if run_event == "repository_dispatch" and display_title.startswith(dispatch_title_prefix): + if ( + run_repo == dispatch_repo + and run_event == "repository_dispatch" + and display_title.startswith(dispatch_title_prefix) + ): dispatched_head = display_title.removeprefix(dispatch_title_prefix).lower() if not GIT_SHA_RE.fullmatch(dispatched_head): continue diff --git a/tests/test_pr_review_merge_scheduler.py b/tests/test_pr_review_merge_scheduler.py index 818a7ae4..3083328c 100644 --- a/tests/test_pr_review_merge_scheduler.py +++ b/tests/test_pr_review_merge_scheduler.py @@ -2289,6 +2289,36 @@ def test_active_review_run_refs_ignore_dynamic_titles_from_pull_request_target(m ) == ([], []) +def test_active_review_run_refs_ignore_target_repo_dispatch_when_central_repo_is_configured(monkeypatch): + head_sha = "a" * 40 + target_repo_dispatch_run = { + "id": 9502, + "name": f"Required OpenCode Review owner/repo#1@{head_sha}", + "event": "repository_dispatch", + "display_title": f"Required OpenCode Review owner/repo#1@{head_sha}", + "head_sha": "target-default-branch-sha", + "pull_requests": [], + } + + def fake_active_runs(repo, statuses=("queued", "in_progress")): + del statuses + if repo == "owner/repo": + return [target_repo_dispatch_run] + return [] + + monkeypatch.setattr(sched, "active_workflow_runs", fake_active_runs) + monkeypatch.setenv( + "SCHEDULER_REQUIRED_WORKFLOW_REPOSITORY", + "ContextualWisdomLab/.github", + ) + + assert sched.active_opencode_run_refs( + "owner/repo", + "OpenCode Review", + make_pr(headRefOid=head_sha), + ) == ([], []) + + def test_active_run_filters_and_stale_opencode_dry_run(monkeypatch): runs = [ { From 8c2fa1a177f8fba8d996866e06339b26f49340b7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 25 Jul 2026 04:54:34 +0900 Subject: [PATCH 06/10] fix(review): accept natural-English line-before-path citation in adversarial probes OpenRouter models commonly write evidence as 'line N of path' or 'line N in path' rather than the canonical 'path:N' form required by the validator. Both formulations cite the exact file path and line number together so they are semantically equivalent and equally secure. Extend adversarial_evidence_rejection_reason to also match: line of line in while preserving all existing boundary checks (leading-prefix rejection, wrong-line-number rejection, independent-proof and observed-result gates, and the source-line-sha256 receipt requirement). Add tests for both new citation formats plus the previously uncovered negated-execution evidence path (line 49). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- scripts/ci/adversarial_evidence.py | 8 +++++- tests/test_adversarial_evidence.py | 41 ++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/scripts/ci/adversarial_evidence.py b/scripts/ci/adversarial_evidence.py index 4fbc4ce5..222816b4 100644 --- a/scripts/ci/adversarial_evidence.py +++ b/scripts/ci/adversarial_evidence.py @@ -58,8 +58,14 @@ def adversarial_evidence_rejection_reason( ) else: escaped_line = re.escape(str(line)) + # Accept "path:N", "path#LN", "path line N", "path, line N" (path-first), + # or "line N of path" / "line N in path" (natural English phrasing). path_citation = re.search( - rf"{escaped_path}(?::|#L|\s+line\s+){escaped_line}\b", + rf"{escaped_path}(?::|#L|,?\s+line\s+){escaped_line}\b", + cleaned, + re.IGNORECASE, + ) or re.search( + rf"\bline\s+{escaped_line}\b\s+(?:of|in)\s+{escaped_path}(?![A-Za-z0-9_./-])", cleaned, re.IGNORECASE, ) diff --git a/tests/test_adversarial_evidence.py b/tests/test_adversarial_evidence.py index d5849012..3e722c58 100644 --- a/tests/test_adversarial_evidence.py +++ b/tests/test_adversarial_evidence.py @@ -10,6 +10,13 @@ def test_rejects_circular_adversarial_evidence(): ) +def test_rejects_negated_execution_evidence(): + assert "explicitly denies" in evidence.adversarial_evidence_rejection_reason( + "No test was run. .github/workflows/review.yml passed.", + ".github/workflows/review.yml", + ) + + def test_accepts_independent_proof_anchor_and_rejects_path_only(): assert ( evidence.adversarial_evidence_rejection_reason( @@ -86,6 +93,40 @@ def test_requires_the_exact_probe_path_and_line_when_line_is_supplied(): ) +def test_accepts_natural_english_line_of_path_citation(): + """'line N of path' and 'line N in path' are valid citation formats.""" + # "line N of path" form + assert ( + evidence.adversarial_evidence_rejection_reason( + f"Source trace at line 42 of .github/workflows/review.yml confirmed the branch was rejected. {SOURCE_RECEIPT}", + ".github/workflows/review.yml", + 42, + ) + is None + ), "should accept 'line N of path' citation" + # "line N in path" form + assert ( + evidence.adversarial_evidence_rejection_reason( + f"Checked line 42 in .github/workflows/review.yml and observed it was rejected. {SOURCE_RECEIPT}", + ".github/workflows/review.yml", + 42, + ) + is None + ), "should accept 'line N in path' citation" + # wrong line number still rejected + assert "must cite" in evidence.adversarial_evidence_rejection_reason( + f"Line 99 of .github/workflows/review.yml confirmed. {SOURCE_RECEIPT}", + ".github/workflows/review.yml", + 42, + ), "wrong line number must still be rejected" + # prefix boundary still enforced + assert "must cite" in evidence.adversarial_evidence_rejection_reason( + f"line 42 of prefix.github/workflows/review.yml confirmed. {SOURCE_RECEIPT}", + ".github/workflows/review.yml", + 42, + ), "leading path boundary must still be enforced" + + def test_path_only_citation_rejects_longer_path_substrings(): """A filename embedded inside another path is not an exact citation.""" assert "exact probe path" in evidence.adversarial_evidence_rejection_reason( From e0b9309d64f42f8a70928955027090980f32aa56 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 25 Jul 2026 05:31:52 +0900 Subject: [PATCH 07/10] fix: remove invalid enqueued/dequeued types; use YAML parser in contract test - Remove enqueued and dequeued from pull_request_target.types: these are not valid activity types for pull_request_target events. Merge-queue transitions use the separate merge_group event. The stacked-PR retarget fix (edited) and auto-merge transitions remain intact. - Refactor test_merge_scheduler_pull_request_target_includes_retarget_and_queue_events to use yaml.safe_load (YAML parser) instead of fragile string splitting, and assert subset membership instead of exact equality. Handles PyYAML 1.1 on -> True boolean parsing with a dual-key lookup. Fixes reviewer comments: - PRRT_kwDOS_C14s6Tpp3J: enqueued/dequeued not valid pull_request_target types - PRRT_kwDOS_C14s6Tpp3a: contract test now uses YAML parser, not string splitting Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../workflows/pr-review-merge-scheduler.yml | 6 ++- .../test_required_workflow_queue_contract.py | 37 +++++++++++-------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/.github/workflows/pr-review-merge-scheduler.yml b/.github/workflows/pr-review-merge-scheduler.yml index 4643ff9a..fb47af47 100644 --- a/.github/workflows/pr-review-merge-scheduler.yml +++ b/.github/workflows/pr-review-merge-scheduler.yml @@ -4,9 +4,11 @@ on: push: branches: [main, develop, master] pull_request_target: - # Include retarget/queue/auto-merge state changes so stacked PR chains are + # Include retarget/auto-merge state changes so stacked PR chains are # re-evaluated immediately when merge readiness shifts without a new head push. - types: [opened, synchronize, reopened, edited, ready_for_review, enqueued, dequeued, auto_merge_enabled, auto_merge_disabled, closed] + # Note: `enqueued`/`dequeued` are not valid pull_request_target activity types; + # merge-queue transitions fire the separate `merge_group` event. + types: [opened, synchronize, reopened, edited, ready_for_review, auto_merge_enabled, auto_merge_disabled, closed] pull_request_review: types: [submitted, dismissed] workflow_run: diff --git a/tests/test_required_workflow_queue_contract.py b/tests/test_required_workflow_queue_contract.py index fc91378e..e99c21d9 100644 --- a/tests/test_required_workflow_queue_contract.py +++ b/tests/test_required_workflow_queue_contract.py @@ -39,29 +39,36 @@ def test_merge_scheduler_dispatches_one_review_by_default() -> None: def test_merge_scheduler_pull_request_target_includes_retarget_and_queue_events() -> None: - workflow = workflow_text("pr-review-merge-scheduler.yml") - trigger_block = workflow.split("pull_request_target:", 1)[1].split( - "pull_request_review:", - 1, - )[0] - types_line = next( - line.strip() for line in trigger_block.splitlines() if line.strip().startswith("types:") - ) - listed = types_line.split("[", 1)[1].split("]", 1)[0] - trigger_types = {item.strip() for item in listed.split(",") if item.strip()} - - assert trigger_types == { + import yaml # noqa: PLC0415 — stdlib-like import in test; yaml is always available in CI + + workflow_path = REPO_ROOT / ".github" / "workflows" / "pr-review-merge-scheduler.yml" + data = yaml.safe_load(workflow_path.read_text(encoding="utf-8")) + # PyYAML (YAML 1.1) parses bare `on` as boolean True, so check both spellings. + on_section = data.get(True) or data.get("on") or {} + trigger_types = set(on_section.get("pull_request_target", {}).get("types", [])) + + # Stacked-PR retarget and auto-merge transitions that must fire the scheduler. + # `enqueued`/`dequeued` are NOT valid pull_request_target activity types — + # merge-queue events use the separate `merge_group` event instead. + required = { "opened", "synchronize", "reopened", - "edited", + "edited", # retarget: base branch change fires this "ready_for_review", - "enqueued", - "dequeued", "auto_merge_enabled", "auto_merge_disabled", "closed", } + assert required.issubset(trigger_types), ( + f"Missing pull_request_target trigger types: {required - trigger_types}" + ) + assert "enqueued" not in trigger_types, ( + "`enqueued` is not a valid pull_request_target activity type" + ) + assert "dequeued" not in trigger_types, ( + "`dequeued` is not a valid pull_request_target activity type" + ) def test_merge_scheduler_provides_same_repository_dispatch_credential() -> None: From 09cea2aa50bda73e71936f7f7e349cc874141b37 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 25 Jul 2026 05:42:24 +0900 Subject: [PATCH 08/10] fix: use stdlib re in contract test; drop merge_group comment - Rewrite test_merge_scheduler_pull_request_target_includes_retarget_and_queue_events to use stdlib re.search() instead of yaml.safe_load(); PyYAML is not in the pinned pytest toolchain and would raise ModuleNotFoundError in CI. The regex matches the inline types:[...] list under pull_request_target, which is more robust than the earlier string-split approach while staying dependency-free. - Remove the merge_group mention from the workflow comment; we are not implementing a merge_group trigger in this PR, and the reference was misleading without a matching workflow declaration or contract test. Fixes reviewer comments: - PRRT_kwDOS_C14s6TqRKw: replaced yaml import with stdlib re - PRRT_kwDOS_C14s6TqRLE: removed unfounded merge_group comment Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../workflows/pr-review-merge-scheduler.yml | 3 +-- .../test_required_workflow_queue_contract.py | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/.github/workflows/pr-review-merge-scheduler.yml b/.github/workflows/pr-review-merge-scheduler.yml index fb47af47..50d3cbca 100644 --- a/.github/workflows/pr-review-merge-scheduler.yml +++ b/.github/workflows/pr-review-merge-scheduler.yml @@ -6,8 +6,7 @@ on: pull_request_target: # Include retarget/auto-merge state changes so stacked PR chains are # re-evaluated immediately when merge readiness shifts without a new head push. - # Note: `enqueued`/`dequeued` are not valid pull_request_target activity types; - # merge-queue transitions fire the separate `merge_group` event. + # `enqueued` and `dequeued` are not valid pull_request_target activity types. types: [opened, synchronize, reopened, edited, ready_for_review, auto_merge_enabled, auto_merge_disabled, closed] pull_request_review: types: [submitted, dismissed] diff --git a/tests/test_required_workflow_queue_contract.py b/tests/test_required_workflow_queue_contract.py index e99c21d9..820f4181 100644 --- a/tests/test_required_workflow_queue_contract.py +++ b/tests/test_required_workflow_queue_contract.py @@ -39,17 +39,19 @@ def test_merge_scheduler_dispatches_one_review_by_default() -> None: def test_merge_scheduler_pull_request_target_includes_retarget_and_queue_events() -> None: - import yaml # noqa: PLC0415 — stdlib-like import in test; yaml is always available in CI + import re # noqa: PLC0415 - workflow_path = REPO_ROOT / ".github" / "workflows" / "pr-review-merge-scheduler.yml" - data = yaml.safe_load(workflow_path.read_text(encoding="utf-8")) - # PyYAML (YAML 1.1) parses bare `on` as boolean True, so check both spellings. - on_section = data.get(True) or data.get("on") or {} - trigger_types = set(on_section.get("pull_request_target", {}).get("types", [])) + workflow = workflow_text("pr-review-merge-scheduler.yml") + # Match the inline types list under pull_request_target (DOTALL to skip comment lines). + m = re.search( + r"pull_request_target:.*?types:\s*\[([^\]]+)\]", + workflow, + re.DOTALL, + ) + assert m is not None, "pull_request_target.types list not found in scheduler workflow" + trigger_types = {t.strip() for t in m.group(1).split(",") if t.strip()} # Stacked-PR retarget and auto-merge transitions that must fire the scheduler. - # `enqueued`/`dequeued` are NOT valid pull_request_target activity types — - # merge-queue events use the separate `merge_group` event instead. required = { "opened", "synchronize", @@ -63,6 +65,7 @@ def test_merge_scheduler_pull_request_target_includes_retarget_and_queue_events( assert required.issubset(trigger_types), ( f"Missing pull_request_target trigger types: {required - trigger_types}" ) + # enqueued/dequeued are not valid pull_request_target activity types. assert "enqueued" not in trigger_types, ( "`enqueued` is not a valid pull_request_target activity type" ) From f65b975a17128fc1b752626124fb97c232ad691d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 25 Jul 2026 05:49:52 +0900 Subject: [PATCH 09/10] fix: close reviewer findings on citation boundary and test naming Rename the pull_request_target contract test to match its actual scope (retarget + auto-merge transitions, not queue events). Accept sentence punctuation after natural-English citations like "line 42 in path." while still rejecting continued suffix paths. Add regression assertions for trailing punctuation acceptance and suffix rejection in adversarial evidence validation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- scripts/ci/adversarial_evidence.py | 4 +++- tests/test_adversarial_evidence.py | 15 +++++++++++++++ tests/test_required_workflow_queue_contract.py | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/scripts/ci/adversarial_evidence.py b/scripts/ci/adversarial_evidence.py index 222816b4..9f53ff54 100644 --- a/scripts/ci/adversarial_evidence.py +++ b/scripts/ci/adversarial_evidence.py @@ -60,12 +60,14 @@ def adversarial_evidence_rejection_reason( escaped_line = re.escape(str(line)) # Accept "path:N", "path#LN", "path line N", "path, line N" (path-first), # or "line N of path" / "line N in path" (natural English phrasing). + # The path may be followed by sentence punctuation (e.g., "... in path."). path_citation = re.search( rf"{escaped_path}(?::|#L|,?\s+line\s+){escaped_line}\b", cleaned, re.IGNORECASE, ) or re.search( - rf"\bline\s+{escaped_line}\b\s+(?:of|in)\s+{escaped_path}(?![A-Za-z0-9_./-])", + rf"\bline\s+{escaped_line}\b\s+(?:of|in)\s+{escaped_path}" + r"(?:(?![A-Za-z0-9_./-])|(?=\.(?:$|[\s)\]}\",;:!?])))", cleaned, re.IGNORECASE, ) diff --git a/tests/test_adversarial_evidence.py b/tests/test_adversarial_evidence.py index 3e722c58..7e0ffdec 100644 --- a/tests/test_adversarial_evidence.py +++ b/tests/test_adversarial_evidence.py @@ -113,6 +113,15 @@ def test_accepts_natural_english_line_of_path_citation(): ) is None ), "should accept 'line N in path' citation" + # sentence punctuation after path should still be accepted + assert ( + evidence.adversarial_evidence_rejection_reason( + f"Checked line 42 in .github/workflows/review.yml. observed it was rejected. {SOURCE_RECEIPT}", + ".github/workflows/review.yml", + 42, + ) + is None + ), "should accept trailing sentence punctuation after path" # wrong line number still rejected assert "must cite" in evidence.adversarial_evidence_rejection_reason( f"Line 99 of .github/workflows/review.yml confirmed. {SOURCE_RECEIPT}", @@ -125,6 +134,12 @@ def test_accepts_natural_english_line_of_path_citation(): ".github/workflows/review.yml", 42, ), "leading path boundary must still be enforced" + # longer suffix paths remain invalid + assert "must cite" in evidence.adversarial_evidence_rejection_reason( + f"line 42 in .github/workflows/review.yml.backup confirmed. {SOURCE_RECEIPT}", + ".github/workflows/review.yml", + 42, + ), "path suffix continuation must still be rejected" def test_path_only_citation_rejects_longer_path_substrings(): diff --git a/tests/test_required_workflow_queue_contract.py b/tests/test_required_workflow_queue_contract.py index 820f4181..1d070bba 100644 --- a/tests/test_required_workflow_queue_contract.py +++ b/tests/test_required_workflow_queue_contract.py @@ -38,7 +38,7 @@ def test_merge_scheduler_dispatches_one_review_by_default() -> None: ) -def test_merge_scheduler_pull_request_target_includes_retarget_and_queue_events() -> None: +def test_merge_scheduler_pull_request_target_includes_retarget_and_automerge_events() -> None: import re # noqa: PLC0415 workflow = workflow_text("pr-review-merge-scheduler.yml") From c4a90235405119bb26737886f3618b932da94db9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 25 Jul 2026 09:28:56 +0900 Subject: [PATCH 10/10] chore: retrigger required checks on current head Create an empty commit so required checks re-evaluate on a fresh head, clearing stale failed opencode-review context from unauthorized repository_dispatch rerun attempts. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>