Skip to content

fix(buds): stop unrelated PRs leaking onto PROD / UAT tabs - #193

Merged
mickyarun merged 5 commits into
mainfrom
fix/bud-pr-linking
Jun 3, 2026
Merged

fix(buds): stop unrelated PRs leaking onto PROD / UAT tabs#193
mickyarun merged 5 commits into
mainfrom
fix/bud-pr-linking

Conversation

@mickyarun

Copy link
Copy Markdown
Owner

Summary

The release-stage tabs (PROD, UAT) on BUD detail were showing every open PR raised to the configured branch on any impacted repo — including PRs linked to a different BUD entirely. The DEVELOPMENT tab was unaffected because it filters strictly by bud_id.

Two coordinated fixes restore the intended behaviour:

1. Broaden the BUD-NNN resolver

  • New extract_bud_number regex: (?<![A-Za-z0-9-])bud-?0*(\d+)\b (case-insensitive). Negative lookbehind rejects mid-word (auth-bud-7) and run-on (abcbud-5) matches while accepting the punctuation real PR titles use: Closes #BUD-4, fix,BUD-3, revert: BUD-21 cleanup, [BUD-12], feat/BUD-7-rename, BUD-8 fix x.
  • resolve_bud_from_pr consults head branch first, then title so manually-named PRs whose author tagged the BUD in the title get linked.
  • pull_request.edited webhook action is now handled (gated on changes.title) so retitling an orphan PR to [BUD-008] X relinks it. Old and new BUD both record audit timeline events (pr_unlinked / pr_linked) for symmetry, and both get a re-check of check_all_repos_have_prs so the development → code_review auto-advance stays consistent.
  • The duplicate _BUD_BRANCH_RE in app/mcp/handlers_hooks.py is removed; both paths now flow through the single extract_bud_number seam.

2. Tighten the stage-tab query

list_open_for_bud_with_repo changes from OR(bud_id == X, repo_id IN impacted) to:

bud_id = X OR (bud_id IS NULL AND repo_id IN impacted)

The IS NULL arm preserves aggregate release PRs (develop → main) that legitimately carry no single owning BUD — those still flow through. PRs linked to a different BUD on the same impacted repo no longer leak.

3. One-shot backfill

scripts.backfill_pr_bud_links re-links existing orphan PRs via the broadened resolver. Defaults to --skip-merged (only OPEN PRs touched). Passing --include-merged opens a runtime CONFIRM prompt because the next release-detection walk uses the SHA→BUD map and can fire new merged_to_{stage} events / auto-close orphaned BUDs. --dry-run to preview. --yes for non-interactive runs.

Test plan

  • cd backend && pytest tests/services/test_pr_auto_transition.py tests/repositories/test_pull_request_open_for_bud_predicate.py — 29 cases pass (parametrized accept/reject + SQL predicate shape).
  • Full backend suite green: 1849 passed, 0 failed.
  • ruff / mypy clean on every touched file.
  • Run python -m scripts.backfill_pr_bud_links --dry-run on prod, eyeball the mapping diff.
  • Open a closed BUD that currently shows unrelated PRs on the PROD tab. After deploy + backfill, only PRs that genuinely reference this BUD remain visible.
  • Smoke-test pull_request.edited: open a PR without a BUD reference, retitle to [BUD-008] X, confirm the PR appears under BUD-008 on next webhook.

Verification snippets

$ cd backend && pytest tests/services/test_pr_auto_transition.py -q
29 passed in 0.4s
>>> from app.services.pr_auto_transition import extract_bud_number
>>> extract_bud_number("Closes #BUD-4")
4
>>> extract_bud_number("auth-bud-7")   # mid-word, must reject
>>> extract_bud_number("[BUD-008] Fix Y")
8

The release-stage tabs (PROD, UAT) were showing every open PR raised to
the configured branch on any impacted repo — including PRs linked to a
different BUD entirely. Root cause was the OR-of-equalities predicate
in ``list_open_for_bud_with_repo`` which let ``repo_id IN impacted``
bypass the BUD link.

Two coordinated fixes:

1. **Broaden the BUD-NNN resolver.** ``extract_bud_number`` now scans
   PR titles in addition to head branches, with a negative-lookbehind
   regex that rejects mid-word and run-on matches (``auth-bud-7``,
   ``abcbud-5``) while accepting the punctuation real titles use
   (``Closes #BUD-4``, ``fix,BUD-3``, ``revert: BUD-21 cleanup``). The
   webhook handler now also processes ``pull_request.edited`` events
   gated on ``changes.title``, so retitling an orphan PR to
   ``[BUD-008] X`` relinks it on the next webhook. Old + new BUD both
   get audit timeline events. The duplicate regex in ``mcp.handlers_hooks``
   is removed in favour of the single seam.

2. **Tighten the stage-tab query.** ``list_open_for_bud_with_repo`` now
   uses ``bud_id == X OR (bud_id IS NULL AND repo_id IN impacted)``.
   The IS-NULL arm preserves aggregate release PRs (``develop → main``)
   that legitimately carry no single owning BUD; PRs linked to a
   different BUD on the same impacted repo no longer leak.

Adds ``scripts.backfill_pr_bud_links`` to re-link existing orphan PRs
using the broadened resolver. Defaults to open PRs only; an explicit
``--include-merged`` opts in to merged-PR backfill behind a CONFIRM
prompt because the next release-detection walk can fire new
``merged_to_{stage}`` timeline events and auto-close BUDs that were
previously orphaned in the release chain.

Tests: 5 parametrized-accept and 8 parametrized-reject cases pin the
matcher; predicate-shape tests pin the three-way SQL form.

Signed-off-by: Arun Rajkumar <mickyarunr@gmail.com>
mickyarun added 3 commits June 3, 2026 10:58
The previous over-match fix preserved aggregate release PRs by keeping
``bud_id IS NULL`` rows on impacted repos visible — but that pulled in
unrelated PRs to ``main`` / ``release/*`` that simply happened to share
a repo with the BUD (e.g. an ATOA-9396 PR appearing on BUD-004's PROD
tab even though it has no BUD reference at all).

Real semantics: a PR belongs on a BUD's release-stage tab only when
its head ref or title carries that BUD's number. Multi-BUD release
branches like ``release/bud-001-bud-004-bud-007`` should surface on
EACH referenced BUD's tab, not on every impacted-repo BUD.

Adds two helpers in ``pr_auto_transition``:
- ``extract_all_bud_numbers(text)`` returns every BUD number found.
- ``pr_references_bud(num, head, title)`` is the call site.

Loosens the matcher's left lookbehind so digit-then-hyphen prefixes
are allowed (``-bud-`` after a digit is the multi-BUD chain pattern),
while keeping letter-then-hyphen prefixes rejected (``auth-bud-7``
mid-word case is still rejected).

The release-stage filter in ``bud_prs.py`` now applies the content
guard to any PR that isn't directly linked to this BUD — directly
linked PRs short-circuit so a user-edited link wins over text matching.

Tests pin the multi-BUD branch parser and the user's exact reported
case (``release/bud-001-bud-004`` matches BUD-1 and BUD-4 but not
BUD-7; unrelated ``ATOA-9396`` PR no longer leaks onto BUD-4's tab).

Signed-off-by: Arun Rajkumar <mickyarunr@gmail.com>
The edited webhook handler only synced ``title`` — base-branch and
head-branch changes from GitHub were silently dropped. When a PR was
retargeted from ``main`` to ``develop`` (the BUD-004 / PR-1997 case),
the local row kept ``base_branch = main`` and the release-stage filter
kept matching the PR onto the PROD tab because ``branch_matches``
runs against the stale value.

Two coordinated changes:

* Dispatcher widens the ``edited`` action gate. We now act when the
  payload's ``changes`` carries ``title`` OR ``base`` — the two fields
  that affect either the BUD link or the release-stage filter. Body /
  label edits stay silent.
* Handler also assigns ``pr.base_branch`` and ``pr.head_branch`` from
  the latest pr_data on every invocation. Cheap, and prevents the same
  drift on future force-pushes or base-branch edits.

Does NOT retroactively fix already-stale rows: an operator-triggered
sync script or a webhook replay is needed for those. The unit test
also pins that the loose space-form title ``Bud 004/processing loader``
does NOT satisfy ``pr_references_bud`` on its own; the head branch
``bud-004/...`` is the reliable link source.

Signed-off-by: Arun Rajkumar <mickyarunr@gmail.com>
Code review of the post-merge fixes flagged two real regressions and
one auto-transition risk:

* The relaxed regex ``(?<![A-Za-z])(?<![A-Za-z]-)`` accepted
  ``1bud-3``, ``sha7bud-5``, ``v2bud-5``, and ``auth1-bud-7`` —
  digit-glued mid-word and prefix-with-trailing-digit shapes that
  are not real BUD references. Revert to the strict
  ``(?<![A-Za-z0-9-])`` prefix that uniformly rejects letters,
  digits, and hyphens on the left. Multi-BUD release branches are
  now handled by a SEPARATE chain matcher: ``extract_all_bud_numbers``
  finds each strict ``bud-NNN`` match and then walks forward through
  any ``-bud-NNN-bud-NNN`` tail. Six new parametrize-reject cases
  pin the digit-glued and trailing-digit-prefix rejections.

* ``_handle_pr_edited`` previously called ``resolve_bud_from_pr`` on
  every invocation. Now that the dispatcher also fires on base edits,
  unconditional re-resolution risked unlinking a PR whose title
  carried a once-accepted-now-rejected reference (the regex tightening
  is exactly that kind of contract change). Gate the relink to actual
  title changes; base / head edits still sync ``pr.base_branch`` and
  ``pr.head_branch`` but do not touch ``pr.bud_id``.

Auto-transition audit confirmed clean:
* ``check_all_repos_have_prs`` / ``check_all_prs_merged`` key off
  ``pr.bud_id``, not the text content — the matcher tightening only
  affects NEW PR-open / title-edit links, not existing rows.
* The development → code_review threshold is unaffected by base
  edits because the gate above blocks the only path that could have
  unlinked an existing link on a base-only edit.

Full backend suite green: 1887 passed.

Signed-off-by: Arun Rajkumar <mickyarunr@gmail.com>
@mickyarun
mickyarun force-pushed the fix/bud-pr-linking branch from 2092b55 to 8a09762 Compare June 3, 2026 06:25
@mickyarun
mickyarun merged commit cd3efc5 into main Jun 3, 2026
14 checks passed
@mickyarun
mickyarun deleted the fix/bud-pr-linking branch June 3, 2026 06:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant