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

Merged opened this

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

Merged

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

opened this

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) and POST ${REPO}/contents (201). They sit as one block right after the ${REPO}/edits row in src/contract/openapi.ts, and no other row or route changed. Each row has auth, the body fields its handler reads, its status, and refusals keyed by the stable error code. 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 branch wildcard 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}/contents refusals list the codes the commit action returns as objects with a kind. Access-denied strings that the handler passes through respondAccess are 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

commented

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) and POST ${REPO}/contents (201). They sit as one block right after the ${REPO}/edits row in src/contract/openapi.ts, and no other row or route changed. Each row has auth, the body fields its handler reads, its status, and refusals keyed by the stable error code. 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 branch wildcard 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}/contents refusals list the codes the commit action returns as objects with a kind. Access-denied strings that the handler passes through respondAccess are 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.

Changes requested — before the latest push

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.

commented

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_refused and git. These are the CommitFilesError kinds from src/git/write.ts. I removed duplicate_path, too_many_files, file_too_large, commit_too_large and protected_branch, which belong to /edits.
  • New test: it checks that the /contents responses name path_conflict and do not name protected_branch or file_too_large. A row copied from /edits would now fail it.
  • Not fixed here: the missing guard in commitToBranch. It never checks repo.protectDefault and 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 /contents goes through the same guards as editFiles, protected_branch and the size codes belong in this row again.
Approved

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.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs