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
|
|
@ -1,9 +1,12 @@
|
|||
//! The review classifier agreement: where its inference key and its queue
|
||||
//! client secret live in the store, and what each object holds.
|
||||
//! The review classifier agreement: where its inference key, its queue
|
||||
//! client secret and its forge token live in the store, and what each object
|
||||
//! holds.
|
||||
//!
|
||||
//! The operator writes the key by hand in the store's UI; the classifier
|
||||
//! reads both under its own certificate, the key once per run. Its read
|
||||
//! stanza in `swarm-bao.nix` names exactly these two paths.
|
||||
//! reads the key and the client secret under its own certificate, the key
|
||||
//! once per run. Its read stanza in `swarm-bao.nix` names exactly these two
|
||||
//! paths. The forge token is `swarm-controller`'s alone: it mints it, and
|
||||
//! posts the classifier's reviews with it.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
@ -35,6 +38,17 @@ pub struct ApiKey {
|
|||
pub api_key: String,
|
||||
}
|
||||
|
||||
/// The path holding the token of the classifier's forge account, as a
|
||||
/// [`crate::forge::Credential`].
|
||||
///
|
||||
/// # Errors
|
||||
/// Never in practice: the name segment is a constant. The `Result` is
|
||||
/// [`principal_prefix`]'s.
|
||||
pub fn forge_token_path() -> Result<String, Error> {
|
||||
let prefix = principal_prefix(Kind::Service, SERVICE)?;
|
||||
Ok(format!("{prefix}/forge/token"))
|
||||
}
|
||||
|
||||
/// The path holding the queue client secret of the classifier's OIDC client
|
||||
/// `client_id`, where `swarm-secret-publisher.nix` puts every swarm service's.
|
||||
///
|
||||
|
|
@ -61,11 +75,15 @@ mod tests {
|
|||
#[test]
|
||||
fn the_paths_are_the_ones_the_store_grants() {
|
||||
// Spelled out: the operator types the first into the store's UI, and
|
||||
// `swarm-bao.nix` grants both literals.
|
||||
// `swarm-bao.nix` grants each literal.
|
||||
assert_eq!(
|
||||
api_key_path().expect("a constant segment is legal"),
|
||||
"swarm/services/review-classifier/anthropic-api-key"
|
||||
);
|
||||
assert_eq!(
|
||||
forge_token_path().expect("a constant segment is legal"),
|
||||
"swarm/services/review-classifier/forge/token"
|
||||
);
|
||||
assert_eq!(
|
||||
client_secret_path("swarm-review-classifier").expect("a plain id is legal"),
|
||||
"swarm/services/swarm-review-classifier/oidc/client"
|
||||
|
|
|
|||
Loading…
Reference in a new issue