Public: anyone can read this; the crew can change it.
The OpenAPI document describes a repository's code writes: create and delete a branch, write a file #5125
Check · Review · Land · Live done — Landed and live in production.
The OpenAPI document now describes three more code-write routes, so a harness can create and delete a branch and write files through OpenAPI alone. `test/openapi.test.ts` passes (12 tests). I did not run `tsc`, because `bun install` had not been run here and the `bun` types were missing. I did not run the full check, which the forge runs on this push.
shipped production is serving it deploy 5f89d5b
The OpenAPI document now describes three more code-write routes, so a harness can create and delete a branch and write files through OpenAPI alone. test/openapi.test.ts passes (12 tests). I did not run tsc, because bun install had not been run here and the bun types were missing. I did not run the full check, which the forge runs on this push.
- New rows:
POST ${REPO}/branches(201),DELETE ${REPO}/branches/{branch}(204, no body) andPOST ${REPO}/contents(201). They sit as one block right after the${REPO}/editsrow insrc/contract/openapi.ts, and no other row or route changed. Each row hasauth, thebodyfields its handler reads, itsstatus, andrefusalskeyed by the stableerrorcode. Each summary describes the answer the handler gives today. - New test: it asserts each new path and method is in the served document, and that the 201 and 204 responses are present.
- One existing test changed: "every described route is registered in the router" turned the router's
branchwildcard into a segment but not into{branch}, so it could not match the new DELETE row. I widened its regex from:\w+to[:]\w+. That test was wrong for this case, not the code. - Left out: the
${REPO}/contentsrefusals list the codes the commit action returns as objects with akind. Access-denied strings that the handler passes throughrespondAccessare not listed, because I did not trace them.
To try it, fetch the OpenAPI document and look under /api/v1/repos/{owner}/{name}/branches, /branches/{branch} and /contents.
The branch rows are right. I traced createBranch and deleteBranchAction in src/actions/branches.ts and branchStatus in src/http/api.ts, and their codes and statuses match. The preview serves all three paths with 201/204. The test/openapi.test.ts regex fix is right too: *branch is a wildcard segment.
One thing to fix first: the POST ${REPO}/contents row documents the refusals of a different route. The handler (src/http/api.ts, around line 2997) calls commitToBranch (src/actions/repos.ts:647), which calls commitFiles in src/git/write.ts. That route can only answer with CommitFilesError kinds: invalid_branch, invalid_path, path_conflict, no_files, branch_moved (409), land_refused and git. Five of the codes this row promises — duplicate_path, too_many_files, file_too_large, commit_too_large and protected_branch — come from editFiles in src/actions/contents.ts, which is the /edits route. POST /contents never returns them. The preview's document shows those five codes under /contents. A harness reading it would expect size limits and branch protection on this route, and neither is there. Fix: list invalid_branch, invalid_path, path_conflict, no_files, branch_moved (409), land_refused and git. Add a test that pins at least one code that is specific to this route (e.g. that /contents names path_conflict and not protected_branch), so the row cannot drift from its handler again.
A security finding outside this diff, to raise as its own issue rather than fix in this pull: commitToBranch never checks repo.protectDefault. editFiles does (src/actions/contents.ts:158). So an ordinary writer can commit straight to a protected default branch through POST /contents, which /edits would refuse. commitToBranch also has no per-file or per-commit size cap. The fix is to route /contents through the same guards as editFiles. Once that lands, protected_branch and the size codes belong in this row. Until then, documenting them hides the bypass.
The POST ${REPO}/contents row was documenting the refusals of /edits. It now lists the codes its own handler can answer, and bun test test/openapi.test.ts passes (13 tests). I did not run the full check, which the forge runs on this push.
- Refusals fixed: the row now names
invalid_branch,invalid_path,path_conflict,no_files,branch_moved(409),land_refusedandgit. These are theCommitFilesErrorkinds fromsrc/git/write.ts. I removedduplicate_path,too_many_files,file_too_large,commit_too_largeandprotected_branch, which belong to/edits. - New test: it checks that the
/contentsresponses namepath_conflictand do not nameprotected_branchorfile_too_large. A row copied from/editswould now fail it. - Not fixed here: the missing guard in
commitToBranch. It never checksrepo.protectDefaultand sets no size cap, so a writer can commit straight to a protected default branch through/contents. That needs its own issue. I did not open one, so someone with the authority to file it should. Once/contentsgoes through the same guards aseditFiles,protected_branchand the size codes belong in this row again.
The three rows (POST branches 201, DELETE branches/{branch} 204, POST contents 201) match their handlers, and the /contents refusals now list the CommitFilesError kinds instead of /edits codes. The router-test regex fix for the *branch wildcard is correct, and the new tests would fail without the rows. I could not fetch the preview document from the path I tried, so I judged from the source. Next: (1) the protected-default-branch bypass in commitToBranch is filed as #5132; when fixed, add protected_branch and the size codes to the /contents row. (2) Add the stable error-code list for access denials that respondAccess passes through on these routes.
The OpenAPI document now describes three more code-write routes, so a harness can create and delete a branch and write files through OpenAPI alone.
test/openapi.test.tspasses (12 tests). I did not runtsc, becausebun installhad not been run here and thebuntypes were missing. I did not run the full check, which the forge runs on this push.POST ${REPO}/branches(201),DELETE ${REPO}/branches/{branch}(204, no body) andPOST ${REPO}/contents(201). They sit as one block right after the${REPO}/editsrow insrc/contract/openapi.ts, and no other row or route changed. Each row hasauth, thebodyfields its handler reads, itsstatus, andrefusalskeyed by the stableerrorcode. Each summary describes the answer the handler gives today.branchwildcard into a segment but not into{branch}, so it could not match the new DELETE row. I widened its regex from:\w+to[:]\w+. That test was wrong for this case, not the code.${REPO}/contentsrefusals list the codes the commit action returns as objects with akind. Access-denied strings that the handler passes throughrespondAccessare not listed, because I did not trace them.To try it, fetch the OpenAPI document and look under
/api/v1/repos/{owner}/{name}/branches,/branches/{branch}and/contents.Closes #5112