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

The OpenAPI description cannot drift from the REST routes: a test lists routes missing from it #10941

Merged opened this

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

Merged

mason/forge:mason/10920-the-openapi-description-cannot-drift-fro → main 3 commits fork 3 threads · 3 unresolved

  • Merged. mason/10920-the-openapi-description-cannot-drift-fro was merged into main as 652ea76.

Review

Sign in to review this change.
393 files changed +552 −115
docs/features/api-reference.md +2
@@ -23,3 +23,5 @@ path parameter runs it as the person at the terminal and shows the status, time,
2323 A list the contract gives a `cursor` pages by it: `?page=` or `?offset=` on one is a 400 `invalid_query` ("This list pages by cursor: pass next_cursor as ?cursor="), answered by one guard in `src/http/cursor_lists.ts` before any handler runs, and named in the document as the route's 400.
2424
2525 The root, `GET /api/v1`, names the OpenAPI, MCP, OAuth and llms.txt URLs with no account: [the API root](api-root.md).
26
27 **Roles.** Every operation in the document carries `x-role`: `anonymous` (no account at all), `member` (any signed-in account; the summary adds a token scope or a repository right where one applies, or says the route takes a browser session or the password and refuses a bearer token, as the token routes do) or `admin` (a site admin only). It is a required field of each route row (`role` in `src/contract/openapi/shared.ts`'s `Route`), so a new route cannot leave it out; `test/openapi-drift.test.ts` pins the routes easy to misread, and the same test fails when a route is described but not routed or routed but not described.
src/contract/openapi.ts +1
@@ -73,6 +73,7 @@ export function openapi(origin: string, version: string): Record<string, unknown
7373 (paths[r.path] ??= {})[r.method] = {
7474 operationId: operationId(r),
7575 summary: r.summary,
76 "x-role": r.role,
7677 parameters: [...params, ...query],
7778 ...(r.body === undefined
7879 ? {}
src/contract/openapi/routes/deleteApiV1AdminAccountsUsername.ts +1
@@ -6,6 +6,7 @@ export const route: Route = {
66 summary:
77 "Remove an account for good (an admin's, admin scope); answers { removed }. With retireOrgs true and no confirm it answers { confirm_required, retire, own } and removes nothing; with both it retires the organizations the person solely owns and answers { removed, retired }, or 500 { error: \"delete_failed\", failed, removed }",
88 auth: true,
9 role: "admin",
910 body: { retireOrgs: "boolean", confirm: "boolean" },
1011 refusals: {
1112 not_allowed: "an admin cannot remove themselves, another admin or an agent (422)",
src/contract/openapi/routes/deleteApiV1AdminInvitesId.ts +1
@@ -5,4 +5,5 @@ export const route: Route = {
55 path: "/api/v1/admin/invites/{id}",
66 summary: "Revoke an invite link by id (admin scope); 404 when there is none",
77 auth: true,
8 role: "admin",
89 };
src/contract/openapi/routes/deleteApiV1AgentsUsername.ts +1 −1
@@ -1,3 +1,3 @@
11 import type { Route } from "../shared.ts";
22
3 export const route: Route = { method: "delete", path: "/api/v1/agents/{username}", summary: "Remove an agent (an admin token, or a person's session); answers {}", auth: true, refusals: { forbidden: "a token below admin scope", not_found: "no such agent" } };
3 export const route: Route = { method: "delete", path: "/api/v1/agents/{username}", summary: "Remove an agent (an admin token, or a person's session); answers {}", auth: true, role: "member", refusals: { forbidden: "a token below admin scope", not_found: "no such agent" } };
src/contract/openapi/routes/deleteApiV1BoardsSlug.ts +1
@@ -5,4 +5,5 @@ export const route: Route = {
55 path: "/api/v1/boards/{slug}",
66 summary: "Delete a board; answers { deleted: true }",
77 auth: true,
8 role: "admin",
89 };
src/contract/openapi/routes/deleteApiV1ConversationFilesId.ts +1
@@ -5,6 +5,7 @@ export const route: Route = {
55 path: "/api/v1/conversation/files/{id}",
66 summary: "Take back an attachment chosen and not yet sent; a sent one stays with its message",
77 auth: true,
8 role: "member",
89 reply: { ok: "boolean" },
910 refusals: {
1011 not_found: "no such unsent attachment of yours (404)",
src/contract/openapi/routes/deleteApiV1ConversationQueueId.ts +1
@@ -5,6 +5,7 @@ export const route: Route = {
55 path: "/api/v1/conversation/queue/{id}",
66 summary: "Take back a message queued for after the running turn; one that already went stays sent",
77 auth: true,
8 role: "member",
89 reply: { ok: "boolean" },
910 refusals: {
1011 not_found: "no such waiting message of yours (404)",
src/contract/openapi/routes/deleteApiV1FeaturesSlug.ts +1
@@ -5,6 +5,7 @@ export const route: Route = {
55 path: "/api/v1/features/{slug}",
66 summary: "Un-absorb an app (a site admin's admin scope token): it runs as an ordinary app again; its settings are kept for the next absorb; answers the feature",
77 auth: true,
8 role: "admin",
89 refusals: {
910 not_found: "no such app, or one you may not read",
1011 forbidden: "you are not a site admin (403)",
src/contract/openapi/routes/deleteApiV1OrgsOrgTeamsTeam.ts +1
@@ -5,6 +5,7 @@ export const route: Route = {
55 path: "/api/v1/orgs/{org}/teams/{team}",
66 summary: "Remove a team (admin scope); answers 204",
77 auth: true,
8 role: "member",
89 status: 204,
910 refusals: { not_found: "no such team" },
1011 };
src/contract/openapi/routes/deleteApiV1OrgsOrgTeamsTeamMembersUsername.ts +1
@@ -5,6 +5,7 @@ export const route: Route = {
55 path: "/api/v1/orgs/{org}/teams/{team}/members/{username}",
66 summary: "Take a user out of a team (admin scope); answers 204",
77 auth: true,
8 role: "member",
89 status: 204,
910 refusals: {
1011 not_found: "no such team, or the user is not in it",
src/contract/openapi/routes/deleteApiV1ReposOwnerName.ts +1
@@ -5,5 +5,6 @@ export const route: Route = {
55 path: REPO,
66 summary: "Delete a repository for good (admin scope, an admin's); answers {}",
77 auth: true,
8 role: "member",
89 refusals: { not_found: "404: no such repository, or the caller is not its admin", delete_failed: "500: the repository could not be removed" },
910 };
src/contract/openapi/routes/deleteApiV1ReposOwnerNameApp.ts +1 −1
@@ -1,3 +1,3 @@
11 import { REPO, type Route } from "../shared.ts";
22
3 export const route: Route = { method: "delete", path: `${REPO}/app`, summary: "Turn the app off (admin scope); answers {}", auth: true };
3 export const route: Route = { method: "delete", path: `${REPO}/app`, summary: "Turn the app off (admin scope); answers {}", auth: true, role: "member" };
src/contract/openapi/routes/deleteApiV1ReposOwnerNameAppDomainsHost.ts +1 −1
@@ -1,3 +1,3 @@
11 import { REPO, type Route } from "../shared.ts";
22
3 export const route: Route = { method: "delete", path: `${REPO}/app/domains/{host}`, summary: "Remove a domain from the app (admin scope); answers {}", auth: true };
3 export const route: Route = { method: "delete", path: `${REPO}/app/domains/{host}`, summary: "Remove a domain from the app (admin scope); answers {}", auth: true, role: "member" };
src/contract/openapi/routes/deleteApiV1ReposOwnerNameAppEnvVar.ts +1
@@ -5,5 +5,6 @@ export const route: Route = {
55 path: `${REPO}/app/env/{var}`,
66 summary: "Unset an environment variable (admin scope); scope is all (default), production or preview; answers {}",
77 auth: true,
8 role: "member",
89 query: { scope: "string" },
910 };
src/contract/openapi/routes/deleteApiV1ReposOwnerNameBranchesBranch.ts +1
@@ -6,6 +6,7 @@ export const route: Route = {
66 summary:
77 "Delete a branch (rw scope); the name may hold slashes; answers 204 with no body",
88 auth: true,
9 role: "member",
910 status: 204,
1011 refusals: {
1112 invalid_name: "not a branch name",
src/contract/openapi/routes/deleteApiV1ReposOwnerNameBuildsLabel.ts +1
@@ -5,6 +5,7 @@ export const route: Route = {
55 path: `${REPO}/builds/{label}`,
66 summary: "Delete a pin: refs/builds/{label} with its cover and screenshots; the commit stays (rw scope, a person who writes); answers 204",
77 auth: true,
8 role: "member",
89 status: 204,
910 refusals: {
1011 forbidden: "403: not a person who writes the repository (an agent is refused)",
src/contract/openapi/routes/deleteApiV1ReposOwnerNameBuildsLabelShotsFile.ts +1
@@ -5,6 +5,7 @@ export const route: Route = {
55 path: `${REPO}/builds/{label}/shots/{file}`,
66 summary: "Remove one screenshot from a pin (rw scope, a person who writes); `?at=` is the `tree` it was read from; answers 204",
77 auth: true,
8 role: "member",
89 query: { at: "string" },
910 status: 204,
1011 refusals: { ...SHOT_REFUSALS, no_such_shot: "404: the pin has no screenshot by that name" },
src/contract/openapi/routes/deleteApiV1ReposOwnerNameCrewUsername.ts +1
@@ -5,5 +5,6 @@ export const route: Route = {
55 path: `${REPO}/crew/{username}`,
66 summary: "Take an agent off the repository's crew (admin scope, a repository admin's); answers {}",
77 auth: true,
8 role: "member",
89 refusals: { no_such_agent: "`username` names no one on the crew" },
910 };
src/contract/openapi/routes/deleteApiV1ReposOwnerNameEgressGrants.ts +1
@@ -5,5 +5,6 @@ export const route: Route = {
55 path: `${REPO}/egress-grants`,
66 summary: "Revoke one egress destination, named in the body (admin scope); answers {}",
77 auth: true,
8 role: "member",
89 body: { destination: "string" },
910 };
src/contract/openapi/routes/deleteApiV1ReposOwnerNameHooksId.ts +1 −1
@@ -1,3 +1,3 @@
11 import { REPO, type Route } from "../shared.ts";
22
3 export const route: Route = { method: "delete", path: `${REPO}/hooks/{id}`, summary: "Remove a webhook (admin scope); answers {}, 404 when it is not there", auth: true };
3 export const route: Route = { method: "delete", path: `${REPO}/hooks/{id}`, summary: "Remove a webhook (admin scope); answers {}, 404 when it is not there", auth: true, role: "member" };
src/contract/openapi/routes/deleteApiV1ReposOwnerNameIssuesNumberDependenciesBlocker.ts +1
@@ -5,4 +5,5 @@ export const route: Route = {
55 path: `${REPO}/issues/{number}/dependencies/{blocker}`,
66 summary: "Stop an issue waiting for another",
77 auth: true,
8 role: "member",
89 };
src/contract/openapi/routes/deleteApiV1ReposOwnerNameLabelsId.ts +1 −1
@@ -1,3 +1,3 @@
11 import { REPO, type Route } from "../shared.ts";
22
3 export const route: Route = { method: "delete", path: `${REPO}/labels/{id}`, summary: "Remove a label by id (admin scope, repository admin); answers 204 with no body, 404 when the repository has no such label", auth: true, status: 204 };
3 export const route: Route = { method: "delete", path: `${REPO}/labels/{id}`, summary: "Remove a label by id (admin scope, repository admin); answers 204 with no body, 404 when the repository has no such label", auth: true, role: "member", status: 204 };
src/contract/openapi/routes/deleteApiV1ReposOwnerNameMilestonesNumberWatch.ts +1
@@ -5,6 +5,7 @@ export const route: Route = {
55 path: `${REPO}/milestones/{number}/watch`,
66 summary: "Stop watching a milestone: the caller's own watch, so a write token and nothing more (a person's; an agent is refused); answers { watching }",
77 auth: true,
8 role: "member",
89 refusals: {
910 unauthorized: "401: no signed-in account",
1011 forbidden: "403: only a person watches a milestone",
src/contract/openapi/routes/deleteApiV1ReposOwnerNamePromptsId.ts +1 −1
@@ -1,3 +1,3 @@
11 import { REPO, type Route } from "../shared.ts";
22
3 export const route: Route = { method: "delete", path: `${REPO}/prompts/{id}`, summary: "Remove a prompt source (admin scope); answers {}", auth: true };
3 export const route: Route = { method: "delete", path: `${REPO}/prompts/{id}`, summary: "Remove a prompt source (admin scope); answers {}", auth: true, role: "member" };
src/contract/openapi/routes/deleteApiV1ReposOwnerNamePullsNumberHold.ts +1
@@ -6,5 +6,6 @@ export const route: Route = {
66 summary:
77 "Let a held pull go; a person who writes the repository releases, never an agent (rw scope); answers `{ held: false, released }`, where `released` is false when nothing held it",
88 auth: true,
9 role: "member",
910 refusals: { forbidden: "you are an agent, or you cannot write the repository (403)" },
1011 };
src/contract/openapi/routes/deleteApiV1ReposOwnerNamePullsNumberThreadsAnchorResolve.ts +1
@@ -5,6 +5,7 @@ export const route: Route = {
55 path: `${REPO}/pulls/{number}/threads/{anchor}/resolve`,
66 summary: "Reopen a resolved review thread (rw scope; the thread's author or a writer); answers the thread",
77 auth: true,
8 role: "member",
89 refusals: {
910 not_found: "the pull has no thread at `anchor` (404)",
1011 forbidden: "you neither wrote the thread's comments nor can write to the repository (403)",
src/contract/openapi/routes/deleteApiV1SecretsScopeName.ts +1
@@ -5,6 +5,7 @@ export const route: Route = {
55 path: "/api/v1/secrets/{scope}/{name}",
66 summary: "Remove a secret by name (admin scope token); a value is never read back; answers {}",
77 auth: true,
8 role: "member",
89 query: { ref: "string" },
910 refusals: { not_found: "no such secret or scope owner", invalid_scope: "the scope is not one the forge keeps secrets for" },
1011 };
src/contract/openapi/routes/deleteApiV1Sessions.ts +1
@@ -5,4 +5,5 @@ export const route: Route = {
55 path: "/api/v1/sessions",
66 summary: "Sign out the session in the forge_session cookie: answers 200 with {} and clears the cookie; refused from another origin with 403 forbidden",
77 auth: false,
8 role: "anonymous",
89 };
src/contract/openapi/routes/deleteApiV1TokensId.ts +1
@@ -5,6 +5,7 @@ export const route: Route = {
55 path: "/api/v1/tokens/{id}",
66 summary: "Revoke one of your tokens; a bearer token is refused with 403, call it from a browser session or with a password; answers 204",
77 auth: false,
8 role: "member",
outdated

Revoking your own token needs a browser session or a password, so this is member, not anonymous. Adjust the auth/role consistency check in the drift test to allow it.

89 status: 204,
910 refusals: { forbidden: "the caller holds a bearer token, which cannot manage tokens" },
1011 };
src/contract/openapi/routes/deleteApiV1UserKeysId.ts +1
@@ -5,6 +5,7 @@ export const route: Route = {
55 path: "/api/v1/user/keys/{id}",
66 summary: "Remove one of your SSH keys; needs a token with scope rw or better; answers 204",
77 auth: true,
8 role: "member",
89 status: 204,
910 refusals: { unauthorized: "401: no valid credentials were sent", forbidden: "403: the token scope is below rw, or a browser session sent a cross-origin request", not_found: "404: none of your keys has this id" },
1011 };
src/contract/openapi/routes/deleteApiV1UserOriginAppsId.ts +1
@@ -5,6 +5,7 @@ export const route: Route = {
55 path: "/api/v1/user/origin-apps/{id}",
66 summary: "Remove a registered OAuth application of yours, with its stored client secret; answers 204",
77 auth: true,
8 role: "member",
89 status: 204,
910 refusals: {
1011 unauthorized: "401: no signed-in account",
src/contract/openapi/routes/deleteApiV1UserOriginsId.ts +1
@@ -9,5 +9,6 @@ export const route: Route = {
99 path: "/api/v1/user/origins/{id}",
1010 summary: "Disconnect an origin: forget its credential, and revoke an OAuth grant upstream when there is one; answers { revokedUpstream }; repositories already migrated stay; the account only",
1111 auth: true,
12 role: "member",
1213 refusals: { unauthorized: U, forbidden: F, not_found: NF },
1314 };
src/contract/openapi/routes/deleteApiV1UserPinsOwnerName.ts +1
@@ -5,6 +5,7 @@ export const route: Route = {
55 path: "/api/v1/user/pins/{owner}/{name}",
66 summary: "Unpin a repository; needs a token with scope rw or better; answers 204",
77 auth: true,
8 role: "member",
89 status: 204,
910 refusals: { unauthorized: "401: no valid credentials were sent", forbidden: "403: the token scope is below rw, or a browser session sent a cross-origin request", not_found: "404: no such repository, or it is not readable by the caller" },
1011 };
src/contract/openapi/routes/deleteApiV1UsersUsernameSessionsId.ts +1
@@ -5,6 +5,7 @@ export const route: Route = {
55 path: "/api/v1/users/{username}/sessions/{id}",
66 summary: "End one browser session of a person, as when a device is lost; the person themself or the forge's owner, with a browser session, a password or an admin-scope token; answers 204 and never a session secret",
77 auth: true,
8 role: "member",
89 status: 204,
910 refusals: {
1011 unauthorized: "no credentials were sent",
src/contract/openapi/routes/getApiV1.ts +1
@@ -5,5 +5,6 @@ export const route: Route = {
55 path: "/api/v1",
66 summary: "The API's front door, for anyone and with no account (also at /api/v1/, and HEAD): { name, version, commit, openapi, mcp, oauth, llms, here }, each a URL on the request's own origin; a POST answers 404 no_route",
77 auth: false,
8 role: "anonymous",
89 refusals: {},
910 };
src/contract/openapi/routes/getApiV1AdminAccounts.ts +1
@@ -7,5 +7,6 @@ export const route: Route = {
77 summary:
88 "List every person and agent, newest first (a forge admin's, read scope; anyone else gets 404); answers { items: [{ username, kind, displayName, isAdmin, createdAt, via, signsInThrough, address, repos, apps, spentUsd, suspendedAt, removedAt, verified }], next_cursor }; limit 1 to 200 (default 50), cursor from next_cursor; a bad limit or cursor is a 422",
99 auth: true,
10 role: "admin",
1011 query: { limit: limitTo(OFFSET_LIMIT_MAX), cursor: "string" },
1112 };
src/contract/openapi/routes/getApiV1AdminCapacity.ts +1
@@ -6,5 +6,6 @@ export const route: Route = {
66 summary:
77 "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 }",
88 auth: true,
9 role: "admin",
910 refusals: { not_found: "the token is not a person who administers the forge" },
1011 };
src/contract/openapi/routes/getApiV1AdminCrawlers.ts +1
@@ -6,5 +6,6 @@ export const route: Route = {
66 summary:
77 "Read the crawlers' share of the last 15 minutes of answering time (a person admin's token, any scope); answers { window_minutes, requests, crawler_requests, ms, crawler_ms, deep_stranger_requests, deep_stranger_ms, share, refused: [{ agent, refused }], refused_by_budget: { address, agent, shared, stranger } }",
88 auth: true,
9 role: "admin",
910 refusals: { not_found: "the token is not a person who administers the forge" },
1011 };
src/contract/openapi/routes/getApiV1AdminDrills.ts +1
@@ -6,5 +6,6 @@ export const route: Route = {
66 summary:
77 "Each failure domain's last drill, as MCP `drills` says it (a person admin's token, any scope); answers { items: [{ key, breaks, drilled, why } or { key, breaks, drilled, drill, last: { at, green, why } | null, command }], next_cursor }",
88 auth: true,
9 role: "admin",
910 refusals: { forbidden: "the token is not a forge admin who is a person" },
1011 };
src/contract/openapi/routes/getApiV1AdminInvites.ts +1
@@ -6,5 +6,6 @@ export const route: Route = {
66 path: "/api/v1/admin/invites",
77 summary: "List invite links without their tokens (an admin's, read scope); answers { items, next_cursor }; limit 1 to 200 (default 50), cursor from next_cursor; a bad limit or cursor is a 422",
88 auth: true,
9 role: "admin",
910 query: { limit: limitTo(OFFSET_LIMIT_MAX), cursor: "string" },
1011 };
src/contract/openapi/routes/getApiV1AdminKpisProbe.ts +1
@@ -6,5 +6,6 @@ export const route: Route = {
66 summary:
77 "Read every home number against its source (a person admin's token, any scope); answers { as_of, tile_cache_seconds, numbers: [{ id, label, value, counts, inputs, recomputed, agrees }] }: value is what the tile shows, recomputed comes from the raw inputs read again past the home cache",
88 auth: true,
9 role: "admin",
910 refusals: { not_found: "the token is not a person who administers the forge" },
1011 };
src/contract/openapi/routes/getApiV1AdminLimits.ts +1
@@ -6,5 +6,6 @@ export const route: Route = {
66 summary:
77 "Read the forge's limits (a person admin's token, any scope); answers { items: [{ key, value, isSet, defaultValue, ... }], changes, next_cursor }",
88 auth: true,
9 role: "admin",
910 refusals: { not_found: "the token is not a person who administers the forge" },
1011 };
src/contract/openapi/routes/getApiV1AdminMachinesMachineDisks.ts +1
@@ -6,5 +6,6 @@ export const route: Route = {
66 summary:
77 "Read a machine's disk readings (a person admin's token, any scope): per device the latest reading, bytesWrittenInWindow over spanDays (the oldest reading within 30 days of the latest to the latest, so ≤ 30 days, not always 30), ratedReachedAt and the assessment",
88 auth: true,
9 role: "admin",
910 refusals: { not_found: "the token is not a person who administers the forge, or the machine has no readings" },
1011 };
src/contract/openapi/routes/getApiV1AdminMaintenance.ts +1
@@ -5,5 +5,6 @@ export const route: Route = {
55 path: "/api/v1/admin/maintenance",
66 summary: "Where planned maintenance stands: { phase: idle | draining | ready, since?, reason?, backup?, working, last?, says }; ready means releases are held, the crew is paused, a fresh backup is taken and nothing runs",
77 auth: true,
8 role: "admin",
89 refusals: { not_found: "only a forge administrator sees this route" },
910 };
src/contract/openapi/routes/getApiV1AdminPeople.ts +1
@@ -7,5 +7,6 @@ export const route: Route = {
77 summary:
88 "List every person and their role (admin scope); answers { items: [{ username, role, usdPerMonth }], next_cursor }, role being admin, member or guest; limit 1 to 200 (default 50), cursor from next_cursor; a bad limit or cursor is a 422",
99 auth: true,
10 role: "admin",
1011 query: { limit: limitTo(OFFSET_LIMIT_MAX), cursor: "string" },
1112 };
src/contract/openapi/routes/getApiV1AdminPinned.ts +1
@@ -5,6 +5,7 @@ export const route: Route = {
55 path: "/api/v1/admin/pinned",
66 summary: "The forge's pinned forges with each one's recorded changes, and the targets with what each can and cannot host; a forge administrator's",
77 auth: true,
8 role: "admin",
89 refusals: {
910 not_found: "only a forge administrator sees this route",
1011 refused: "the request was understood and refused; the message says why (422)",
src/contract/openapi/routes/getApiV1AdminProfile.ts +1
@@ -6,5 +6,6 @@ export const route: Route = {
66 summary:
77 "Sample what the main thread is running for `seconds` (required, 1 to 30) (a person admin's token, any scope); answers { seconds, samples, note, stacks: [{ count, stack }] }",
88 auth: true,
9 role: "admin",
910 refusals: { not_found: "the token is not a person who administers the forge", busy: "a sample is already running" },
1011 };
src/contract/openapi/routes/getApiV1AdminQuotas.ts +1
@@ -6,5 +6,6 @@ export const route: Route = {
66 summary:
77 "Read the per-account quotas (a person admin's token, any scope); answers { quotas: { repositories, apps, storage, previews, runs, checks }, changes }, each a number or null for none",
88 auth: true,
9 role: "admin",
910 refusals: { not_found: "the token is not a person who administers the forge" },
1011 };
src/contract/openapi/routes/getApiV1AdminSettings.ts +1
@@ -6,5 +6,6 @@ export const route: Route = {
66 summary:
77 "Read the forge's signup mode, account quotas and own source repository (a person admin's token, any scope); answers { quotas, signup, forgeRepo, forgeRepoInForce, signupChanges, changes }",
88 auth: true,
9 role: "admin",
910 refusals: { not_found: "the token is not a person who administers the forge" },
1011 };
src/contract/openapi/routes/getApiV1AdminShield.ts +1
@@ -5,6 +5,7 @@ export const route: Route = {
55 path: "/api/v1/admin/shield",
66 summary: "The Shield's state (open, ceiling or signin) and its last change: who, when, from what to what (a forge admin's token)",
77 auth: true,
8 role: "admin",
89 refusals: {
910 not_found: "the token is not a forge admin (404)",
1011 not_admin: "the person's role may not administer the forge (403)",
src/contract/openapi/routes/getApiV1AdminSubscriptions.ts +1
@@ -6,5 +6,6 @@ export const route: Route = {
66 summary:
77 "The crew's Claude accounts, as MCP `subscriptions` says it, never a token (a forge admin); answers { items: [{ id, name, kind, apiBase, scopes, budgetUsd, spentUsd, restingUntil, restingSaid, lastUsedAt }] }",
88 auth: true,
9 role: "admin",
910 refusals: { not_admin: "the caller is not a forge admin (403)" },
1011 };
src/contract/openapi/routes/getApiV1AdminTeams.ts +1
@@ -6,5 +6,6 @@ export const route: Route = {
66 summary:
77 "Read the crew's teams (a person admin's token, any scope); answers { teams: [{ slug, charter, dial, members, ... }] }",
88 auth: true,
9 role: "admin",
910 refusals: { not_found: "the token is not a person who administers the forge" },
1011 };
src/contract/openapi/routes/getApiV1AdminTrafficProbe.ts +1
@@ -6,5 +6,6 @@ export const route: Route = {
66 summary:
77 "Read the traffic view's numbers (where people come from) against their source (a person admin's token, any scope); query: scope (forge, or an app id), window (7d, 30d, 90d), who (people, scrapers), number (one id), dimension (source, medium, campaign, landing, exit) with key, steps (comma-separated path steps ending in a goal); answers { as_of, scope, window, numbers: [{ id, label, value, counts, inputs, recomputed, agrees }] }: recomputed counts the stored page views again in SQL",
88 auth: true,
9 role: "admin",
910 refusals: { not_found: "the token is not a person who administers the forge, or the scope names no app" },
1011 };
src/contract/openapi/routes/getApiV1Agents.ts +1 −1
@@ -1,3 +1,3 @@
11 import type { Route } from "../shared.ts";
22
3 export const route: Route = { method: "get", path: "/api/v1/agents", summary: "List the crew's agents; answers { items, next_cursor: null }; needs a signed-in caller, anonymous gets 401", auth: true };
3 export const route: Route = { method: "get", path: "/api/v1/agents", summary: "List the crew's agents; answers { items, next_cursor: null }; needs a signed-in caller, anonymous gets 401", auth: true, role: "member" };
src/contract/openapi/routes/getApiV1AgentsUsername.ts +1 −1
@@ -1,3 +1,3 @@
11 import type { Route } from "../shared.ts";
22
3 export const route: Route = { method: "get", path: "/api/v1/agents/{username}", summary: "Read one agent; the brief, model and budget show to those who may see them", auth: false, refusals: { not_found: "no such agent, or not yours to see" } };
3 export const route: Route = { method: "get", path: "/api/v1/agents/{username}", summary: "Read one agent; the brief, model and budget show to those who may see them", auth: false, role: "anonymous", refusals: { not_found: "no such agent, or not yours to see" } };
src/contract/openapi/routes/getApiV1AppTemplates.ts +1
@@ -6,5 +6,6 @@ export const route: Route = {
66 summary:
77 "The repositories New app offers as templates that you can read, `trevato/app-template` first; answers { templates: [\"owner/name\"] }",
88 auth: true,
9 role: "member",
910 refusals: {},
1011 };
src/contract/openapi/routes/getApiV1Apps.ts +1 −1
@@ -1,3 +1,3 @@
11 import type { Route } from "../shared.ts";
22
3 export const route: Route = { method: "get", path: "/api/v1/apps", summary: "List the apps whose repositories you can read; answers { items, next_cursor }", auth: false };
3 export const route: Route = { method: "get", path: "/api/v1/apps", summary: "List the apps whose repositories you can read; answers { items, next_cursor }", auth: false, role: "anonymous" };
src/contract/openapi/routes/getApiV1Asked.ts +1
@@ -6,6 +6,7 @@ export const route: Route = {
66 path: "/api/v1/asked",
77 summary: "What people asked, and the caller's thought stream; answers { asked, thoughts } (ro scope)",
88 auth: true,
9 role: "member",
910 query: { show: "string", order: "string", repo: "string" },
1011 refusals: { invalid_show: "show is all, unshipped or flagged", invalid_order: ORDER_WORDS },
1112 };
src/contract/openapi/routes/getApiV1Asks.ts +1
@@ -5,4 +5,5 @@ export const route: Route = {
55 path: "/api/v1/asks",
66 summary: "What asks the caller, ranked, with the week's measure (ro scope; never cached)",
77 auth: true,
8 role: "member",
89 };
src/contract/openapi/routes/getApiV1Boards.ts +1
@@ -5,4 +5,5 @@ export const route: Route = {
55 path: "/api/v1/boards",
66 summary: "List the owner's boards; answers { boards: [{ slug, title, range, refresh, panels }] }. Site admin people only",
77 auth: true,
8 role: "admin",
89 };
src/contract/openapi/routes/getApiV1BoardsPins.ts +1
@@ -5,4 +5,5 @@ export const route: Route = {
55 path: "/api/v1/boards/pins",
66 summary: "List the owner's pinned panels in order; answers { pins: [{ slug, title, panel }] }",
77 auth: true,
8 role: "admin",
89 };
src/contract/openapi/routes/getApiV1BoardsSlug.ts +1
@@ -5,4 +5,5 @@ export const route: Route = {
55 path: "/api/v1/boards/{slug}",
66 summary: "Read one board: { slug, title, range, refresh, panels: [{ title, chart, query, w, h }] }",
77 auth: true,
8 role: "admin",
89 };
src/contract/openapi/routes/getApiV1BoardsSlugPanelsId.ts +1
@@ -6,6 +6,7 @@ export const route: Route = {
66 summary:
77 "Read one panel's rows; `id` is its place on the board from 0. Answers { board, panel, title, chart, rows, from, to, step }, or a CSV table with format=csv",
88 auth: true,
9 role: "admin",
910 query: { format: "string", from: "string", to: "string", step: "string" },
1011 refusals: {
1112 invalid_format: "format is not json or csv (422)",
src/contract/openapi/routes/getApiV1BoardsSources.ts +1
@@ -5,4 +5,5 @@ export const route: Route = {
55 path: "/api/v1/boards/sources",
66 summary: "List the sources a board query reads; answers { sources: [{ name, time, columns, docs? }] }",
77 auth: true,
8 role: "admin",
89 };
src/contract/openapi/routes/getApiV1Changelog.ts +1
@@ -6,6 +6,7 @@ export const route: Route = {
66 summary: "What went live and what is on its way, as /changelog shows it, for anyone: version, commit, live and onItsWay rows (repo, number, title, at, and for the forge's own landings by: who built it, with: the models and people its commit credits) of public repositories; ?repo=all|owner/name, this forge's own by default (all when it has none); carries a weak ETag of the live version, the chosen repo and the rows, and a repeat sending If-None-Match gets 304 with no body and the same ETag and cache-control",
77 alsoStatus: [304],
88 auth: false,
9 role: "anonymous",
910 query: { repo: "string" },
1011 refusals: {},
1112 };
src/contract/openapi/routes/getApiV1CloudAccounts.ts +1
@@ -5,6 +5,7 @@ export const route: Route = {
55 path: "/api/v1/cloud/accounts",
66 summary: "The forge's cloud accounts, each attached provider token with its use, verification, write permission and today's calls, never a secret; a forge administrator's, with a token that may write",
77 auth: true,
8 role: "admin",
89 refusals: {
910 forbidden: "a read-only token cannot list cloud accounts",
1011 not_found: "only a forge administrator sees this route",
src/contract/openapi/routes/getApiV1Contribute.ts +1 −1
@@ -1,3 +1,3 @@
11 import type { Route } from "../shared.ts";
22
3 export const route: Route = { method: "get", path: "/api/v1/contribute", summary: "What an agent that arrives should know: { forge, bottleneck, welcomeLabel, issues, propose, telemetry }; issues are the open welcome-labelled ones in public repositories", auth: false };
3 export const route: Route = { method: "get", path: "/api/v1/contribute", summary: "What an agent that arrives should know: { forge, bottleneck, welcomeLabel, issues, propose, telemetry }; issues are the open welcome-labelled ones in public repositories", auth: false, role: "anonymous" };
src/contract/openapi/routes/getApiV1ConversationFilesId.ts +1
@@ -5,5 +5,6 @@ export const route: Route = {
55 path: "/api/v1/conversation/files/{id}",
66 summary: "Download one of the signed-in person's own attachments: always as an attachment with a fixed type (an image type or text/plain) and nosniff; another person's is not_found",
77 auth: true,
8 role: "member",
89 refusals: { not_found: "no such attachment (404)" },
910 };
src/contract/openapi/routes/getApiV1ConversationTurns.ts +1
@@ -6,6 +6,7 @@ export const route: Route = {
66 path: "/api/v1/conversation/turns",
77 summary: "The signed-in person's conversation with the forge's agent: the latest turns, oldest first, and whether an answer is being written now",
88 auth: true,
9 role: "member",
910 query: { limit: limitTo(MAX_PAGE), before: "string", conversation: "string" },
1011 reply: { running: "boolean", turns: "array" },
1112 };
src/contract/openapi/routes/getApiV1Conversations.ts +1
@@ -5,6 +5,7 @@ export const route: Route = {
55 path: "/api/v1/conversations",
66 summary: "The signed-in person's conversations with the forge's agent, newest activity first; archived: 1 lists the put-away ones",
77 auth: true,
8 role: "member",
89 query: { archived: "string" },
910 reply: { conversations: "array" },
1011 };
src/contract/openapi/routes/getApiV1Credit.ts +1
@@ -6,5 +6,6 @@ export const route: Route = {
66 summary:
77 "On a forge its parent funds: the cap and the spend of the credit it was lent, asked of the parent afresh; answers { capUsd, spentUsd, says } (\"funded by its parent: $X of $200 left\"). 404 when no parent funds this forge",
88 auth: true,
9 role: "member",
910 refusals: { not_found: "no parent funds this forge" },
1011 };
src/contract/openapi/routes/getApiV1CrewNow.ts +1
@@ -5,5 +5,6 @@ export const route: Route = {
55 path: "/api/v1/crew/now",
66 summary: "What the crew is doing right now, as MCP `now` answers it: { items: [{ run, app, who, on, why, state, since, costUsd, ran? }], next_cursor: null } for the apps the caller may read; a planner that declined the direction and waits for the caller comes first (`run: null`, its words in `says`), then pulls waiting for their review",
77 auth: true,
8 role: "member",
89 refusals: { unauthorized: "no valid credentials were sent" },
910 };
src/contract/openapi/routes/getApiV1CrewScorecard.ts +1
@@ -5,5 +5,6 @@ export const route: Route = {
55 path: "/api/v1/crew/scorecard",
66 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; also modelDays (runs, landed and spend per model per day) and wastedDays: [{ day, wasted, runs, spentUsd }], the runs that ended on work not theirs to do",
77 auth: true,
8 role: "admin",
89 refusals: { unauthorized: "no valid credentials were sent", forbidden: "the caller is not a forge admin" },
910 };
src/contract/openapi/routes/getApiV1CrewWeek.ts +1
@@ -5,5 +5,6 @@ export const route: Route = {
55 path: "/api/v1/crew/week",
66 summary: "Read the crew's last 7 days (a forge admin's, any token scope): { days, members: [{ username, merged, returned, spentUsd, idle }] }, merged pulls, pulls returned by review and ledger spend per agent; idle is true when all three are nothing",
77 auth: true,
8 role: "admin",
89 refusals: { unauthorized: "no valid credentials were sent", forbidden: "the caller is not a forge admin" },
910 };
src/contract/openapi/routes/getApiV1Explore.ts +1 −1
@@ -1,3 +1,3 @@
11 import type { Route } from "../shared.ts";
22
3 export const route: Route = { method: "get", path: "/api/v1/explore", summary: "Explore apps and repositories; `q` takes words and `kind:`/`owner:` filters; answers { query, items, next_cursor } (60 rows a page, next_cursor null on the last); cursor from next_cursor; a bad cursor is a 400; the answer carries an ETag (cache-control private, no-cache; vary authorization, cookie) that changes when anything listed changes, and a repeat sending it in If-None-Match gets 304 with no body", auth: false, alsoStatus: [304], query: { q: "string", cursor: "string" } };
3 export const route: Route = { method: "get", path: "/api/v1/explore", summary: "Explore apps and repositories; `q` takes words and `kind:`/`owner:` filters; answers { query, items, next_cursor } (60 rows a page, next_cursor null on the last); cursor from next_cursor; a bad cursor is a 400; the answer carries an ETag (cache-control private, no-cache; vary authorization, cookie) that changes when anything listed changes, and a repeat sending it in If-None-Match gets 304 with no body", auth: false, role: "anonymous", alsoStatus: [304], query: { q: "string", cursor: "string" } };
src/contract/openapi/routes/getApiV1Family.ts +1
@@ -5,4 +5,5 @@ export const route: Route = {
55 path: "/api/v1/family",
66 summary: "The forge's family: { parent, here, children, more }; the parent for anyone, the children for a site admin",
77 auth: false,
8 role: "anonymous",
89 };
src/contract/openapi/routes/getApiV1Features.ts +1
@@ -5,5 +5,6 @@ export const route: Route = {
55 path: "/api/v1/features",
66 summary: "Every app whose forge.json declares a `feature`, absorbed or not, with its typed settings and kept values (forge admin); answers { items, next_cursor }",
77 auth: true,
8 role: "member",
89 refusals: { not_found: "you are not a site admin" },
910 };
src/contract/openapi/routes/getApiV1FeaturesSlug.ts +1
@@ -5,5 +5,6 @@ export const route: Route = {
55 path: "/api/v1/features/{slug}",
66 summary: "One app's feature: name, page, whether it is absorbed, and each setting with its type, value, default and the FORGE_SETTING_ name its process reads (for whoever may read the app)",
77 auth: false,
8 role: "anonymous",
89 refusals: { not_found: "no such app, or one you may not read", not_a_feature: "the app's forge.json declares no feature (422)" },
910 };
src/contract/openapi/routes/getApiV1FeaturesSlugFeed.ts +1
@@ -5,5 +5,6 @@ export const route: Route = {
55 path: "/api/v1/features/{slug}/feed",
66 summary: "The feed items about one app's feature, newest first, as {items, next_cursor} (for whoever may read the app)",
77 auth: false,
8 role: "anonymous",
89 refusals: { not_found: "no such app, or one you may not read", not_a_feature: "the app's forge.json declares no feature (422)" },
910 };
src/contract/openapi/routes/getApiV1Feed.ts +1
@@ -6,6 +6,7 @@ export const route: Route = {
66 path: "/api/v1/feed",
77 summary: "The caller's own feed, newest first (ro scope; never cached); answers { items, next cursor } to pass back as `cursor`; `before=now` (then each `next_cursor` as `before`) reads the log newest first instead, one event id as cursor, a page costing the same at any depth, no repeat or skip when posts arrive; without a `cursor` it also carries `digest`, home's \"Since you were here\" card, `{ heading, lines: [{ repo, says, href }] }` or null (a page with a cursor has no `digest` key); reading never counts as a visit; the ETag moves with the digest",
88 auth: true,
9 role: "member",
910 query: { limit: limitTo(FEED_LIMIT_MAX), cursor: "string", before: "string" },
1011 refusals: { invalid_limit: "limit is not a whole number from 1 to 50 (422)", invalid_cursor: "the cursor was not issued here (422)" },
1112 };
src/contract/openapi/routes/getApiV1FeedChips.ts +1
@@ -5,4 +5,5 @@ export const route: Route = {
55 path: "/api/v1/feed/chips",
66 summary: "The chips the caller switched off, kinds of post and people (ro scope; never cached); answers { kinds, people }",
77 auth: true,
8 role: "member",
89 };
src/contract/openapi/routes/getApiV1FeedLikes.ts +1
@@ -5,5 +5,6 @@ export const route: Route = {
55 path: "/api/v1/feed/likes",
66 summary: "The like ledger of one feed item, newest first, 50 a page (ro scope): `?item=` is a feed item id, `app` (owner/name) names the repository when the item is not an event; `before` is a seq to read the rows older than; answers { item, ledger: [{ seq, who, act, at }], more }, `more` true when older rows remain, `act` being like or unlike; an anonymous caller is told to sign in at /signin",
77 auth: true,
8 role: "member",
89 query: { item: "string", app: "string", before: "string" },
910 };
src/contract/openapi/routes/getApiV1Flags.ts +1
@@ -6,5 +6,6 @@ export const route: Route = {
66 summary:
77 "Every declared flag with why it exists, its default, the issue that owns removing it and its live value, as MCP `flags` answers (a person admin's token); answers { flags }",
88 auth: true,
9 role: "member",
910 refusals: { forbidden: "the token is not a person who administers the forge" },
1011 };
src/contract/openapi/routes/getApiV1FleetGraph.ts +1
@@ -5,6 +5,7 @@ export const route: Route = {
55 path: "/api/v1/fleet/graph",
66 summary: "Spend and runs per repository by UTC hour (24 h) and day (14 d); `repo` (owner/name) repeats, else every repository the caller may write that spent lately",
77 auth: true,
8 role: "member",
89 query: { repo: "string" },
910 refusals: { invalid_selection: "`repo` is not a list of owner/name" },
1011 };
src/contract/openapi/routes/getApiV1FleetOps.ts +1
@@ -6,6 +6,7 @@ export const route: Route = {
66 path: "/api/v1/fleet/ops",
77 summary: "List the fleet operations, newest first; answers { items, next_cursor }",
88 auth: true,
9 role: "member",
910 query: { limit: limitTo(LIST_LIMIT_MAX), cursor: "string" },
1011 refusals: { invalid_limit: "limit is not a whole number from 1 to 200 (422)" },
1112 };
src/contract/openapi/routes/getApiV1FleetOpsId.ts +1 −1
@@ -1,3 +1,3 @@
11 import type { Route } from "../shared.ts";
22
3 export const route: Route = { method: "get", path: "/api/v1/fleet/ops/{id}", summary: "Read one fleet operation and what it changed", auth: false };
3 export const route: Route = { method: "get", path: "/api/v1/fleet/ops/{id}", summary: "Read one fleet operation and what it changed", auth: false, role: "anonymous" };
src/contract/openapi/routes/getApiV1FleetRows.ts +1
@@ -5,6 +5,7 @@ export const route: Route = {
55 path: "/api/v1/fleet/rows",
66 summary: "The rows a fleet query line selects, as /fleet shows them, only those the caller may read; answers { kind, columns, items }",
77 auth: false,
8 role: "anonymous",
89 query: { q: "string", sort: "string" },
910 refusals: {
1011 invalid_query: "q is empty, over 500 characters, or does not parse; the reason is the message (422)",
src/contract/openapi/routes/getApiV1Forge.ts +1 −1
@@ -1,3 +1,3 @@
11 import type { Route } from "../shared.ts";
22
3 export const route: Route = { method: "get", path: "/api/v1/forge", summary: "A spawned forge's word about itself: { claimed, lastActivityAt, link? }; lastActivityAt and link ({ state, revokedAt }) are null or absent unless the parent's secret is sent", auth: false, refusals: { not_found: "404: this forge was not spawned" } };
3 export const route: Route = { method: "get", path: "/api/v1/forge", summary: "A spawned forge's word about itself: { claimed, lastActivityAt, link? }; lastActivityAt and link ({ state, revokedAt }) are null or absent unless the parent's secret is sent", auth: false, role: "anonymous", refusals: { not_found: "404: this forge was not spawned" } };
src/contract/openapi/routes/getApiV1ForgeEconomics.ts +1
@@ -5,6 +5,7 @@ export const route: Route = {
55 path: "/api/v1/forge/economics",
66 summary: "The profit slider: position (0 cheapest to 100 most responsive), marginPct, the policy the position means (sleep windows in ms, idle-pull window, cloud spill share; null is never), placement (whether a cloud body takes bursts, the owned-node label, in words), the operator's prices in US dollars, this UTC month's cost so far ({ owned, cloud, storage, crew, total } in USD) and the last changes (a person admin's token)",
77 auth: true,
8 role: "admin",
89 refusals: {
910 not_found: "the token is not a person who administers the forge (404)",
1011 not_admin: "the person's role may not administer the forge (403)",
src/contract/openapi/routes/getApiV1ForgeView.ts +1
@@ -5,5 +5,6 @@ export const route: Route = {
55 path: "/api/v1/forge/view",
66 summary: "The forge as one read-only picture for a reader that draws it: { counts: { apps, running, queued, waiting }, apps: [{ app, slug, access, running, queued }], runs: [the rows of /api/v1/crew/now; each run's state is its station], health: as /api/v1/health answers this caller, at } for the apps the caller may read. Any token scope reads it; it is built at most once per two seconds per caller and answers 304 to a held ETag, so polling it is cheap",
77 auth: true,
8 role: "member",
89 refusals: { unauthorized: "no valid credentials were sent" },
910 };
src/contract/openapi/routes/getApiV1Forges.ts +1 −1
@@ -1,3 +1,3 @@
11 import type { Route } from "../shared.ts";
22
3 export const route: Route = { method: "get", path: "/api/v1/forges", summary: "List the forges sent from here (a site admin's, ro scope): the client list /forges and the MCP forges tool show. answers { items, next_cursor: null }; each item has name, url, for, apps, sentAt, made, claimed (null when it did not answer), lastActivityAt, running, reach, attention (silent, down, unclaimed or null), spendTodayUsd, ceilingUsd, size, use, lastHeartbeatAt and nextStep, and beside them what the list always carried: bodyId, done, createdAt, placement, placementNote and template (when one was chosen). what needs attention comes first", auth: true };
3 export const route: Route = { method: "get", path: "/api/v1/forges", summary: "List the forges sent from here (a site admin's, ro scope): the client list /forges and the MCP forges tool show. answers { items, next_cursor: null }; each item has name, url, for, apps, sentAt, made, claimed (null when it did not answer), lastActivityAt, running, reach, attention (silent, down, unclaimed or null), spendTodayUsd, ceilingUsd, size, use, lastHeartbeatAt and nextStep, and beside them what the list always carried: bodyId, done, createdAt, placement, placementNote and template (when one was chosen). what needs attention comes first", auth: true, role: "admin" };
src/contract/openapi/routes/getApiV1ForgesSizes.ts +1 −1
@@ -1,3 +1,3 @@
11 import type { Route } from "../shared.ts";
22
3 export const route: Route = { method: "get", path: "/api/v1/forges/sizes", summary: "What small, medium and large mean; public, cacheable by ETag", auth: false };
3 export const route: Route = { method: "get", path: "/api/v1/forges/sizes", summary: "What small, medium and large mean; public, cacheable by ETag", auth: false, role: "anonymous" };
src/contract/openapi/routes/getApiV1ForgesTemplates.ts +1 −1
@@ -1,3 +1,3 @@
11 import type { Route } from "../shared.ts";
22
3 export const route: Route = { method: "get", path: "/api/v1/forges/templates", summary: "Which templates a new forge may start from, blank first; public, cacheable by ETag", auth: false };
3 export const route: Route = { method: "get", path: "/api/v1/forges/templates", summary: "Which templates a new forge may start from, blank first; public, cacheable by ETag", auth: false, role: "anonymous" };
src/contract/openapi/routes/getApiV1Front.ts +1 −1
@@ -1,3 +1,3 @@
11 import type { Route } from "../shared.ts";
22
3 export const route: Route = { method: "get", path: "/api/v1/front", summary: "The front page a stranger is shown: { featured, apps, total }, each entry { repo, description, url, state, picture, pinned, score, signals }; a site admin also gets `picks`", auth: false };
3 export const route: Route = { method: "get", path: "/api/v1/front", summary: "The front page a stranger is shown: { featured, apps, total }, each entry { repo, description, url, state, picture, pinned, score, signals }; a site admin also gets `picks`", auth: false, role: "anonymous" };
src/contract/openapi/routes/getApiV1Health.ts +1
@@ -5,4 +5,5 @@ export const route: Route = {
55 path: "/api/v1/health",
66 summary: "How the forge is, uncached; anyone may ask, a stranger is told little, the release probe and a site admin the whole of it",
77 auth: false,
8 role: "anonymous",
89 };
src/contract/openapi/routes/getApiV1Here.ts +1 −1
@@ -1,3 +1,3 @@
11 import type { Route } from "../shared.ts";
22
3 export const route: Route = { method: "get", path: "/api/v1/here", summary: "The manifest of what you can do at `path` (default /), given who you are: { here, you, can, mcp, docs }; never cached", auth: false, query: { path: "string" }, refusals: { not_found: "404: the path names no page you can read" } };
3 export const route: Route = { method: "get", path: "/api/v1/here", summary: "The manifest of what you can do at `path` (default /), given who you are: { here, you, can, mcp, docs }; never cached", auth: false, role: "anonymous", query: { path: "string" }, refusals: { not_found: "404: the path names no page you can read" } };
src/contract/openapi/routes/getApiV1Keeper.ts +1
@@ -5,6 +5,7 @@ export const route: Route = {
55 path: "/api/v1/keeper",
66 summary: "The release keeper's releases, holds and what blocks the next cut, for the forge's administrators and its repository's readers",
77 auth: false,
8 role: "anonymous",
89 refusals: {
910 forbidden: "the release history is for the forge's administrators and its repository's readers",
1011 },
src/contract/openapi/routes/getApiV1Map.ts +1 −1
@@ -1,3 +1,3 @@
11 import type { Route } from "../shared.ts";
22
3 export const route: Route = { method: "get", path: "/api/v1/map", summary: "The global map: every repository the caller can see, with its issues and dependency edges", auth: false };
3 export const route: Route = { method: "get", path: "/api/v1/map", summary: "The global map: every repository the caller can see, with its issues and dependency edges", auth: false, role: "anonymous" };
src/contract/openapi/routes/getApiV1MigrateJob.ts +1
@@ -5,5 +5,6 @@ export const route: Route = {
55 path: "/api/v1/migrate/{job}",
66 summary: "Read a migration job's stage as { job }; only the account that started it can",
77 auth: true,
8 role: "member",
89 refusals: { unauthorized: "401: no signed-in account", not_found: "404: no such job, or not the caller's" },
910 };

293 more files not shown.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs