swarm-controller: post the review classifier's verdict on a review request
Requesting the forge account `review-classifier` as a reviewer now runs the repo's rules against the PR and posts the result as that account's review, on the head commit the run read. A push starts no run; only a fresh review request does. - An instance-wide `pull_request_review_request` system hook delivers to `/webhook/forge/review-request`. A request for any other reviewer, or a removed request, does nothing. - The controller reads the PR, loads the rules with the same reader as `GET /api/review-classifier/rules`, sends one request on the try-out subject, and posts APPROVED or REQUEST_CHANGES with the table as the body. - With no clean verdict (PR or rules don't load, the run fails, or any classification has no answer) it posts a COMMENT saying why, never an approval. The event is one function, `no_clean_verdict_event`. - Table cells are flattened, markup-escaped, and every `@` gets a zero-width space, so a model's reason can't break the table or mention anyone. - At most two requested reviews run at once; the rest wait in memory. - The account is minted the way hive accounts are: a five-minute pass queues a `MintClassifierForgeToken` node when the token at `swarm/services/review-classifier/forge/token` is missing or stale. The token is `write:repository`. The controller's bao policy gains that leaf (create/read/update), pinned in module-eval bao-grants. The classifier never sees the token. The account gets no repo access automatically; a repo admin adds it by hand. A re-request retires the account's earlier review: Forgejo clears `official` on a reviewer's earlier reviews when it is requested again (models/issues/review.go AddReviewRequest, v16.0.5), and approvals and rejection blocks count official reviews only. Refs #5137
This commit is contained in:
parent
ad47664e01
commit
a669cd96b6
17 changed files with 1156 additions and 50 deletions
|
|
@ -68,30 +68,36 @@ classifications:
|
|||
|
||||
## Trigger and lifecycle
|
||||
|
||||
The classifier has a dedicated forge service account. Requesting that
|
||||
account as a reviewer on a PR starts a run:
|
||||
The classifier has a dedicated forge service account, `review-classifier`
|
||||
(see [The service account](#the-service-account)). Requesting that account
|
||||
as a reviewer on a PR starts a run:
|
||||
|
||||
1. The forge sends the review-request webhook to
|
||||
1. The forge sends the `pull_request_review_request` delivery to
|
||||
[`swarm-controller`](../../swarm-controller/README.md), the swarm's one
|
||||
webhook owner.
|
||||
2. The controller hands the run to the classifier service as an async
|
||||
message.
|
||||
3. The classifier runs every classification in the repo's config, read from
|
||||
the default branch, against the PR's commits and diff.
|
||||
4. The service account posts the result on the PR as a review.
|
||||
webhook owner, on its instance-wide hook `/webhook/forge/review-request`.
|
||||
A delivery for any other reviewer, or a removed request, does nothing.
|
||||
2. The controller reads the PR and the repo's rules from the default branch,
|
||||
and hands the run to the classifier service as one queue request on
|
||||
`$SWARM.review-classifier.try-out`, the subject a try-out uses.
|
||||
3. The classifier runs every classification against the PR's commits and
|
||||
diff.
|
||||
4. The controller posts the result on the PR as the service account's
|
||||
review, on the commit it read.
|
||||
|
||||
A verdict belongs to the commit it ran on. Once the PR's head moves,
|
||||
requesting the service account as a reviewer again starts a fresh run on the
|
||||
new head.
|
||||
A verdict belongs to the commit it ran on. A push starts no run. Once the
|
||||
PR's head moves, requesting the service account as a reviewer again starts a
|
||||
fresh run on the new head.
|
||||
|
||||
That only holds if the bot's review on an older commit stops counting once
|
||||
the head moves. Either the repo enables Forgejo's stale-review dismissal
|
||||
(see [PR review gate](../process/pr-review-gate.md)), or the service
|
||||
dismisses its own earlier review when it posts a new one. Where neither
|
||||
applies, an old approval keeps counting and an old request-changes keeps
|
||||
blocking.
|
||||
The re-request also retires the account's earlier review. The forge clears
|
||||
the `official` flag on a reviewer's earlier reviews when it's requested
|
||||
again, and only official reviews count toward the required approvals or
|
||||
block a merge. Removing the request before the new review lands restores
|
||||
the earlier one. A repo whose branch protection also dismisses stale approvals (see
|
||||
[PR review gate](../process/pr-review-gate.md)) stops counting an approval
|
||||
on an older commit even without a re-request.
|
||||
|
||||
Runs go in parallel, up to a maximum set at swarm level.
|
||||
At most two requested reviews run at once. More wait in the controller's
|
||||
memory, so a controller restart drops them, and nothing gets posted for them.
|
||||
|
||||
## Components
|
||||
|
||||
|
|
@ -137,6 +143,31 @@ and from every agent's account. It's the reviewer someone requests, it
|
|||
authors every classifier review, and it pushes the branches the try-out page
|
||||
creates.
|
||||
|
||||
`swarm-controller` creates it as `review-classifier`, with the email
|
||||
`review-classifier@services.hyperhive.local` and no repo creation, and mints
|
||||
its one token, `swarm-review-classifier` with scope `write:repository`, into
|
||||
the store at `swarm/services/review-classifier/forge/token`. A pass at start
|
||||
and every five minutes re-mints when the token is missing or no longer
|
||||
matches the forge. The controller refuses an existing account by that name
|
||||
with any other email. The controller posts the reviews with that token, and
|
||||
the classifier service never sees it.
|
||||
|
||||
**Giving a repo access is a repo admin's step, by hand.** Nothing grants
|
||||
it automatically. In the repo's settings, add `review-classifier` as a
|
||||
collaborator:
|
||||
|
||||
- with **read**, so someone can request it as a reviewer, and its reviews show,
|
||||
but its approval doesn't count
|
||||
- with **write**, so its approval counts toward the required approvals and
|
||||
its request-changes blocks the merge where the branch protection blocks on
|
||||
rejected reviews. Where the branch protection restricts approvals to an
|
||||
allowlist, the account counts only once it's on that list, and read access
|
||||
is then enough.
|
||||
|
||||
The repo also needs a rules file on its default branch (see
|
||||
[Repo configuration](#repo-configuration)). Without one, a request posts a
|
||||
comment saying so.
|
||||
|
||||
## The classification run
|
||||
|
||||
### Shared context
|
||||
|
|
@ -209,6 +240,18 @@ like any other ([`pr-review-gate.md`](../process/pr-review-gate.md)). The
|
|||
Repos that rely on it typically raise their required approval count to
|
||||
account for it.
|
||||
|
||||
A run with no clean verdict never approves. When the PR or the rules don't
|
||||
load, the run fails, or **any** classification has no answer, the account
|
||||
posts a **comment** instead, starting with `**No verdict:**` and the reason,
|
||||
followed by the table when there is one. A comment neither approves nor
|
||||
blocks the merge: nine timeouts and one pass post a comment, not an
|
||||
approval.
|
||||
|
||||
The controller flattens every cell to one line, with `|`, backticks, brackets, `<`, `>`,
|
||||
`&`, `#` and other markup characters escaped, and a zero-width space after
|
||||
each `@`, so a model's `reason` can't break the table, embed markup, or
|
||||
mention anyone.
|
||||
|
||||
## Runtimes
|
||||
|
||||
The classifier runs on one of three runtimes, chosen at swarm level:
|
||||
|
|
@ -302,9 +345,6 @@ These get settled, and filled in here, as the work reaches them:
|
|||
and how the recent-runs page reads them.
|
||||
- **Oversized diffs.** What happens when the diff doesn't fit the model's
|
||||
context. Lockfiles already collapse to one line each, as in `hive-forge diff`.
|
||||
- **Stale reviews.** Whether the repo's stale-review dismissal setting
|
||||
retires the bot's review on an older commit, or the service dismisses its
|
||||
own earlier review when it posts a new one.
|
||||
- **Budget.** Any per-run or per-day spend limit on top of the concurrency
|
||||
cap.
|
||||
- **Diff-leaves-swarm policy.** On the API key and ACP runtimes the diff
|
||||
|
|
@ -315,20 +355,17 @@ These get settled, and filled in here, as the work reaches them:
|
|||
- **The "no tools" mechanism on ACP.** How the ACP runtime runs a session with
|
||||
no tools at all. The claude runtimes pass `--tools ""`.
|
||||
- **Credential paths.** Where in bao the ACP and subscription runtimes'
|
||||
credentials and the service account's forge token live, and which component
|
||||
holds the token. The API key is at
|
||||
`secret/swarm/services/review-classifier/anthropic-api-key`, field `api_key`.
|
||||
credentials live. The API key is at
|
||||
`secret/swarm/services/review-classifier/anthropic-api-key`, field `api_key`,
|
||||
and the service account's forge token at
|
||||
`secret/swarm/services/review-classifier/forge/token`.
|
||||
- **Model naming across runtimes.** How the swarm-level model setting
|
||||
handles runtime-specific model names.
|
||||
- **ACP cost labels.** How an ACP run's cost gets a repo and PR label, so
|
||||
it reads from the metrics store like the claude runtimes' cost.
|
||||
- **Async message medium for review runs.** Which queue subjects or streams
|
||||
carry a webhook-started run. A try-out is one request on
|
||||
`$SWARM.review-classifier.try-out`, answered on its reply subject.
|
||||
- **Reviewer handoff.** How a later reviewer, human or agent, learns the
|
||||
classifier passed a PR before it starts its own review.
|
||||
- **Opt-in details.** Beyond the config file: the service account's access
|
||||
to a repo, so someone can request it as reviewer and it can push try-out
|
||||
branches.
|
||||
- **Try-out branch access.** The service account's access for pushing try-out
|
||||
branches, beyond what a repo admin gives it for reviews.
|
||||
- **Commit author for try-out PRs.** How the classifier derives the commit
|
||||
author's name and email from the swarm UI login.
|
||||
|
|
|
|||
Loading…
Reference in a new issue