Skip to content

Commit 1bcabe0

Browse files
authored
fix: close pr264 reviewer reminder incident (#558)
* fix: close pr264 reviewer reminder incident * test(reviewer-bot): align authority ownership contract * fix(reviewer-bot): honor approval suppression semantics
1 parent ee62fbe commit 1bcabe0

29 files changed

Lines changed: 1237 additions & 236 deletions
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
name: Reviewer Bot Preview
2+
3+
run-name: preview ${{ github.event.inputs.action }} issue ${{ github.event.inputs.issue_number }} nonce ${{ github.event.inputs.validation_nonce }}
4+
5+
on:
6+
workflow_dispatch:
7+
inputs:
8+
action:
9+
description: Preview action to perform
10+
required: true
11+
default: preview-check-overdue
12+
type: choice
13+
options: [preview-check-overdue, preview-reviewer-board]
14+
issue_number:
15+
description: PR or issue number to preview
16+
required: true
17+
type: string
18+
validation_nonce:
19+
description: Validation nonce used to correlate this preview run
20+
required: true
21+
type: string
22+
23+
permissions:
24+
contents: read
25+
26+
env:
27+
STATE_ISSUE_NUMBER: '314'
28+
29+
jobs:
30+
reviewer-bot-preview:
31+
runs-on: ubuntu-latest
32+
permissions:
33+
contents: read
34+
issues: read
35+
pull-requests: read
36+
steps:
37+
- name: Install uv
38+
run: python -m pip install uv
39+
- name: Fetch trusted bot source tarball
40+
env:
41+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
42+
run: |
43+
python - <<'PY'
44+
import io, os, tarfile, urllib.request
45+
from pathlib import Path
46+
req = urllib.request.Request(
47+
f"https://api.github.com/repos/{os.environ['GITHUB_REPOSITORY']}/tarball/{os.environ['GITHUB_SHA']}",
48+
headers={'Authorization': f"Bearer {os.environ['GITHUB_TOKEN']}", 'Accept': 'application/vnd.github+json'},
49+
)
50+
target = Path(os.environ['RUNNER_TEMP']) / 'reviewer-bot-src'
51+
target.mkdir(parents=True, exist_ok=True)
52+
with urllib.request.urlopen(req) as response:
53+
data = response.read()
54+
with tarfile.open(fileobj=io.BytesIO(data), mode='r:gz') as archive:
55+
archive.extractall(target)
56+
roots = list(target.iterdir())
57+
print(f'BOT_SRC_ROOT={roots[0]}', file=open(os.environ['GITHUB_ENV'], 'a', encoding='utf-8'))
58+
PY
59+
- name: Run reviewer bot preview
60+
env:
61+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
62+
EVENT_NAME: workflow_dispatch
63+
EVENT_ACTION: ''
64+
MANUAL_ACTION: ${{ github.event.inputs.action }}
65+
ISSUE_NUMBER: ${{ github.event.inputs.issue_number }}
66+
VALIDATION_NONCE: ${{ github.event.inputs.validation_nonce }}
67+
REPO_OWNER: ${{ github.repository_owner }}
68+
REPO_NAME: ${{ github.event.repository.name }}
69+
GITHUB_REPOSITORY: ${{ github.repository }}
70+
GITHUB_REF: ${{ github.ref }}
71+
WORKFLOW_RUN_ID: ${{ github.run_id }}
72+
WORKFLOW_NAME: ${{ github.workflow }}
73+
WORKFLOW_JOB_NAME: ${{ github.job }}
74+
REVIEWER_BOARD_ENABLED: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'preview-reviewer-board' && 'true' || 'false' }}
75+
REVIEWER_BOARD_TOKEN: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'preview-reviewer-board' && secrets.REVIEWER_BOARD_TOKEN || '' }}
76+
run: |
77+
artifact_dir="$RUNNER_TEMP/reviewer-bot-preview-output-${GITHUB_RUN_ID}-attempt-${GITHUB_RUN_ATTEMPT}"
78+
mkdir -p "$artifact_dir"
79+
uv run --project "$BOT_SRC_ROOT" reviewer-bot > "$artifact_dir/preview-output.json"
80+
- name: Upload preview artifact
81+
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808
82+
with:
83+
name: reviewer-bot-preview-output-${{ github.run_id }}-attempt-${{ github.run_attempt }}
84+
path: ${{ runner.temp }}/reviewer-bot-preview-output-${{ github.run_id }}-attempt-${{ github.run_attempt }}
85+
retention-days: 7
86+
if-no-files-found: error

scripts/reviewer_bot_core/approval_policy.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,22 @@
2222
def compute_pr_approval_state_from_reviews(
2323
survivors: dict[str, dict],
2424
*,
25+
current_reviewer: str | None,
2526
current_head: str,
2627
permission_statuses: dict[str, str],
2728
) -> dict[str, object]:
2829
approvals = [review for review in survivors.values() if str(review.get("state", "")).upper() == "APPROVED"]
30+
current_reviewer_approvals = []
31+
current_reviewer_key = current_reviewer.lower() if isinstance(current_reviewer, str) and current_reviewer.strip() else None
32+
if current_reviewer_key is not None:
33+
for review in approvals:
34+
author = review.get("user", {}).get("login")
35+
if isinstance(author, str) and author.lower() == current_reviewer_key:
36+
current_reviewer_approvals.append(review)
2937
completion = {
30-
"completed": bool(approvals),
38+
"completed": bool(current_reviewer_approvals),
3139
"current_head_sha": current_head,
32-
"qualifying_review_ids": [review.get("id") for review in approvals],
40+
"qualifying_review_ids": [review.get("id") for review in current_reviewer_approvals],
3341
}
3442

3543
has_write_approval = False
@@ -100,6 +108,7 @@ def compute_pr_approval_state_result(
100108
)
101109
result = compute_pr_approval_state_from_reviews(
102110
survivors,
111+
current_reviewer=review_data.get("current_reviewer"),
103112
current_head=current_head,
104113
permission_statuses=permission_cache,
105114
)

0 commit comments

Comments
 (0)