Repository navigation
[qa-lead reach 3/3] The pull_request advisory surface has no deployable form — decide and implement how it ships beyond .github-private #1869
Description
Story
As a maintainer of a repo other than .github-private,
I want qa-lead's pull_request advisory surface to be deployable to my repo,
so that the automatic advisory from #1646 is not permanently confined to one repo.
Phase 2 of epic #1643. The router half is tracked in petry-projects/.github (#783 ring soak, and the DEPLOYABLE_WORKFLOWS story). This issue is the harder half — unlike the router, this surface has no deployable form at all.
Why it cannot simply be listed as deployable
.github/workflows/qa-lead-pr-advisory.yml is self-contained, not a thin caller stub. As of 2026-09-21 it:
- checks out this repo and
sourcesscripts/qa-lead-advisory-gate.sh(a script that exists only here), - calls
./.github/workflows/persona-runner-reusable.yml— a local reusable, not an org-published one, - and has no
standards/workflows/template inpetry-projects/.github.
So there is nothing for the standards sweep to carry, and copying the file into another repo would reference scripts and a reusable that do not exist there. Contrast persona-mention.yml, which is already a thin stub pinned to an org channel.
The decision this issue must make first
Choose one and justify it in the PR:
- (a) Publish the persona runtime as an org reusable. Move/copy
persona-runner-reusable.ymltopetry-projects/.github, give it apersona-runner/v1-*channel viacut-release.sh, vendor or relocateqa-lead-advisory-gate.shso it travels with the reusable, and authorstandards/workflows/qa-lead-pr-advisory.ymlas a thin caller stub. Highest effort, and the only option that matches how every other fleet workflow is shipped. - (b) Fold the advisory trigger into the existing mention/router path so the
pull_requestevent reaches the already-published router rather than a second runtime. Less duplication; changes the event model and needs care with the Persona mention surface ignores needs-human-review / dev-lead:needs-human — a human brake stops the PR gate but not a mention #1745 stop-marker semantics that now live in the router. - (c) Keep it repo-local and say so. Document that the
pull_requestsurface is.github-private-only by design, and updatepersonas/qa-lead/persona.ymland the README so the declared surface matches reality — the [qa-lead S4] Make persona.yml's declared surfaces agree with what is actually deployed #1647 rule that a declared surface must be a served one applies fleet-wide, not just here.
Do not start implementing before the decision is written down. This is an architecture choice with a fleet-wide blast radius, and options (a) and (c) point in opposite directions.
Acceptance Criteria
- The decision above is recorded — an ADR under
docs/adr/if (a) or (b), since it changes where the persona runtime lives. - If (a) or (b): qa-lead's
pull_requestadvisory runs in at least one repo that is not.github-private, evidenced by a real run link in the PR. - If (c):
persona.yml,personas/qa-lead/README.mdand the interaction contract state the restriction, andscripts/validate-personas.py/validate-interaction-model.shenforce it rather than trusting prose. - Whichever path: the fleet-wide claim in
prompts/qa-lead/advisory.mdstays true everywhere it can be printed (the [qa-lead S1] Provision <id>:hands-off labels across the fleet — the opt-out is inert where the persona actually runs #1644 rule). - No caller stub is hand-edited in a consumer repo; deployment goes through the standards sweep (AGENTS.md).
Dev Notes
- Sequencing: the router's ring soak (pilot(mcp): scan PR diffs for secrets via run_secret_scanning #783) and its
DEPLOYABLE_WORKFLOWSstory gate qa-lead's mention reach. This issue is independent of them — but if (b) is chosen, it becomes dependent on both, because the router would then serve this surface too. scripts/qa-lead-advisory-gate.shreusespr_has_escalation_labelfromscripts/lib/pr-automation-budget.sh. Whatever option is chosen, the stop-marker semantics reconciled in Persona mention surface ignores needs-human-review / dev-lead:needs-human — a human brake stops the PR gate but not a mention #1745 must hold on the new surface — a human hold must still bind.- Relevant history: [qa-lead S3] Serve the pull_request surface so qa-lead runs without being summoned #1646 (built this surface), [qa-lead S4] Make persona.yml's declared surfaces agree with what is actually deployed #1647 (declared surfaces must match served ones), Persona mention surface ignores needs-human-review / dev-lead:needs-human — a human brake stops the PR gate but not a mention #1745 (stop markers), Epic: qa-lead — close the gap between the declared persona and the served one #1643 (epic).
DECISION — option (b), by solution-architect advisory 2026-09-23 (implement this; the decision is made, do not re-open it)
The architect advisory on this issue decided (b): route the pull_request event into the existing mention router, rather than publishing a second runtime.
Grounds (quoted): governing ADR-0007 (one agent-ingress stub per repo, not one stub per role), reinforced by ADR-0006. ADR-0007 fact 4 already names persona-mention → persona-runner as the fleet's hub-and-spoke model; publishing a second runtime for this surface "repeats, at the ingress-path level, the exact per-role-stub proliferation ADR-0007 just collapsed." Option (a)'s persona-runner/v1-* channel is not itself the problem — the second ingress path it requires is.
Scope of THIS issue (this repo only)
The router change is cross-repo and is filed separately in petry-projects/.github. Do not attempt it here and do not edit any caller stub.
- Write the ADR under
docs/adr/recording decision (b): the context (this surface had no deployable form), the decision, the ADR-0007/0006 grounds above, and the consequences — one ingress path per repo, thepull_requestsurface served by the shared router, and the localqa-lead-pr-advisory.ymlretired once the router serves it. Reference this issue and the advisory comment. - Do NOT retire
.github/workflows/qa-lead-pr-advisory.ymlyet. It is the only thing serving the advisory today. Retiring it before the router change is released to its channel would leave the surface dead — the same ordering failure as landing a fail-closed validator ahead of its data. Sequence it as a follow-up gated on the router change being onpersona-mention/v1-next. - State the resulting contract truthfully in the meantime. If
personas/qa-lead/persona.yml,personas/qa-lead/README.mdorpersonas/qa-lead/interaction.ymlimply thepull_requestsurface is fleet-available, correct them to say it is served locally today and by the router after the pending change — the [qa-lead S4] Make persona.yml's declared surfaces agree with what is actually deployed #1647 rule that a declared surface must be a served one.
Hard acceptance criterion from the advisory
The #1745 stop-marker semantics must hold for a pull_request-sourced dispatch, not only a mention-sourced one. The architect calls this "the real open risk, not the routing choice." The ADR must state how that will be verified before the local surface is retired.
If (b) proves infeasible
Do not fall back to (a) silently. Record the specific reason the router could not absorb the event — the advisory notes that is the fact a future ADR would need to supersede this reasoning. (c) is the fallback only if both fail.
Budget guard
- The ADR (AC test issue from agent #1 of this issue). That is the commit that matters — it unblocks the router work and records the reasoning while it is fresh.
- The contract-truthfulness corrections (item 3).
Stop after step 1 with a green PR if budget runs low.
Activity
don-petry commentedon Sep 23, 2026
@petry-projects/solution-architect — decision requested on this issue.
The qa-lead
pull_requestadvisory surface (.github/workflows/qa-lead-pr-advisory.yml) has no deployable form: it is self-contained rather than a thin caller stub,sourcesscripts/qa-lead-advisory-gate.sh(present only in this repo), calls a local./.github/workflows/persona-runner-reusable.yml, and has nostandards/workflows/template. So the standards sweep has nothing to carry, and copying the file into another repo would reference scripts and a reusable that do not exist there.The three options are written up in the issue body with their trade-offs:
persona-runner/v1-*channel) + author a thin caller-stub template — highest effort, matches how every other fleet workflow ships;pull_requesttrigger into the already-published mention router so one runtime serves both surfaces — less duplication, changes the event model, and must preserve the Persona mention surface ignores needs-human-review / dev-lead:needs-human — a human brake stops the PR gate but not a mention #1745 stop-marker semantics that now live in the router;persona.yml/ the README / the interaction contract, enforced by the validators rather than prose.Context that post-dates the issue body: the router half is fully shipped — combsmith/.github#1134 merged,
persona-mentionpromoted throughv1-stable(all tiers atfde249e0), and combsmith/.github#1159 merged so the caller stub now deploys fleet-wide. Nine repos receive it on the next sweep. That materially strengthens option (b): the router is now a deployed, channel-managed, fleet-wide runtime, which it was not when this issue was filed.Please decide and justify, per AC #1 — an ADR under
docs/adr/if (a) or (b), since either moves where the persona runtime lives. Epic #1643 stays open on this decision.don-petry commentedon Sep 23, 2026
Solution Architect — structural advisory
Governing ADR: ADR-0007 (one agent-ingress stub per repo, not one stub per role) — reinforced by ADR-0006 (shared-runtime personas are not ring-registered).
Alignment: drifts (a) / aligns (b). ADR-0007 fact 4 already names
persona-mention→persona-runneras the fleet's hub-and-spoke model and says plainly: "what is not consolidated is the ingress file, and consolidating that needs no receiver." Publishing a second runtime for thepull_requestsurface repeats, at the ingress-path level, the exact per-role-stub proliferation ADR-0007 just collapsed. ADR-0006 already anticipated the shared runtime itself carrying a channel tag "if one is ever wired," so (a)'spersona-runner/v1-*channel isn't itself the problem — the second ingress path it requires is.What I'd shore up:
pull_requestevent into the existing router (b) rather than standing up a second published runtime — that's the ADR-0007 consolidation, not an exception to it.pull_request-sourced dispatch, not only mention-sourced ones — that's the real open risk, not the routing choice.Escalate? no — the issue already gates implementation on a written decision; this is ordinary architecture work.
Advisory only — I comment, I do not change code. Ground: BMAD Method architect (bmad-agent-architect v6.8.0). Opt out on this item with the solution-architect:hands-off label.
don-petry commentedon Sep 23, 2026
Dev-Lead Implementation Plan
Issue: #1869 — [qa-lead reach 3/3] The pull_request advisory surface has no deployable form — decide and implement how it ships beyond .github-private
Scope
Record the already-made architecture decision (b) — route the
pull_requestadvisory event into the existing shared persona-mention router rather than publishing a second runtime — as a new ADR underdocs/architecture/adr/, and make the persona contract state the surface truthfully in the interim (served locally today, by the router after the pending cross-repo router change). The router change itself is cross-repo and out of scope here; no caller stub is edited and the localqa-lead-pr-advisory.ymlis not retired.Implementation Checklist
docs/architecture/adr/0009-*.md— new ADR: context (surface had no deployable form), decision (b), ADR-0007/0006 grounds, consequences (one ingress path per repo;pull_requestserved by the shared router; localqa-lead-pr-advisory.ymlretired only as a follow-up gated on the router change reachingpersona-mention/v1-next), and how the Persona mention surface ignores needs-human-review / dev-lead:needs-human — a human brake stops the PR gate but not a mention #1745 stop-marker semantics will be verified on apull_request-sourced dispatch before the local surface is retired. References [qa-lead reach 3/3] The pull_request advisory surface has no deployable form — decide and implement how it ships beyond .github-private #1869 + the 2026-09-23 solution-architect advisory.personas/qa-lead/persona.yml—pull_requestsurface note: keepenabled: true(the local workflow still serves it), clarify it is served locally in.github-privatetoday and moves to the shared router per the ADR.personas/qa-lead/interaction.yml— mirror the same clarification in the contract comments.personas/qa-lead/README.md— clarify the served-surface section the same way, pointing at the ADR.Tests to Write
enabled: true, so the existing surface-liveness invariant ([qa-lead S4] Make persona.yml's declared surfaces agree with what is actually deployed #1647) stays satisfied. Verification is by running the existing validators.Test Command
bash .dev-lead/scripts/dev-lead-lint.shpluspython3 personas/validate-personas.pyandbash scripts/validate-interaction-model.shdon-petry commentedon Sep 23, 2026
Dev-Lead: Progress (in progress — not yet durable)
Plan Execution (intended)
pull_requestadvisoryevent through the shared
persona-mentionrouter rather than publishing asecond runtime — with the ADR-0007/0006 grounds, the consequences (one ingress
path per repo; local
qa-lead-pr-advisory.ymlretired only as a follow-upgated on the router change reaching
persona-mention/v1-next), and the hardcriterion that Persona mention surface ignores needs-human-review / dev-lead:needs-human — a human brake stops the PR gate but not a mention #1745 stop-marker semantics be verified on a
pull_request-sourced dispatch before the local surface is retired.qa-lead-pr-advisory.ymland did not edit any callerstub (the router change is cross-repo, filed separately).
persona.yml,interaction.yml,and
README.md: thepull_requestsurface is served locally in.github-privatetoday and by the router after the pending change; keptenabled: truesince the surface is served today ([qa-lead S4] Make persona.yml's declared surfaces agree with what is actually deployed #1647).Local check output (provisional — not the pushed run)
Files touched (this workspace, not yet pushed)
docs/architecture/adr/0009-qa-lead-pull-request-surface-routes-through-the-shared-router.md: new ADR (decision b).personas/qa-lead/persona.yml:pull_requestsurface note — served locally today, router after ADR-0009.personas/qa-lead/interaction.yml: contract comment mirrors the same.personas/qa-lead/README.md: served-surface section clarified + ADR link.Notes
that matters (unblocks the cross-repo router work); the contract corrections
are the secondary item. No implementation of the router itself here.
exception — no observable behavioral change; verification is by the existing
validators above, which stay green.
don-petry commentedon Sep 23, 2026
Dev-Lead: Implementation Complete — PR #1892
The implementation for issue #1869 is durable: commits are pushed and a pull request is open. This record is backed by verifiable artifacts you can confirm in one click.
b51280c9fbbacefe1c32eed3a6d68a44066dd5a9main: 4 file(s), +180/-2 linesReview happens on the PR. Acceptance criteria and test results are evidenced by the PR's diff and CI checks — not asserted here. If a later stage fails, this claim is retracted in place.
feat: implement issue #1869 — [qa-lead reach 3/3] The pull_request ad…