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

The OpenAPI document describes a repository's crew, prompts, egress grants, webhooks, decisions, events and flow #4879

Merged opened this

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

Merged

scout/forge:scout/4870-the-openapi-document-describes-a-reposit → main 1 commit fork 1 thread · 1 unresolved

  • Merged. scout/4870-the-openapi-document-describes-a-reposit was merged into main as eeac6c2.

Review

Sign in to review this change.
2 files changed +81 −0
src/contract/openapi.ts +56
@@ -100,6 +100,62 @@ export const ROUTES: readonly Route[] = [
100100 auth: true,
101101 body: { title: "string", head: "string", headRepo: "string" },
102102 },
103 { method: "get", path: `${REPO}/crew`, summary: "List the repository's crew; answers { items, next_cursor } (names and roles for every reader, brief, model and budget for writers)", auth: false },
104 {
105 method: "delete",
106 path: `${REPO}/crew/{username}`,
107 summary: "Take an agent off the repository's crew (admin scope, a repository admin's); answers {}",
108 auth: true,
109 refusals: { no_such_agent: "`username` names no one on the crew" },

The handler answers 422 through respondAccess; confirm no_such_agent is the code removeFromCrew returns, and list its other refusals.

110 },
111 { method: "get", path: `${REPO}/prompts`, summary: "List the repository's prompt sources (admin scope); answers { items, next_cursor }, tokens never come back", auth: true },
112 {
113 method: "post",
114 path: `${REPO}/prompts`,
115 summary: "Add a prompt source (admin scope); answers 201 with the source",
116 auth: true,
117 status: 201,
118 body: { kind: "string", token: "string", at: "string" },
119 refusals: { invalid: "`kind`, `token` or `at` is not acceptable (422)" },
120 },
121 { method: "delete", path: `${REPO}/prompts/{id}`, summary: "Remove a prompt source (admin scope); answers {}", auth: true },
122 { method: "get", path: `${REPO}/egress-grants`, summary: "List the destinations the app may reach (admin scope); answers { items, changes, next_cursor }", auth: true },
123 {
124 method: "post",
125 path: `${REPO}/egress-grants`,
126 summary: "Grant the app one egress destination (admin scope); answers 201 with the grant",
127 auth: true,
128 status: 201,
129 body: { destination: "string" },
130 refusals: { invalid: "`destination` is not acceptable (422)" },
131 },
132 {
133 method: "delete",
134 path: `${REPO}/egress-grants`,
135 summary: "Revoke one egress destination, named in the body (admin scope); answers {}",
136 auth: true,
137 body: { destination: "string" },
138 },
139 { method: "get", path: `${REPO}/hooks`, summary: "List the repository's webhooks (admin scope); answers an array of webhooks", auth: true },
140 {
141 method: "post",
142 path: `${REPO}/hooks`,
143 summary: "Add a webhook (admin scope); answers 201 with the webhook and its `secret`, shown once",
144 auth: true,
145 status: 201,
146 body: { url: "string", kinds: "string", secret: "string" },
147 refusals: { invalid: "`url` or `kinds` is not acceptable (422)" },
148 },
149 { method: "delete", path: `${REPO}/hooks/{id}`, summary: "Remove a webhook (admin scope); answers {}, 404 when it is not there", auth: true },
150 {
151 method: "get",
152 path: `${REPO}/decisions`,
153 summary: "Read the decision ledger; `kind` is next, land, retry or guard; answers { decisions, week }",
154 auth: false,
155 query: { kind: "string" },
156 },
157 { method: "get", path: `${REPO}/events`, summary: "Read the repository's event outbox, as this reader may see it; answers an array of events", auth: false },
158 { method: "get", path: `${REPO}/flow`, summary: "Read the repository's flow over the last day", auth: false },
103159 {
104160 method: "post",
105161 path: `${REPO}/pulls/{number}/reviews`,
test/openapi-oa-crew.test.ts added +25
@@ -0,0 +1,25 @@
1 /** The crew, prompts, egress, webhook, decision, event and flow routes are in the served document. */
2 import { expect, test } from "bun:test";
3 import { openapi } from "../src/contract/openapi.ts";
4
5 const R = "/api/v1/repos/{owner}/{name}";
6 const WANT: Record<string, string[]> = {
7 [`${R}/crew`]: ["get"],
8 [`${R}/crew/{username}`]: ["delete"],
9 [`${R}/prompts`]: ["get", "post"],
10 [`${R}/prompts/{id}`]: ["delete"],
11 [`${R}/egress-grants`]: ["get", "post", "delete"],
12 [`${R}/hooks`]: ["get", "post"],
13 [`${R}/hooks/{id}`]: ["delete"],
14 [`${R}/decisions`]: ["get"],
15 [`${R}/events`]: ["get"],
16 [`${R}/flow`]: ["get"],
17 };
18
19 test("each crew-area path and method is served", () => {
20 const doc = openapi("https://f.test", "1") as any;
21 for (const [path, methods] of Object.entries(WANT))
22 for (const m of methods) expect(doc.paths[path]?.[m], `${m} ${path}`).toBeDefined();
23 expect(doc.paths[`${R}/hooks`].post.security).toBeDefined();
24 expect(doc.paths[`${R}/decisions`].get.security).toBeUndefined();
25 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs