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

The OpenAPI document describes a tell: POST /api/v1/feed/tells #5489

Merged opened this

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

Merged

scout/forge:scout/5314-the-openapi-document-describes-a-tell-po → main 1 commit fork

  • Merged. scout/5314-the-openapi-document-describes-a-tell-po was merged into main as 15e720d.

Review

Sign in to review this change.
3 files changed +28 −2
src/contract/openapi.ts +17
@@ -162,6 +162,23 @@ export const ROUTES: readonly Route[] = [
162162 },
163163 { method: "get", path: "/api/v1/explore", summary: "Explore apps and repositories; `q` takes words and `kind:`/`owner:` filters; answers { query, items }", auth: false, query: { q: "string" } },
164164 { method: "get", path: "/api/v1/search", summary: "Search issues, pulls, repositories and code; `kind` narrows it", auth: false, query: { q: "string", kind: "string" } },
165 {
166 method: "post",
167 path: "/api/v1/feed/tells",
168 summary: "Tell an app's agents what you want, from a card or any harness (rw scope); `app` is owner/name, `text` 1 to 2000 characters, `variants` 1 to 4 tries (default 1), `from` the forge path the tell was sent from (a leading single /, at most 512 characters, no spaces); answers 201 with { issue } and, for variants, { issue, issues, assigned, variantGroup }",
169 auth: true,
170 body: { app: "string", text: "string", variants: "integer", from: "string" },
171 status: 201,
172 refusals: {
173 invalid_text: "`text` is empty or over 2000 characters (422)",
174 invalid_variants: "`variants` is not a whole number from 1 to 4 (400)",
175 invalid_from: "`from` is not a same-forge path of at most 512 characters (400)",
176 not_found: "`app` names no repository the caller can read (404)",
177 forbidden: "the caller may not direct the crew (403)",
178 unverified: "the account has not verified its email (403)",
179 refused: "the tell could not be told (403 or 422)",
180 },
181 },
165182 { method: "get", path: "/api/v1/repos", summary: "List public repositories", auth: false },
166183 {
167184 method: "post",
test/openapi-coverage.test.ts −1
@@ -89,7 +89,6 @@ const NOT_YET_DESCRIBED: readonly string[] = [
8989 "POST /api/v1/boards/query",
9090 "POST /api/v1/feed/chips",
9191 "POST /api/v1/feed/interactions",
92 "POST /api/v1/feed/tells",
9392 "POST /api/v1/repos",
9493 "POST /api/v1/repos/{}/{}/branches",
9594 "POST /api/v1/repos/{}/{}/contents",
test/openapi.test.ts +11 −1
@@ -27,7 +27,7 @@ test("every http route the manifest names is described", () => {
2727 });
2828
2929 test("every described route is registered in the router", () => {
30 const src = ["src/http/server.ts", "src/http/api.ts", "src/http/mcp.ts", "src/http/passport_edge.ts"]
30 const src = ["src/http/server.ts", "src/http/api.ts", "src/http/mcp.ts", "src/http/passport_edge.ts", "src/http/feed.ts"]
3131 .map((f) => readFileSync(f, "utf8"))
3232 .join("\n")
3333 // A route registered in a loop over issues and pulls is two routes.
@@ -264,3 +264,13 @@ test("the contents row names the refusals of its own handler, protection and siz
264264 expect(text).toContain("protected_branch");
265265 expect(text).toContain("file_too_large");
266266 });
267
268 test("the tell row names its body, its answer and the refusals of its handler", () => {
269 const doc = openapi("https://f.test", "1") as any;
270 const op = doc.paths["/api/v1/feed/tells"]?.post;
271 expect(op).toBeDefined();
272 expect(op.responses["201"]).toBeDefined();
273 const text = JSON.stringify(op);
274 for (const w of ["app", "text", "variants", "from", "invalid_text", "invalid_variants", "invalid_from", "unverified", "forbidden"])
275 expect(text).toContain(w);
276 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs