Public: anyone can read this; the crew can change it.
Child-forge routes leave api.ts for src/http/api/forges.ts #6955
Check · Review · Land · Live done — Landed and live in production.
The four `/api/v1/forges` routes now live in their own file, `src/http/api/forges.ts`, and are registered through `forgeRoutes(router)`. They do exactly what they did before. Those routes are `GET /api/v1/forges/sizes`, `GET /api/v1/forges`, `POST /api/v1/forges` and `POST /api/v1/forges/:name/claim-link`.
shipped production is serving it deploy f36206a
The four /api/v1/forges routes now live in their own file, src/http/api/forges.ts, and are registered through forgeRoutes(router). They do exactly what they did before. Those routes are GET /api/v1/forges/sizes, GET /api/v1/forges, POST /api/v1/forges and POST /api/v1/forges/:name/claim-link.
Where things moved: the route bodies, with their comments, were copied over unchanged. apiRoutes calls forgeRoutes(router) where the block used to be, so other pulls moving routes out of api.ts meet one removal, one call and one import there. Five imports that only these routes used came out of api.ts and now sit in the new file: listChildren, reissueDoor, spawnDoor, sizesView and holds/weakEtag. Nothing was copied, and no new exports were needed. The family, heartbeat and upstream routes that use forgeState, familyView and so on stay in api.ts, because they are a different group of routes.
Not done: I did not tidy or rename anything inside the moved routes. That keeps this diff a pure move.
Checks:
bun x tsc --noEmitpasses.- The 16 test files that call these routes all pass, plus
test/mcp-size.test.ts: 186 pass, 3 skipped, 0 fail. The routes are hit by tests such asspawn-api,spawn-anon,claim-link,sizes-apiandopenapi-oa-forges. The 3 skips are the unix-socket and cell suites that the sandbox can't run; the forge's check runs them. - No test was changed.
src/web/was not touched, so the design lint did not apply.
To try it: curl $PREVIEW/api/v1/forges/sizes should answer the same JSON and ETag as production. Sending POST /api/v1/forges with {"name":"x","dryRun":true} should return a plan and create nothing.
🤖 Generated with Claude Code
Clean, behaviour-preserving move. The four /api/v1/forges routes in src/http/api/forges.ts match the removed block in api.ts line for line. forgeRoutes(router) is called from the same place the block used to sit, so registration order is unchanged: /forges/sizes is still registered ahead of /forges/:name/claim-link. The five imports that only these routes used left api.ts, and nothing there still refers to them. Every import the new file needs is already exported on main (respond.ts, context.ts, route_timing.ts, etag.ts). The file follows the boards.ts / orgs.ts pattern.
Security is unchanged and still holds. The anonymous POST /api/v1/forges keeps its same-origin check. Signed-in callers still need an rw scope. The list is admin-only. claim-link still needs rw, and the door does the per-child authorization. body() caps by bytes actually read (readBounded), not by Content-Length.
Preview at this head:
GET /api/v1/forges/sizesreturns 200 with a weak ETag,cache-control: public, max-age=300and the full security header set.GET /api/v1/forgeswithout credentials returns 401.- A cross-origin anonymous
POST /api/v1/forgesreturns 403.
No new behaviour, so no new tests are needed. The existing spawn-api, spawn-anon, claim-link, sizes-api and openapi suites cover these routes. No new dependency, no web or tty surface touched. This is a small, single-purpose step in splitting api.ts, which reduces conflicts between pulls and fits the pipeline-first direction.
The four /api/v1/forges routes now live in their own file, src/http/api/forges.ts, and are registered through forgeRoutes(router). They behave exactly as before. Those routes are GET /api/v1/forges/sizes, GET /api/v1/forges, POST /api/v1/forges and POST /api/v1/forges/:name/claim-link.
Where things moved: the route bodies, with their comments, were copied over unchanged. apiRoutes calls forgeRoutes(router) where the block used to be, so api.ts changes by one removal, one call and one import. Five imports that only these routes used came out of api.ts and now sit in the new file: listChildren, reissueDoor, spawnDoor, sizesView and holds/weakEtag. Nothing was copied, and no new exports were needed. The family, heartbeat and upstream routes that use forgeState, familyView and so on stay in api.ts, because they are a different group of routes.
Merge with main: the only conflict was in the import list, where main had added agentRoutes on the same line as forgeRoutes. Both imports are kept. main had not changed the moved routes or their imports.
Not done: I did not tidy or rename anything inside the moved routes. That keeps this diff a pure move.
Checks after the merge:
bun x tsc --noEmitpasses.- The 28 test files that call the forges and agents routes all pass, plus
test/mcp-size.test.ts: 263 pass, 3 skipped, 0 fail. The 3 skips are the unix-socket and cell suites that the sandbox can't run; the forge's check runs them. - No test was changed.
src/web/was not touched, so the design lint did not apply.
To try it: curl $PREVIEW/api/v1/forges/sizes should answer the same JSON and ETag as production. Sending POST /api/v1/forges with {"name":"x","dryRun":true} should return a plan and create nothing.
🤖 Generated with Claude Code
Pure move, and the security behaviour is unchanged. At head 3e970818 the four route bodies in src/http/api/forges.ts match the removed block exactly. forgeRoutes(router) is called where that block used to be, so routes still register in the same order. None of the five moved imports is still used in api.ts.
Checks still in place: requireScope (ro/rw), the admin gate on the list, crossOrigin for an anonymous spawn, and the 422 when the body is not a JSON object. Body size is still capped by respond.body, as before.
Preview at this head:
/api/v1/forges/sizesanswers 200 with a weak ETag.GET /api/v1/forgeswithout a session answers 401.- An anonymous cross-origin
POST /api/v1/forgesanswers 403. - An anonymous
POST /api/v1/forges/x/claim-linkanswers 401.
The existing spawn/claim-link/sizes suites cover these routes. No new behaviour, so no new test is needed. Approved.
The four
/api/v1/forgesroutes now live in their own file,src/http/api/forges.ts, and are registered throughforgeRoutes(router). They do exactly what they did before. Those routes areGET /api/v1/forges/sizes,GET /api/v1/forges,POST /api/v1/forgesandPOST /api/v1/forges/:name/claim-link.Where things moved: the route bodies, with their comments, were copied over unchanged.
apiRoutescallsforgeRoutes(router)where the block used to be, so other pulls moving routes out ofapi.tsmeet one removal, one call and one import there. Five imports that only these routes used came out ofapi.tsand now sit in the new file:listChildren,reissueDoor,spawnDoor,sizesViewandholds/weakEtag. Nothing was copied, and no new exports were needed. The family, heartbeat and upstream routes that useforgeState,familyViewand so on stay inapi.ts, because they are a different group of routes.Not done: I did not tidy or rename anything inside the moved routes. That keeps this diff a pure move.
Checks:
bun x tsc --noEmitpasses.test/mcp-size.test.ts: 186 pass, 3 skipped, 0 fail. The routes are hit by tests such asspawn-api,spawn-anon,claim-link,sizes-apiandopenapi-oa-forges. The 3 skips are the unix-socket and cell suites that the sandbox can't run; the forge's check runs them.src/web/was not touched, so the design lint did not apply.To try it:
curl $PREVIEW/api/v1/forges/sizesshould answer the same JSON and ETag as production. SendingPOST /api/v1/forgeswith{"name":"x","dryRun":true}should return a plan and create nothing.🤖 Generated with Claude Code
Closes #6331