From 237dff14b015d6760847e974683f92b3453da8f1 Mon Sep 17 00:00:00 2001 From: shin-core <153108882+shin-core@users.noreply.github.com> Date: Wed, 29 Jul 2026 20:07:28 +0900 Subject: [PATCH] fix(openapi): declare the real statuses for POST /v1/loop/request-apr-transfer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `requestAprTransfer` spec entry declared `200: "Transfer requested"`, but the handler has exactly four exits — `400` on a parse failure, `409` when the transfer gate rejects (GitHub untouched), `502` when the attempt fails downstream, and `202` (pending-acceptance) otherwise. It never returns `200`, so every generated client documented a status the route cannot produce and omitted the three it can. Replace `200` with `202` and add `409` + `502` (keeping `400`/`401`), mirroring the concrete-outcome description style of the neighbouring MISC_ROUTES entries. The handler's statuses are unchanged — this only makes the published contract match what it already returns. openapi.json regenerated. Closes #9711 --- apps/loopover-ui/public/openapi.json | 10 ++++++++-- src/openapi/internal-and-public-route-specs.ts | 8 +++++++- test/unit/openapi.test.ts | 4 ++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/apps/loopover-ui/public/openapi.json b/apps/loopover-ui/public/openapi.json index 424fb6588..8124cf79d 100644 --- a/apps/loopover-ui/public/openapi.json +++ b/apps/loopover-ui/public/openapi.json @@ -26291,14 +26291,20 @@ } ], "responses": { - "200": { - "description": "Transfer requested" + "202": { + "description": "Transfer initiation accepted (pending acceptance)" }, "400": { "description": "Malformed request" }, "401": { "description": "Missing or invalid token" + }, + "409": { + "description": "Transfer gate rejected the request; GitHub was not touched" + }, + "502": { + "description": "Transfer attempt failed downstream" } } } diff --git a/src/openapi/internal-and-public-route-specs.ts b/src/openapi/internal-and-public-route-specs.ts index adb85db87..2bb6a204c 100644 --- a/src/openapi/internal-and-public-route-specs.ts +++ b/src/openapi/internal-and-public-route-specs.ts @@ -403,7 +403,13 @@ const MISC_ROUTES: SpecEntry[] = [ tags: ["Loop"], summary: "Request an APR transfer for a rented loop", auth: "token", - responses: { 200: { description: "Transfer requested" }, 400: { description: "Malformed request" }, 401: { description: "Missing or invalid token" } }, + responses: { + 202: { description: "Transfer initiation accepted (pending acceptance)" }, + 400: { description: "Malformed request" }, + 401: { description: "Missing or invalid token" }, + 409: { description: "Transfer gate rejected the request; GitHub was not touched" }, + 502: { description: "Transfer attempt failed downstream" }, + }, }, { method: "post", diff --git a/test/unit/openapi.test.ts b/test/unit/openapi.test.ts index b7b79b8d5..41626c747 100644 --- a/test/unit/openapi.test.ts +++ b/test/unit/openapi.test.ts @@ -353,6 +353,10 @@ describe("OpenAPI contract", () => { const aprTransfer = spec.paths["/v1/loop/request-apr-transfer"]?.post; expect(aprTransfer?.operationId).toBe("requestAprTransfer"); expect(aprTransfer?.requestBody).toBeUndefined(); + // #9711: the declared statuses must match the handler's real exits (400/409/502/202) — never the 200 it + // could never return. A spec claiming 200 for a route that only ever answers 202 misleads every generated + // client. + expect(Object.keys(aprTransfer?.responses ?? {}).sort()).toEqual(["202", "400", "401", "409", "502"]); }); // #9308: the eight /v1/lint/* + /v1/validate/focus-manifest advisory-check routes are each backed by an MCP