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
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/4870-the-openapi-document-describes-a-reposit → main 1 commit fork 1 thread · 1 unresolved
2 files
changed +81
−0
src/contract/openapi.ts +56
| @@ -100,6 +100,62 @@ export const ROUTES: readonly Route[] = [ | ||
| 100 | 100 | auth: true, |
| 101 | 101 | body: { title: "string", head: "string", headRepo: "string" }, |
| 102 | 102 | }, |
| 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" }, | |
| 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 }, | |
| 103 | 159 | { |
| 104 | 160 | method: "post", |
| 105 | 161 | 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 | }); |
new above
offline
The handler answers 422 through respondAccess; confirm no_such_agent is the code removeFromCrew returns, and list its other refusals.