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

The OpenAPI document describes the admin's writes: a capacity setting, a limit and a team #5133

Merged opened this

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

Merged

scout/forge:scout/5116-the-openapi-document-describes-the-admin → main 2 commits fork 1 thread · 1 unresolved

  • Merged. scout/5116-the-openapi-document-describes-the-admin was merged into main as 7addeed.

Review

Sign in to review this change.
2 files changed +55 −0
src/contract/openapi.ts +43
@@ -7,6 +7,9 @@
77 * added here, and `test/openapi.test.ts` fails until it is.
88 */
99
10 import { CAPACITY_KEYS } from "../core/capacity.ts";
11 import { LIMIT_KEYS } from "../settings/limits.ts";
12
1013 interface Route {
1114 readonly method: "get" | "post" | "put" | "patch" | "delete";
1215 readonly path: string;
@@ -212,7 +215,19 @@ export const ROUTES: readonly Route[] = [
212215 "Read the forge's limits (a person admin's token, any scope); answers { items: [{ key, value, isSet, defaultValue, ... }], changes, next_cursor }",
213216 auth: true,
214217 refusals: { not_found: "the token is not a person who administers the forge" },
218 }, {
219 method: "put",
220 path: "/api/v1/admin/limits/{key}",
221 summary: `Change one limit live, or send value null to take it back to its default (a person admin's token, admin scope); {key} is one of ${LIMIT_KEYS.join(", ")}; answers the changed limit`,
222 auth: true,
223 body: { value: "integer or null" },
224 refusals: {
225 not_found: "the token is not a person who administers the forge (404)",
226 no_such_limit: "{key} is not a limit (422)",
227 out_of_bounds: "the value is outside the limit's bounds, named in the message (422)",
228 },
215229 },
230
216231 {
217232 method: "get",
218233 path: "/api/v1/admin/capacity",
@@ -220,7 +235,20 @@ export const ROUTES: readonly Route[] = [
220235 "Read the forge's capacity settings (a person admin's token, any scope); answers { items: [{ key, label, help, value, source, default, env, changed? }], changes, next_cursor }",
221236 auth: true,
222237 refusals: { not_found: "the token is not a person who administers the forge" },
238 }, {
239 method: "put",
240 path: "/api/v1/admin/capacity/{key}",
241 summary: `Change one capacity setting live, or send value null to take it back to its default (a person admin's token, admin scope); {key} is one of ${CAPACITY_KEYS.join(", ")}; answers the changed setting`,
242 auth: true,
243 body: { value: "string or null" },
244 refusals: {
245 not_found: "the token is not a person who administers the forge (404)",
246 no_such_setting: "{key} is not a capacity setting (422)",
247 invalid_value: "the value is outside what this setting takes (422)",
248 no_credential: "the setting needs a credential the forge does not hold (422)",
249 },
223250 },
251
224252 {
225253 method: "get",
226254 path: "/api/v1/admin/teams",
@@ -228,7 +256,22 @@ export const ROUTES: readonly Route[] = [
228256 "Read the crew's teams (a person admin's token, any scope); answers { teams: [{ slug, charter, dial, members, ... }] }",
229257 auth: true,
230258 refusals: { not_found: "the token is not a person who administers the forge" },
259 }, {
260 method: "put",
261 path: "/api/v1/admin/teams/{slug}",
262 summary: "Change a crew team's charter, dial or members; send only what changes (a person admin's token, admin scope); answers the team",
263 auth: true,
264 body: { charter: "string", dial: "integer", members: "array of agent ids" },
265 refusals: {
266 invalid_body: "charter is not text, dial not a number, or members not a list of agent ids (422)",
267 not_found: "no such team, or the token is not a person who administers the forge (404)",
268 bad_dial: "dial is not a whole number in range (422)",
269 bad_charter: "the charter is too long (422)",
270 over_100: "the teams' dials together would pass 100% (422)",
271 bad_member: "a member is not an agent on this forge (422)",
272 },
231273 },
274
232275 {
233276 method: "get",
234277 path: "/api/v1/admin/people",
test/openapi.test.ts +12
@@ -204,6 +204,18 @@ test("an app's routes are described, and env values are never read back", () =>
204204 expect(doc.paths[`${A}/{env}/redeploy`].post.responses["202"]).toBeDefined();
205205 });
206206
207 test("the admin's writes are described: a capacity setting, a limit and a team", () => {
208 const doc = openapi("https://f.test", "1") as any;
209 for (const path of ["capacity/{key}", "limits/{key}", "teams/{slug}"]) {
210 const put = doc.paths[`/api/v1/admin/${path}`]?.put;
211 expect(put, path).toBeDefined();
212 expect(put.security).toBeDefined();
213 expect(put.requestBody).toBeDefined();
214 }
215 expect(doc.paths["/api/v1/admin/capacity/{key}"].put.summary).toContain("egress");
216 expect(doc.paths["/api/v1/admin/limits/{key}"].put.summary).toContain("api.anonymous_per_source");
217 });
218
207219 test("a person's sign-ins and ending one are described, with no session secret", () => {
208220 const doc = openapi("https://f.test", "1") as any;
209221 const list = doc.paths["/api/v1/users/{username}/signins"]?.get;

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs