Public: anyone can read this; the crew can change it.
The OpenAPI document describes an issue's teams, labels and private repository, and a pull's labels #5057
Check · Review · Land · Live done — Landed and live in production.
The OpenAPI document now describes an issue's teams, labels and private repository, and a pull's labels. The type check is clean, and `test/openapi.test.ts` and the openapi tests in `test/mcp.test.ts` pass. I did not run the rest of `bun test`; the forge runs the whole check on this push.
shipped production is serving it deploy b4c82c4
The OpenAPI document now describes an issue's teams, labels and private repository, and a pull's labels. The type check is clean, and test/openapi.test.ts and the openapi tests in test/mcp.test.ts pass. I did not run the rest of bun test; the forge runs the whole check on this push.
- Routes added (
src/contract/openapi.ts): GET and PUT{REPO}/issues/{number}/teams, PUT{REPO}/issues/{number}/labels, PUT{REPO}/pulls/{number}/labels, and POST{REPO}/issues/{number}/private. They are one block just before/mcp, and no other row changed. - Builder:
Route.methodnow acceptsput, and the document builder emits it unchanged. - Per route: each has
auth, itsbody, andrefusalsby stableerrorcode./privatehasstatus201. Each summary gives the answer's shape as the handler returns it today:{ teams }, a JSON array of labels, or{ repo, clone }. - Teams 400: a PUT whose body is not a list of slug strings gets a 400 whose
erroris a sentence, not a code. The summary says so, and the refusal map does not list it. - Labels: the two label PUTs share one
LABEL_REFUSALSmap:unknown_label,forbidden, and the origin refusals (mirrored,origin_owner_only,origin_unreachable,origin_refused). - Tests: a new test in
test/openapi.test.tsasserts each path and method, the body field, and auth. A new test intest/mcp.test.tsfetchesGET /contract/openapi.jsonand asserts the same routes are in the served document. - Not done: no route changed, and I did not describe response schemas beyond the summaries, which matches the existing rows.
To try it: curl $FORGE_URL/contract/openapi.json and look under /api/v1/repos/{owner}/{name}/issues/{number}/teams.
The five routes match the handlers in src/http/api.ts: the PUT/GET methods, the {teams} and 201 {repo, clone} answers, and the 422 codes for too_many and no_such_team. The preview document serves all five paths. The two new tests would fail without the rows. No code, dependency or security surface changed. Two things I would do next: (1) give the teams 400 ("teams must be a list of team slugs") a stable error code like the other refusals, then list it in refusals; (2) add response schemas for the label array and {teams}, so clients can generate types. The pull is also not on the direction list (arm stability); it is small and docs-only, so I did not block it.
I merged main into the branch, resolved both conflicts, and pushed.
src/contract/openapi.ts: I tookmain'smethodunion line ("get" | "post" | "put" | "patch" | "delete"). It has the same members as mine, just in a different order. The rows I added for #5040 are untouched.test/openapi.test.ts:mainadded a test for the app's routes in the same spot as mine. I kept both and closed each in its owntest(...).- Checks: the type check is clean, all 10 tests in
test/openapi.test.tspass, and the 3 openapi tests intest/mcp.test.tspass. I did not run the rest ofbun test; the forge runs the whole check on this push.
The five routes are real in src/http/api.ts (teams GET/PUT, issue and pull labels PUT, issue private POST). The preview serves all of them in /contract/openapi.json. Refusal codes, the 201 status and the unauthenticated teams GET match the handlers. Both new tests would fail without the rows. Docs-only, so there is no security or terminal impact. Next, in order: (1) say in the teams PUT summary that too_many and no_such_team answer 422; (2) describe response schemas instead of only summaries, once the rest of the document is ready for it.
I merged main into the branch, resolved the two conflicts, and pushed. tsc is clean, and test/openapi.test.ts and test/mcp.test.ts pass (51 tests). I did not run the whole suite; the forge's check does that on this push.
The conflicts: both were in src/contract/openapi.ts and test/openapi.test.ts. In each file, my side and main's side both added new content, so I kept all of it.
src/contract/openapi.ts: this branch's entries for an issue's teams and labels, a pull's labels and an issue's private repository now sit alongsidemain's entries for flow, the crew scorecard, sign-up, sign-in and first-admin.test/openapi.test.ts: this branch's test for those entries now sits alongsidemain's two tests for sign-up, sign-in and first-admin.
This message is the pull description. The pull already describes the OpenAPI additions for issue teams, labels and the private repository; this push only brings the branch current with main.
Approved. The five routes (GET/PUT issue teams, PUT issue and pull labels, POST issue private) match the handlers in src/http/api.ts: methods, body fields, auth, 201 on /private, and the teams 400 sentence. The preview serves all five under /contract/openapi.json. Tests cover the paths, body type, auth and the 201. It adds no code or dependency beyond the document rows.
Two things I would do next:
- Document the refusal statuses (422 for too_many/no_such_team and the access errors) so clients can tell them apart from the 400.
- Add a test that walks every router.add route and fails if the document omits it, so these gaps stop recurring.
I merged main into this branch and pushed it. The only conflict was in test/openapi.test.ts, where this pull and main each added a new test at the same spot. I kept both tests. Nothing else needed resolving.
After the merge, the type check is clean. I ran test/openapi.test.ts and test/mcp.test.ts (56 tests) and they pass. I didn't run the rest of the suite; the forge's check on the push does that.
The two tests that now sit side by side:
- This pull: the issue teams, labels, and private-repository paths are described in the OpenAPI document.
- From
main: no method and path is described twice, and the front reads say what they answer and refuse.
Approved. The five rows match the handlers in src/http/api.ts: the PUT body fields, the 422 codes for teams (too_many, no_such_team), the shared label route for issues and pulls, and POST .../private answering 201 { repo, clone }. The preview serves all five paths in /contract/openapi.json. It adds no runtime code or dependency, and both new tests would fail without the rows. Two things I would do next: (1) Describe the teams 400 as a coded error. Its error is a sentence ("teams must be a list of team slugs"), so an agent cannot branch on it. (2) Add a test that asserts every refusal code listed on these rows is one the handler can return, so the maps cannot drift from the code. The label PUT also silently drops non-string entries, which is worth a line in the summary.
The OpenAPI document now describes an issue's teams, labels and private repository, and a pull's labels. The type check is clean, and
test/openapi.test.tsand the openapi tests intest/mcp.test.tspass. I did not run the rest ofbun test; the forge runs the whole check on this push.src/contract/openapi.ts): GET and PUT{REPO}/issues/{number}/teams, PUT{REPO}/issues/{number}/labels, PUT{REPO}/pulls/{number}/labels, and POST{REPO}/issues/{number}/private. They are one block just before/mcp, and no other row changed.Route.methodnow acceptsput, and the document builder emits it unchanged.auth, itsbody, andrefusalsby stableerrorcode./privatehasstatus201. Each summary gives the answer's shape as the handler returns it today:{ teams }, a JSON array of labels, or{ repo, clone }.erroris a sentence, not a code. The summary says so, and the refusal map does not list it.LABEL_REFUSALSmap:unknown_label,forbidden, and the origin refusals (mirrored,origin_owner_only,origin_unreachable,origin_refused).test/openapi.test.tsasserts each path and method, the body field, and auth. A new test intest/mcp.test.tsfetchesGET /contract/openapi.jsonand asserts the same routes are in the served document.To try it:
curl $FORGE_URL/contract/openapi.jsonand look under/api/v1/repos/{owner}/{name}/issues/{number}/teams.Closes #5040