fix(gateway): resolve adversarial-review findings on the pr_reviewer flow

An adversarial review of the feature found two blocking defects (both would
surface the moment external_pr_enabled is turned on) plus hardening gaps:

- HIGH: the enforcement legacy role-gate overlay OVERWROTE spec-derived roles,
  so pr_reviewer was erased from the (in_progress->completed) edge it shares
  with the PM self-complete gate — the review task could never complete. Fix:
  UNION legacy + spec roles instead of overwriting (also preserves the legacy
  'add roles' intent on every shared edge).
- HIGH: claim_pr_review routed claim+start through the verb runner, which hit
  start()'s plan gate (planless review task -> None -> crash/respawn loop) and
  auto-created+pushed a stray branch (violating the read-only/branchless
  invariant). Fix: mirror QA's claim_review — a verb-body TaskService.pr_review_claim
  does pending->in_progress with no plan and no branch.
- MED: add the pr_reviewer Write(*)/Edit(*) deny at the permission layer (it
  ingests untrusted PR diffs — make read-only explicit, not implicit).
- MED: regenerate the verb-table artifacts (the schemas existed but the
  generator had not been re-run; the agent prompt showed 'unknown' signatures).

ruff + mypy clean (279 files); foundation + gateway suites green (5205 passed).
This commit is contained in:
Renn F
2026-06-16 11:48:24 +02:00
parent 831321f436
commit 5bea82dbbc
6 changed files with 72 additions and 20 deletions
+2 -2
View File
@@ -7,10 +7,10 @@
| Verb | Body schema |
|------|-------------|
| `claim_pr_review` | `claim_pr_review(unknown — no Pydantic schema)` |
| `claim_pr_review` | `claim_pr_review(task_id: UUID)` |
| `give_me_work` | `give_me_work()` |
| `i_am_idle` | `i_am_idle()` |
| `post_pr_review` | `post_pr_review(unknown — no Pydantic schema)` |
| `post_pr_review` | `post_pr_review(task_id: UUID, body: str, event: str = 'REQUEST_CHANGES')` |
### Content (do) tools
+2 -2
View File
@@ -255,10 +255,10 @@ real tools live in their agent_sdk drivers, not role_config.
| Verb | Body schema |
|------|-------------|
| `claim_pr_review` | `claim_pr_review(unknown — no Pydantic schema)` |
| `claim_pr_review` | `claim_pr_review(task_id: UUID)` |
| `give_me_work` | `give_me_work()` |
| `i_am_idle` | `i_am_idle()` |
| `post_pr_review` | `post_pr_review(unknown — no Pydantic schema)` |
| `post_pr_review` | `post_pr_review(task_id: UUID, body: str, event: str = 'REQUEST_CHANGES')` |
### Content (do) tools