feat(pr-review): internal-PR safety reviewer — review off-task-flow org PRs

Extend the inbound-PR reviewer beyond external/fork PRs to internal org-repo
PRs that bypassed the agent task-flow (a human-pushed branch). The org's own
in-flight integration PRs are skipped — a live task owns their branch and they
already pass QA + PM review — so the reviewer only flags off-process PRs.

- config: internal_pr_enabled (default OFF, like external_pr_enabled)
- PR_REVIEW_SOURCES = (external_pr, internal_pr); generalize dispatch, dedup,
  the decision queue, the git-gate exemption, and supersede to both sources
- TaskService.active_task_owns_branch (skip lifecycle PRs) + ingest source param
  with source-aware wording
- poll loop runs when EITHER flag is on; _ingest_pr_if_reviewable picks the
  source per PR (external: flag+author-allow; internal: flag+not-task-owned)
- 11 unit tests (decision logic + branch-ownership)
This commit is contained in:
Renn F
2026-06-17 16:50:09 +02:00
parent 34de96397f
commit 66a8ad40eb
5 changed files with 257 additions and 47 deletions
@@ -108,3 +108,35 @@ async def test_dismiss_rejects_non_external_pr() -> None:
svc = TaskService(session)
_bind(svc, "get", AsyncMock(return_value=task))
assert await svc.dismiss_external_pr_review(uuid4()) is None
# ---------------------------------------------------------------------------
# active_task_owns_branch — the internal-PR "is this a lifecycle PR?" check (#3)
# ---------------------------------------------------------------------------
def _branch_service(*, found: bool) -> TaskService:
res = MagicMock()
res.first.return_value = ("some-task-id",) if found else None
session = MagicMock()
session.execute = AsyncMock(return_value=res)
return TaskService(session)
@pytest.mark.asyncio
async def test_active_task_owns_branch_true_when_live_task_holds_it() -> None:
assert (
await _branch_service(found=True).active_task_owns_branch("feature/x") is True
)
@pytest.mark.asyncio
async def test_active_task_owns_branch_false_when_no_live_task() -> None:
svc = _branch_service(found=False)
assert await svc.active_task_owns_branch("feature/x") is False
@pytest.mark.asyncio
async def test_active_task_owns_branch_false_for_empty_branch() -> None:
# No branch → cannot be owned; never hits the DB.
assert await TaskService(MagicMock()).active_task_owns_branch("") is False