From 786ea11ca561ccd7ee2822fe9cd30401cc8c28b7 Mon Sep 17 00:00:00 2001 From: GISCE Bot Date: Fri, 30 Oct 2026 12:22:08 +0100 Subject: [PATCH] fix: preserve bot PR authorship over assignment Keep changes-requested reviews writable when a bot-authored pull request is also assigned to the bot, with direct email and webhook regressions. Co-authored-by: Eduard Carreras --- src/github_agent_bridge/actionability.py | 4 +-- tests/test_actionability.py | 12 +++++++ tests/test_queue.py | 2 +- tests/test_webhook.py | 46 ++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 3 deletions(-) diff --git a/src/github_agent_bridge/actionability.py b/src/github_agent_bridge/actionability.py index 3fd89c7..5ca7732 100644 --- a/src/github_agent_bridge/actionability.py +++ b/src/github_agent_bridge/actionability.py @@ -69,11 +69,11 @@ def evaluate_feedback_authorization( return "defer_to_executor" if first_mentioned_login(facts.body) in configured: return "mentioned" - if configured.intersection(login.lower() for login in facts.assignees): - return "assigned" author = (facts.subject_author or "").lower() if facts.is_pull_request and author in configured: return "pr_authored_by_bot" + if configured.intersection(login.lower() for login in facts.assignees): + return "assigned" return "ignored" diff --git a/tests/test_actionability.py b/tests/test_actionability.py index 6c47d37..2b73d8c 100644 --- a/tests/test_actionability.py +++ b/tests/test_actionability.py @@ -34,6 +34,18 @@ def test_pull_request_authorship_is_a_positive_signal(): ) == "pr_authored_by_bot" +def test_pull_request_authorship_is_not_masked_by_assignment(): + assert evaluate_feedback_authorization( + FeedbackAuthorizationFacts( + body="Please address the migration safety gap.", + assignees=("giscebot",), + subject_author="giscebot", + is_pull_request=True, + ), + {"giscebot"}, + ) == "pr_authored_by_bot" + + def test_first_mention_must_address_the_configured_bot(): assert evaluate_feedback_authorization( FeedbackAuthorizationFacts(body="@marc ask @giscebot to check this"), diff --git a/tests/test_queue.py b/tests/test_queue.py index 08c921c..139d829 100644 --- a/tests/test_queue.py +++ b/tests/test_queue.py @@ -1124,7 +1124,7 @@ def test_email_changes_requested_uses_verified_review_state(tmp_path, monkeypatc }, "repos/gisce/example/issues/42": { "user": {"login": "giscebot"}, - "assignees": [], + "assignees": [{"login": "giscebot"}], "pull_request": {"url": "https://api.github.com/repos/gisce/example/pulls/42"}, }, } diff --git a/tests/test_webhook.py b/tests/test_webhook.py index 065f708..f9b0633 100644 --- a/tests/test_webhook.py +++ b/tests/test_webhook.py @@ -1152,6 +1152,52 @@ def test_webhook_canary_enqueues_actionable_pull_request_review_states(tmp_path, assert con.execute("SELECT COUNT(*) FROM jobs").fetchone()[0] == 1 +def test_webhook_changes_requested_on_assigned_bot_pr_allows_work( + tmp_path, monkeypatch +): + monkeypatch.setattr( + "github_agent_bridge.queue.trigger_actor_details_for_enqueue", + lambda notification, ctx: None, + ) + payload = pull_request_review_payload( + state="changes_requested", + review_id=5710723075, + sender="pilipilisbot", + body="Please bind the shadow delivery to its own receipt.", + pr_author="giscebot", + assignees=("giscebot",), + ) + config = DashboardConfig( + db=tmp_path / "bridge.sqlite3", + require_auth=False, + webhook_secrets=(SECRET,), + webhook_mode="canary", + webhook_policy=canary_policy(tmp_path), + ) + + response = TestClient(create_app(config)).post( + "/api/webhooks/github", + content=payload, + headers=signed_headers( + payload, + delivery="review-assigned-bot-pr", + event="pull_request_review", + ), + ) + + assert response.json()["enqueue_status"] == "enqueued" + with sqlite3.connect(config.db) as con: + work_intent, metadata_json = con.execute( + "SELECT work_intent,metadata_json FROM jobs" + ).fetchone() + metadata = json.loads(metadata_json) + assert work_intent == "work_allowed" + assert metadata["feedback_actionability"] == "pr_authored_by_bot" + assert metadata["intent_guardrail"] == ( + "bot_authored_pr_changes_requested_work_allowed" + ) + + def test_webhook_canary_ignores_unaddressed_review_on_someone_elses_pr(tmp_path): payload = pull_request_review_payload( state="changes_requested",