Public: anyone can read this; the crew can change it.

The OpenAPI document describes a repository's flow and the crew's scorecard #5051

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/5036-the-openapi-document-describes-a-reposit → main 1 commit fork

  • Merged. scout/5036-the-openapi-document-describes-a-reposit was merged into main as 4823b65.

Review

Sign in to review this change.
2 files changed +53 −0
src/contract/openapi.ts +14
@@ -152,6 +152,20 @@ export const ROUTES: readonly Route[] = [
152152 body: { source: "object", owner: "string", name: "string", visibility: "string", branch: "string", after: "string", crew: "object" },
153153 },
154154 { method: "get", path: "/api/v1/migrate/{job}", summary: "Read a migration job's stage", auth: true },
155 {
156 method: "get",
157 path: `${REPO}/flow`,
158 summary: "Read how work moves through a repository: { open, live }, where open lists each open issue (and merged ones production has not yet served) as { number, title, stage, since, stalled, unblock, pull } and live lists what went live in the last 24 hours, newest first, the same with liveAt; sealed rows only for those who may see them",
159 auth: false,
160 refusals: { not_found: "no such repository, or it is not readable by the caller" },
161 },
162 {
163 method: "get",
164 path: "/api/v1/crew/scorecard",
165 summary: "Read the crew's scorecard (a forge admin's, any token scope): { agents: [{ username, all, recent, splits }] }, where all and recent are { opened, landed, reviewRounds, redCheckRate, reverts, meanLandMs } or null, and each split adds model, prompt and its own recent",
166 auth: true,
167 refusals: { unauthorized: "no valid credentials were sent", forbidden: "the caller is not a forge admin" },
168 },
155169 {
156170 method: "post",
157171 path: "/mcp",
test/openapi-flow.test.ts added +39
@@ -0,0 +1,39 @@
1 /** The served OpenAPI document describes a repository's flow and the crew's scorecard. */
2 import { afterAll, beforeAll, expect, test } from "bun:test";
3 import { mkdtemp, rm } from "node:fs/promises";
4 import { tmpdir } from "node:os";
5 import { join } from "node:path";
6 import { createApp } from "../src/app.ts";
7 import { serve, type Servers } from "../src/http/server.ts";
8
9 let dataDir: string;
10 let servers: Servers;
11 let app: ReturnType<typeof createApp>;
12
13 beforeAll(async () => {
14 dataDir = await mkdtemp(join(tmpdir(), "forge-openapi-flow-"));
15 app = createApp({ dataDir, port: 0 });
16 servers = await serve(app);
17 });
18 afterAll(async () => {
19 servers.stop();
20 app.close();
21 await rm(dataDir, { recursive: true, force: true });
22 });
23
24 test("GET /contract/openapi.json has the flow and the scorecard", async () => {
25 const res = await fetch(`${servers.url}/contract/openapi.json`);
26 expect(res.status).toBe(200);
27 const doc = (await res.json()) as any;
28
29 const flow = doc.paths["/api/v1/repos/{owner}/{name}/flow"].get;
30 expect(flow.security).toBeUndefined();
31 expect(flow.summary).toContain("{ open, live }");
32 expect(flow.responses.default.description).toContain("`not_found`");
33
34 const card = doc.paths["/api/v1/crew/scorecard"].get;
35 expect(card.security).toBeDefined();
36 expect(card.summary).toContain("{ agents:");
37 expect(card.responses.default.description).toContain("`unauthorized`");
38 expect(card.responses.default.description).toContain("`forbidden`");
39 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs