Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
468fa37
feat: implement issue #1905 — [qa-lead reach 3/3] persona-runner igno…
don-petry Sep 23, 2026
3e6cffb
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Sep 23, 2026
6739306
chore: dev-lead update (review-changes) [skip ci-relay]
don-petry Sep 23, 2026
74670c6
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Sep 23, 2026
0a31dab
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Sep 24, 2026
88d8560
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Sep 24, 2026
1a189eb
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Sep 24, 2026
72a3288
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Sep 24, 2026
4833bd8
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Sep 25, 2026
d1c723e
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Sep 25, 2026
bbe6d64
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Sep 25, 2026
c7d8b9a
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Sep 26, 2026
9718e65
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Sep 26, 2026
322654b
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Sep 27, 2026
533ff1a
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Sep 27, 2026
85d5336
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Sep 29, 2026
39a2787
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Sep 29, 2026
07036f5
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Sep 30, 2026
7c959cf
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Sep 30, 2026
4c0ee5c
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 1, 2026
adba0c8
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 1, 2026
08dcfa7
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 1, 2026
63cd63a
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 1, 2026
28c82d0
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 1, 2026
98424f2
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 1, 2026
8be342c
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 1, 2026
c8cc4c2
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 1, 2026
224526b
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 2, 2026
8aa99b1
fix(reviews): address review comments [skip ci-relay]
don-petry Oct 2, 2026
8b74ee1
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 2, 2026
0afc8f7
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 2, 2026
40498b0
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 2, 2026
dfb1dcb
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 2, 2026
18f7704
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 2, 2026
e777810
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 2, 2026
8e47bdd
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 3, 2026
3037da5
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 3, 2026
0287db3
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 3, 2026
ea0e6da
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 3, 2026
388e23d
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 4, 2026
0dd51eb
fix(bot): invert fail-closed logic in persona-runner-reusable.yml to …
don-petry Oct 4, 2026
ab68dcc
fix(reviews): restore pre-gate failure on unavailable signals, keep p…
don-petry Oct 4, 2026
ea5d7ef
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 5, 2026
65ef371
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 6, 2026
8160346
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 6, 2026
8c19c33
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 6, 2026
d5d8247
docs: clarify that private source repositories are out of scope for q…
don-petry Oct 6, 2026
7195672
fix(reviews): qualify private-source limitation for qa-lead pull_requ…
don-petry Oct 6, 2026
07f9e99
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 7, 2026
0f9e230
fix: add issues:read permission to qa-lead-pr-advisory gate job
don-petry Oct 7, 2026
de910d2
fix(reviews): fail closed on empty gate decision; fix permissions com…
don-petry Oct 7, 2026
81ff6dd
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 8, 2026
f0d62f9
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 8, 2026
d777d6e
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 9, 2026
8889453
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 9, 2026
c2167a3
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 9, 2026
ec6b892
fix(gate-functions): add explicit return statements to prevent set -e…
don-petry Oct 9, 2026
a802ed7
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 9, 2026
7c2fdbd
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 9, 2026
89d4d8a
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 9, 2026
5ee63e4
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 9, 2026
b18cf98
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 9, 2026
b598a1d
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 9, 2026
e01c69f
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 9, 2026
dd93f5e
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 9, 2026
0c3f4e9
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 9, 2026
a08494a
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 9, 2026
8242dd9
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 9, 2026
d099852
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 9, 2026
ea41f1f
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 9, 2026
c958249
fix(reviews): qa-lead pre-gate pipes gh --slurp to external jq; gh st…
don-petry Oct 9, 2026
2a627a6
fix(reviews): persona with no registered pull_request pre-gate skips …
don-petry Oct 9, 2026
f0b946c
Merge branch 'main' into dev-lead/issue-1905-20260923-0336
don-petry Oct 9, 2026
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
1 change: 1 addition & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@
tests/dev-lead/unit/test_verify_approval_review.bats \
tests/test_qa_lead_test_surface.bats \
tests/test_qa_lead_advisory_gate.bats \
tests/test_persona_pr_pregate.bats \
tests/test_qa_lead_holdout_calibration.bats \
tests/test_advisory_offline_mode.bats \
tests/agent_ingress_reference.bats \
Expand Down Expand Up @@ -267,7 +268,7 @@
timeout-minutes: 5
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# NOTE: persist-credentials is intentionally left at the default (true) —

Check warning on line 271 in .github/workflows/lint.yml

View workflow job for this annotation

GitHub Actions / Lint

271:9 [comments-indentation] comment not indented like content
# vci_resolve_reusable needs `git fetch origin` to authenticate against this
# private repo when resolving same-repo channel tags. Setting
# persist-credentials: false would silently push every such caller into the
Expand Down
62 changes: 57 additions & 5 deletions .github/workflows/persona-runner-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,22 @@
required: false
type: string
default: 'advisory'
surface:
# Which surface the router dispatched from. Defaults to 'mention' so
# existing mention dispatches (and the local qa-lead-pr-advisory.yml
# advise job, which never sets it) are unchanged. When 'pull_request',
# the persona's event pre-gate runs before the engine (#1905).
Comment thread
don-petry marked this conversation as resolved.
Resolved
required: false
type: string
default: 'mention'
event_action:
# The underlying event action the router observed (e.g. opened,
# ready_for_review). Forwarded for parity with the router payload and
# future per-persona gates; the surface's declared events are bound by
# the router (ADR-0009), so the qa-lead decision does not consider it.
required: false
type: string
default: ''
secrets:
CLAUDE_CODE_OAUTH_TOKEN:
required: true
Expand All @@ -89,7 +105,7 @@
# restoring the ADR-0005 position; reverses #1650, fixes #1734). Chosen per
# persona from runtime.identity.credential, never a repo-wide default.
required: false
# GH_PAT_WORKFLOWS removed (#1650): the broadly-scoped workflows PAT is no

Check warning on line 108 in .github/workflows/persona-runner-reusable.yml

View workflow job for this annotation

GitHub Actions / Lint

108:7 [comments-indentation] comment not indented like content
# longer a posting fallback here — least privilege, and posting under it
# attributed advisories to a human owner account.

Expand All @@ -108,7 +124,7 @@
timeout-minutes: 15
permissions:
contents: read # checkout THIS repo's tooling + prompts. The agent reads
# the public source repo with github.token; the comment is

Check warning on line 127 in .github/workflows/persona-runner-reusable.yml

View workflow job for this annotation

GitHub Actions / Lint

127:24 [comments-indentation] comment not indented like content
# posted by the workflow with the persona's manifest PAT.
steps:
- name: Checkout persona tooling + prompts
Expand Down Expand Up @@ -151,6 +167,42 @@
;;
esac

- name: Event pre-gate — suppress the pull_request surface before the engine
# #1905. The router serves qa-lead's pull_request advisory here with
# surface=pull_request, but binds only the GENERIC brakes — it does NOT
# carry the persona-specific suppressors (no-test-surface, budget-
# exhausted, already-advised) the local qa-lead gate enforces. Run the
# persona's event pre-gate BEFORE the engine so a router-served PR
# dispatch is suppressed with the SAME fidelity as the local surface;
# otherwise the engine runs and posts on every trusted PR. For a mention
# dispatch (surface defaults to 'mention') the gate is a no-op ('run'),
# so existing mention behaviour is unchanged. Fails CLOSED: an unreadable
# signal yields a skip (no engine, no post) with a ::error naming it.
id: pregate
if: steps.resolve.outputs.skip == 'false'
env:
# github.token reads the (public) source repo — the same read scope the
# agent step uses. The pre-gate only READS PR signals; it never posts.
GH_TOKEN: ${{ github.token }}
Comment thread
don-petry marked this conversation as resolved.
Resolved
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Resolved
Comment thread
don-petry marked this conversation as resolved.
Resolved
PERSONA: ${{ inputs.persona }}
SURFACE: ${{ inputs.surface }}
SOURCE_REPO: ${{ inputs.source_repo }}
ITEM_NUMBER: ${{ inputs.item_number }}
EVENT_ACTION: ${{ inputs.event_action }}
run: |
set -euo pipefail
# shellcheck source=scripts/persona-pr-pregate.sh
source scripts/persona-pr-pregate.sh

decision="$(persona_event_pregate \
"$PERSONA" "$SURFACE" "$SOURCE_REPO" "$ITEM_NUMBER" "$EVENT_ACTION" || true)"
Comment thread
don-petry marked this conversation as resolved.
Resolved
echo "persona pre-gate for '${PERSONA}' (surface=${SURFACE}) on ${SOURCE_REPO}#${ITEM_NUMBER}: ${decision}"
if [ "$decision" = "run" ]; then
echo "run=true" >> "$GITHUB_OUTPUT"
else
echo "run=false" >> "$GITHUB_OUTPUT"
fi

- name: Resolve the persona's posting identity from its manifest
# Source of truth for who a persona posts as: personas/<id>/persona.yml
# runtime.identity (the #1316/#1317 pattern). The runner honors the
Expand All @@ -159,7 +211,7 @@
# PATs only for the sanctioned posting identities; any other declared
# credential fails loudly rather than silently borrowing a default.
id: identity
if: steps.resolve.outputs.skip == 'false'
if: steps.resolve.outputs.skip == 'false' && steps.pregate.outputs.run == 'true'
env:
PERSONA: ${{ inputs.persona }}
run: |
Expand Down Expand Up @@ -199,7 +251,7 @@
# derived per-persona from the manifest (via the identity step), so every
# persona is covered without an enumerated list (AC #6).
id: authz_preflight
if: steps.resolve.outputs.skip == 'false'
if: steps.resolve.outputs.skip == 'false' && steps.pregate.outputs.run == 'true'
env:
# Same manifest-driven PAT selection as the post step — the token that
# WILL post is the token we probe. The agent step never sees these.
Expand Down Expand Up @@ -235,7 +287,7 @@
bash scripts/persona-authz-preflight.sh

- name: Install Claude Code
if: steps.resolve.outputs.skip == 'false'
if: steps.resolve.outputs.skip == 'false' && steps.pregate.outputs.run == 'true'
env:
CLAUDE_CODE_VERSION: ${{ vars.CLAUDE_CODE_VERSION || 'latest' }}
run: |
Expand All @@ -246,7 +298,7 @@
npm install -g "@anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}"

- name: Produce the advisory (agent — read only, no post)
if: steps.resolve.outputs.skip == 'false'
if: steps.resolve.outputs.skip == 'false' && steps.pregate.outputs.run == 'true'
id: advise
env:
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
Expand Down Expand Up @@ -297,7 +349,7 @@

- name: Enforce marker and post the comment (workflow — the only writer)
id: post
if: steps.resolve.outputs.skip == 'false' && steps.advise.outputs.skip_post == 'false'
if: steps.resolve.outputs.skip == 'false' && steps.pregate.outputs.run == 'true' && steps.advise.outputs.skip_post == 'false'
env:
# The PAT lives ONLY here. The agent step above never had it. WHICH
# PAT is used is driven by the persona manifest
Expand Down
14 changes: 12 additions & 2 deletions .github/workflows/persona-runner.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,15 @@
#
# The mention router (in petry-projects/.github) fires `persona-mention` at this
# repo with client_payload {persona, mode, source_repo, item_number, comment_url,
# requested_by}. This stub hands that payload to the reusable, which runs the
# addressed persona's advisory prompt.
# requested_by, surface, event_action}. This stub hands that payload to the
# reusable, which runs the addressed persona's advisory prompt.
#
# `surface` selects which suppressors the reusable applies before the engine:
# it defaults to `mention` (existing mention dispatches unchanged), and a
# router-served `pull_request` dispatch (surface=pull_request) triggers the
# persona's event pre-gate — test-surface / budget / already-advised for qa-lead
# (#1905). This reusable is referenced by a LOCAL `./` path (same commit), so the
# new inputs are declared in the same change — no channel-skew concern.
#
# Thin caller: no logic here beyond mapping the payload to inputs. All behaviour
# lives in the reusable. This repo is the `next` ring — the first, smallest
Expand All @@ -30,4 +37,7 @@ jobs:
comment_url: ${{ github.event.client_payload.comment_url }}
requested_by: ${{ github.event.client_payload.requested_by }}
mode: ${{ github.event.client_payload.mode }}
# Absent surface -> 'mention' so existing dispatches are unchanged (#1905).
surface: ${{ github.event.client_payload.surface || 'mention' }}
event_action: ${{ github.event.client_payload.event_action || '' }}
secrets: inherit
91 changes: 9 additions & 82 deletions .github/workflows/qa-lead-pr-advisory.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,89 +72,16 @@ jobs:
PR: ${{ github.event.pull_request.number }}
run: |
set -euo pipefail
# shellcheck source=scripts/qa-lead-advisory-gate.sh
source scripts/qa-lead-advisory-gate.sh
# shellcheck source=scripts/qa-lead-pr-gate.sh
source scripts/qa-lead-pr-gate.sh

# Fail closed: qa-lead advises only when EVERY signal is available.
# Silence is the safe default, so any signal-gathering API failure must
# SKIP — never degrade to an empty value ([] labels, [] events, "no
# marker") that would bypass a suppressor or duplicate an advisory.
skip_closed() {
echo "gate: $1 — failing closed (skip) for ${REPO}#${PR}"
echo "decision=skip" >> "$GITHUB_OUTPUT"
exit 0
}

# AC #3 — changed files. Fetch the raw JSON array of filenames so the
# list can be validated BEFORE it is flattened to the newline-delimited
# text qa_lead_test_surface consumes. An empty list on success is fine
# (an empty PR does not fire); only an API failure skips.
if ! files_json="$(gh api --paginate --slurp \
"repos/${REPO}/pulls/${PR}/files" --jq '[.[][].filename]')"; then
skip_closed "changed-file list unavailable"
fi

# Completeness: the /pulls/{n}/files endpoint caps at 3000 files, so a
# larger PR yields a truncated list that could omit a test/source path
# and mislabel the PR. Compare received vs. the declared changed_files
# count and fail closed (skip) on an incomplete list — never advise off
# partial data.
if ! declared_files="$(gh api "repos/${REPO}/pulls/${PR}" \
--jq '.changed_files')"; then
skip_closed "changed-file count unavailable"
fi
received_files="$(jq 'length' <<< "$files_json")"
if ! qa_lead_file_list_complete "$received_files" "$declared_files"; then
skip_closed "changed-file list incomplete (${received_files}/${declared_files})"
fi

# Control characters: a filename containing an LF would split into
# phantom records once flattened (a docs-only PR could forge a SOURCE
# path and trigger the advisory). Reject before flattening.
if qa_lead_paths_have_control_char "$files_json"; then
skip_closed "changed-file list contains control characters"
fi

changed_paths="$(jq -r '.[]' <<< "$files_json")"

# AC #4/#6 — label names as a JSON array. A labels API failure must NOT
# degrade to [] — that would bypass the opt-out and human-gating
# suppressors and advise a PR marked hands-off / needs-human-review.
if ! labels_json="$(gh api "repos/${REPO}/pulls/${PR}" \
--jq '[.labels[]?.name]')"; then
skip_closed "labels unavailable"
fi

# AC #5 — has a qa-lead advisory already been posted? Scan issue comments
# (the PR shares the issues comment stream) for the recursion marker. A
# comment API failure is indistinguishable from "no marker", so failing
# open here would risk a duplicate advisory — fail closed instead.
marker="<!-- persona:qa-lead -->"
if ! comment_bodies="$(gh api --paginate \
"repos/${REPO}/issues/${PR}/comments" --jq '.[].body')"; then
skip_closed "existing-advisory scan unavailable"
fi
if grep -qF "$marker" <<< "$comment_bodies"; then
existing_advisory=1
else
existing_advisory=0
fi

# AC #6 — is the per-PR automation budget exhausted? (#860/#926 breaker)
# gather_pr_automation_events returns non-zero when any API call fails;
# a failure must NOT degrade to an empty event list (an exhausted PR
# would then pass the budget gate during a transient outage), so skip.
budget_exhausted=0
if events_json="$(gather_pr_automation_events "$PR" "$REPO")"; then
if pr_budget_exhausted "$events_json"; then
budget_exhausted=1
fi
else
skip_closed "automation-budget events unavailable"
fi

decision="$(qa_lead_gate_decision \
"$changed_paths" "$labels_json" "$existing_advisory" "$budget_exhausted" || true)"
# The signal gathering + fire/skip decision lives in ONE shared script
# (#1905 AC #2/#4): the router-served pull_request surface calls the
# SAME qa_lead_pr_gather_and_decide via scripts/persona-pr-pregate.sh,
# so the suppressors are identical, not a copy. It fails closed — any
# unreadable signal yields a skip with a ::error naming it — so this
# workflow keeps advising only when EVERY signal is available.
decision="$(qa_lead_pr_gather_and_decide "$REPO" "$PR" || true)"
Comment thread
don-petry marked this conversation as resolved.
Resolved
echo "gate decision for ${REPO}#${PR}: ${decision}"
# Normalize to run|skip for the downstream `if`; keep the reason in logs.
if [ "$decision" = "run" ]; then
Expand Down
83 changes: 83 additions & 0 deletions scripts/persona-pr-pregate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/usr/bin/env bash
set -euo pipefail
# persona-pr-pregate.sh — the persona event pre-gate the shared runtime consults
# before running the engine (issue #1905).
#
# The mention router serves qa-lead's pull_request advisory by dispatching
# repository_dispatch:persona-mention with client_payload.surface=pull_request
# into .github/workflows/persona-runner-reusable.yml. The router binds the
# GENERIC brakes (stop markers, opt-out label, write-gate, trust floor, recursion
# axes, declared events); it does NOT carry the PERSONA-SPECIFIC suppressors the
# local qa-lead-pr-advisory.yml gate enforces (no-test-surface, budget-exhausted,
# already-advised). Without this pre-gate the runner posts on EVERY trusted PR.
#
# persona_event_pregate is what the runner calls after resolving the persona and
# BEFORE the engine:
# * surface != pull_request (mention, or absent -> mention): "run", no gate.
# Existing mention dispatches are unchanged (AC #1).
# * surface == pull_request + a persona with a registered pre-gate (qa-lead):
# run that gate — the ONE shared gather+decide (AC #2).
# * surface == pull_request + a persona with NO registered pre-gate: the generic
# already-advised marker check only, logging that no persona gate exists (AC #3).
# * any unreadable signal: fail closed (skip) with a ::error naming it (AC #3).
#
# Prints exactly one decision line on stdout: "run" or "skip:<reason>". Returns 0
# for run, 1 for skip. Diagnostics (::error/::notice) go to stderr.

_PERSONA_PREGATE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=scripts/qa-lead-pr-gate.sh
source "${_PERSONA_PREGATE_DIR}/qa-lead-pr-gate.sh"
# shellcheck source=scripts/lib/persona-runner.sh
source "${_PERSONA_PREGATE_DIR}/lib/persona-runner.sh"

# persona_generic_already_advised_gate <persona> <repo> <item>
# The fallback pull_request pre-gate for a persona with no registered
# suppressor set: the generic idempotency check only. Scans the item's comment
# stream for the persona's recursion marker; an existing marker means an
# advisory already landed -> skip. Fails closed on an unreadable comment stream.
persona_generic_already_advised_gate() {
local persona="$1" repo="$2" item="$3" marker bodies
marker="$(pr_agent_marker "$persona")"
if ! bodies="$(gh api --paginate \
"repos/${repo}/issues/${item}/comments" --jq '.[].body')"; then
Comment thread
don-petry marked this conversation as resolved.
Resolved
echo "::error::persona pull_request pre-gate: existing-advisory scan unavailable for ${repo}#${item} — failing closed (skip)" >&2
printf 'skip:signal-unavailable\n'
return 1
fi
if grep -qF "$marker" <<< "$bodies"; then
printf 'skip:already-advised\n'
return 1
fi
printf 'run\n'
}

# persona_event_pregate <persona> <surface> <repo> <item> [event_action]
# The single entry point the runner calls. See the header for the dispatch.
persona_event_pregate() {
local persona="$1" surface="${2:-mention}" repo="$3" item="$4"
# event_action ($5) is forwarded for parity with the router payload and future
# per-persona gates; the qa-lead decision does not consider it (the surface's
# declared events are bound by the router, per ADR-0009).

# A non-pull_request surface (mention, or absent -> mention) is unchanged: the
# pre-gate is a pull_request-only concern (AC #1).
if [ "$surface" != "pull_request" ]; then
printf 'run\n'
return 0
fi
Comment thread
don-petry marked this conversation as resolved.
Resolved
Comment thread
don-petry marked this conversation as resolved.
Resolved

case "$persona" in
qa-lead)
qa_lead_pr_gather_and_decide "$repo" "$item"
;;
*)
echo "::notice::no persona-specific pull_request pre-gate registered for '${persona}' — applying only the generic already-advised marker check" >&2
persona_generic_already_advised_gate "$persona" "$repo" "$item"
Comment thread
don-petry marked this conversation as resolved.
Resolved
;;
esac
}

# Allow direct CLI use for debugging: persona-pr-pregate.sh <persona> <surface> …
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
persona_event_pregate "$@"
fi
Loading
Loading