From ed15105e6f7c136c00c6d486f52f4399b16697c1 Mon Sep 17 00:00:00 2001 From: atlas Date: Mon, 12 Oct 2026 04:17:18 +0200 Subject: [PATCH] swarm-controller: document every try-out status and row shape in the OpenAPI The routes' annotations now list every 400 reason (including the JSON shape rejections), the 503 causes, the 31-minute running window, the 404 after a controller restart, and that an errored row reads not-applicable with certainty 0 and an empty reason, so the page keys off error, and that overall can be approve beside errored rows. --- swarm-controller/src/review_classifier.rs | 32 ++++++++++++++------- swarm-queue-client/src/review_classifier.rs | 5 +++- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/swarm-controller/src/review_classifier.rs b/swarm-controller/src/review_classifier.rs index de3af7d2..78affff2 100644 --- a/swarm-controller/src/review_classifier.rs +++ b/swarm-controller/src/review_classifier.rs @@ -53,6 +53,9 @@ pub(crate) struct TryOutAccepted { id: String, } +/// `running` lasts at most the run deadline, 31 minutes: ten +/// classifications at three minutes each, one after another, and a minute +/// more. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, ToSchema)] #[serde(rename_all = "lowercase")] pub(crate) enum RunState { @@ -70,10 +73,12 @@ pub(crate) struct TryOutStatus { /// Set when `done`. #[serde(skip_serializing_if = "Option::is_none")] rows: Option>, - /// Set when `done`. + /// Set when `done`. Rows with an `error` don't count, so `approve` can + /// stand beside them. #[serde(skip_serializing_if = "Option::is_none")] overall: Option, - /// Set when `failed`. + /// Set when `failed`: no API key, no classification answered, no + /// classifier listening, or no answer within the run deadline. #[serde(skip_serializing_if = "Option::is_none")] error: Option, } @@ -225,16 +230,19 @@ impl RunSink for async_nats::Client { responses( (status = 202, description = "the run started; read it at \ `/api/review-classifier/try-out/{id}`", body = TryOutAccepted), - (status = 400, description = "the body is not a valid run: a repo that is not \ - owner/name, no PR number, no classification or more than ten, a classification \ - without a name or description, two with one name, or a threshold over 100 \ - (problem+json)", body = String), + (status = 400, description = "the body is not a valid run: not JSON or not this \ + shape (an unknown polarity, a negative or non-integer number, a threshold over \ + 255); a repo that is not owner/name with segments of `[A-Za-z0-9._-]` other than \ + `.` and `..`; a PR number of 0; no classification or more than ten; a \ + classification whose trimmed name or description is empty; two with one trimmed \ + name; or a threshold over 100 (problem+json)", body = String), (status = 401, description = "the request carries no `Remote-User`, so there is no \ operator to name (problem+json)", body = String), (status = 404, description = "the forge knows no such repo or PR (problem+json)", body = String), - (status = 503, description = "no forge access or no swarm queue on this host, or the \ - forge failed to answer (problem+json)", body = String), + (status = 503, description = "this host has no forge token yet, no swarm queue \ + configured, or a queue that is not connected; or the forge failed or did not \ + answer within two minutes (problem+json)", body = String), ), tag = "review-classifier" )] @@ -291,9 +299,11 @@ pub(crate) async fn post_try_out( path = "/api/review-classifier/try-out/{id}", params(("id" = String, Path, description = "the id the POST answered with")), responses( - (status = 200, description = "the run, running or settled", body = TryOutStatus), - (status = 404, description = "no run has this id, or its result expired \ - (problem+json)", body = String), + (status = 200, description = "the run: `running` for at most 31 minutes, then \ + `done` or `failed`", body = TryOutStatus), + (status = 404, description = "no run has this id: it never existed, its result \ + expired an hour after it settled, or the controller restarted since the POST, \ + which loses every run (problem+json)", body = String), ), tag = "review-classifier" )] diff --git a/swarm-queue-client/src/review_classifier.rs b/swarm-queue-client/src/review_classifier.rs index bdc216a7..ae6c7cb9 100644 --- a/swarm-queue-client/src/review_classifier.rs +++ b/swarm-queue-client/src/review_classifier.rs @@ -109,7 +109,10 @@ pub struct Row { /// polarity. `false` for every row that is not counted. pub passed: bool, pub reason: String, - /// Why this classification has no answer. Such a row is never counted. + /// Why this classification has no answer: a runtime error, a timeout, or + /// an answer that did not validate. Such a row reads `not-applicable`, + /// certainty 0 and an empty reason, and is never counted; key off this + /// field, not off the verdict. #[serde(default, skip_serializing_if = "Option::is_none")] pub error: Option, }