Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions .github/scripts/pr-review-mention/lib/force-review.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env bash
# force_review decision for the pr-review-mention reusable workflow (#1281).
#
# Pure and side-effect-free, so it can be unit-tested with bats (see
# test/workflows/pr-review-mention/force-review.bats). The reusable sources this
# file from its own commit and acts on the verdict in "Trigger review agent".
#
# pr-review.yml in .github-private sets FORCE_REVIEW (skip the advisory-bot and
# comment-disposition waiting gates; the review itself still runs in full) only
# when the dispatch carries client_payload.force_review == true. The rule:
# - a trusted human's @donpetry-bot comment (OWNER / MEMBER / COLLABORATOR,
# not donpetry-bot, no automation marker) is the break-glass → force;
# - the review_requested path never forces;
# - a comment carrying an automation marker never forces. Agents post as the
# owner account, so the association alone cannot tell them from a person.
# Known, accepted limit: an agent posting as the owner WITHOUT a marker is
# indistinguishable from the maintainer and is forced too.

# Body substrings that identify an agent-posted comment.
PR_REVIEW_MENTION_AUTOMATION_MARKERS=(
'<!-- pr-review-agent'
'<!-- pr-review-claim'
'<!-- persona:'
'<!-- dev-lead'
'<!-- dependency-advisory'
'<!-- maintainer-resolve'
'<!-- auto-rebase-conflict'
)

# pr_review_mention_force_decision EVENT_NAME AUTHOR_ASSOC AUTHOR_LOGIN BODY
# Prints one line "<verdict> <rule>" and returns 0. Verdicts:
# force dispatch with client_payload[force_review]=true
# plain dispatch without the flag (today's behaviour)
# none do not dispatch (untrusted / bot author / unsupported event)
# AUTHOR_ASSOC, AUTHOR_LOGIN and BODY are the comment's fields; they are
# ignored for the pull_request (review_requested) event.
pr_review_mention_force_decision() {
local event="$1" assoc="$2" login="$3" body="$4" marker

case "$event" in
pull_request)
echo "plain review-requested"
return 0
;;
issue_comment | pull_request_review_comment) ;;
*)
echo "none unsupported-event"
return 0
;;
esac

case "$assoc" in
OWNER | MEMBER | COLLABORATOR) ;;
*)
echo "none untrusted-association"
return 0
;;
esac

if [[ "$login" == "donpetry-bot" || "$login" == *"[bot]" ]]; then
echo "none bot-author"
return 0
fi

for marker in "${PR_REVIEW_MENTION_AUTOMATION_MARKERS[@]}"; do
if [[ "$body" == *"$marker"* ]]; then
echo "plain automation-marker"
return 0
fi
done

echo "force trusted-human-comment-mention"
}
69 changes: 67 additions & 2 deletions .github/workflows/pr-review-mention-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@
# repository_dispatch event to petry-projects/.github-private to trigger the
# pr-review agent cascade.
#
# force_review rule (#1281): the dispatch carries client_payload[force_review]
# =true (a JSON boolean, which lets pr-review skip its advisory-bot and
# comment-disposition waiting gates; the review itself still runs in full) ONLY
# for an @donpetry-bot comment / review comment by a trusted human: author
# association OWNER, MEMBER or COLLABORATOR, author not donpetry-bot, and a body
# with no automation marker (<!-- pr-review-agent, <!-- pr-review-claim,
# <!-- persona:, <!-- dev-lead, <!-- dependency-advisory, <!-- maintainer-resolve,
# <!-- auto-rebase-conflict). The review_requested path and marker-carrying
# comments dispatch a plain, un-forced review. Agents post as the owner account,
# so association alone cannot separate them from a person. The decision lives in
# .github/scripts/pr-review-mention/lib/force-review.sh (bats-tested).
#
# Requires:
# GH_PAT_WORKFLOWS — org secret, classic PAT (repo scope) for API calls and dispatch
# DON_PETRY_BOT_GH_PAT — org secret, classic PAT owned by donpetry-bot (repo scope) for posting comments
Expand Down Expand Up @@ -192,19 +204,72 @@ jobs:
gh pr comment "$PR_URL" --body "$(printf '<!-- pr-review-agent mention-ack -->\n%s' "${MSG}")" \
|| echo "::warning::could not post ack comment on $PR_URL (comment cap / API error) — continuing"

# Source the force_review decision from this reusable's own commit so the
# rule always matches the pinned workflow version. Authenticated with the
# workflows PAT (already required here) so callers need no extra
# `contents:` permission.
- name: Checkout force-review tooling
if: steps.trust.outputs.trusted == 'true'
# Best-effort: a checkout failure must not suppress the review dispatch;
# the trigger step falls back to a plain (non-forced) dispatch.
continue-on-error: true
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: petry-projects/.github
ref: ${{ github.job_workflow_sha }}
path: .pr-review-mention-tooling
token: ${{ secrets.GH_PAT_DON_PETRY || secrets.GH_PAT_WORKFLOWS }}
fetch-depth: 1
persist-credentials: false
Comment thread
codeant-ai[bot] marked this conversation as resolved.

- name: Trigger review agent
if: steps.trust.outputs.trusted == 'true'
env:
GH_TOKEN: ${{ secrets.GH_PAT_DON_PETRY || secrets.GH_PAT_WORKFLOWS }}
PR_URL: ${{ steps.pr.outputs.pr_url }}
# Comment fields via env, never inline `${{ }}` (script-injection).
EVENT_NAME: ${{ github.event_name }}
COMMENT_ASSOC: ${{ github.event.comment.author_association }}
COMMENT_USER: ${{ github.event.comment.user.login }}
COMMENT_BODY: ${{ github.event.comment.body }}
run: |
LIB=.pr-review-mention-tooling/.github/scripts/pr-review-mention/lib/force-review.sh
if [ -f "$LIB" ]; then
# shellcheck source=/dev/null
. "$LIB"
# Pure decision (see the header comment): force | plain | none.
read -r VERDICT RULE < <(pr_review_mention_force_decision \
"$EVENT_NAME" "$COMMENT_ASSOC" "$COMMENT_USER" "$COMMENT_BODY")
else
echo "::warning::force-review tooling unavailable — dispatching a plain review"
VERDICT=plain
RULE=tooling-unavailable
fi

FORCE_ARGS=()
case "$VERDICT" in
force)
# -F (typed field) so the payload value is the JSON boolean true.
FORCE_ARGS=(-F "client_payload[force_review]=true")
echo "force_review=true (rule: $RULE)"
;;
plain)
echo "force_review=false (rule: $RULE)"
;;
*)
echo "No dispatch (rule: $RULE)"
exit 0
;;
esac

# repository_dispatch requires only Contents: write, not Actions: write.
# pr-review.yml in .github-private listens for "pr-review-mention" and
# extracts client_payload.pr_url to run the cascade.
# extracts client_payload.pr_url (and force_review) to run the cascade.
gh api \
--method POST \
--header "Accept: application/vnd.github+json" \
/repos/petry-projects/.github-private/dispatches \
--field event_type=pr-review-mention \
--field "client_payload[pr_url]=$PR_URL"
--field "client_payload[pr_url]=$PR_URL" \
"${FORCE_ARGS[@]}"
echo "Dispatch sent for $PR_URL"
9 changes: 7 additions & 2 deletions .github/workflows/pr-review-mention-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
# - .github/workflows/pr-review-mention*.yml
# - test/workflows/pr-review-mention/**
# - standards/workflows/pr-review-mention.yml
# - .github/scripts/pr-review-mention/**
#
# Gates (all must pass before merge):
# 1. shellcheck — static analysis for the test gh stub
# 1. shellcheck — static analysis for the test gh stub and force-review lib
# 2. bats — guard (self-trigger exclusion) + ack (no bot self-mention)
# + force-review (break-glass force_review decision, #1281)
#
# Standard: pins issue #538 — the mention listener must never re-trigger itself.

Expand All @@ -19,13 +21,15 @@ on:
- '.github/workflows/pr-review-mention*.yml'
- 'test/workflows/pr-review-mention/**'
- 'standards/workflows/pr-review-mention.yml'
- '.github/scripts/pr-review-mention/**'
push:
branches:
- main
paths:
- '.github/workflows/pr-review-mention*.yml'
- 'test/workflows/pr-review-mention/**'
- 'standards/workflows/pr-review-mention.yml'
- '.github/scripts/pr-review-mention/**'

permissions:
contents: read
Expand Down Expand Up @@ -80,7 +84,8 @@ jobs:
- name: shellcheck
run: |
set -euo pipefail
shellcheck -x test/workflows/pr-review-mention/stubs/gh
shellcheck -x test/workflows/pr-review-mention/stubs/gh \
.github/scripts/pr-review-mention/lib/force-review.sh

- name: Run bats suite
run: bats --print-output-on-failure test/workflows/pr-review-mention/
Loading
Loading