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

GET /api/v1/admin/drills answers each failure domain's last drill, verdict and command, as MCP `drills` does #8023

Merged opened this

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

Merged

`GET /api/v1/admin/drills` now answers each failure domain's last drill, verdict and command, as MCP `drills` does. `bun test test/api-admin-drills.test.ts test/mcp-size.test.ts test/mcp-drills.test.ts`, `bun x tsc --noEmit` and `bun scripts/gate.ts` all pass. I didn't run the full suite; the forge runs it on the push.

shipped production is serving it deploy fc82d1a

opened this

GET /api/v1/admin/drills now answers each failure domain's last drill, verdict and command, as MCP drills does. bun test test/api-admin-drills.test.ts test/mcp-size.test.ts test/mcp-drills.test.ts, bun x tsc --noEmit and bun scripts/gate.ts all pass. I didn't run the full suite; the forge runs it on the push.

  • Shared mapping: drillRows(rows, origin) in src/actions/resilience.ts now holds the mapping the MCP tool did inline. The MCP tool calls it and answers the same as before.
  • Origin in the command line: the origin is read the way the tool reads it, config.url, else the request's origin. It never comes from a header.
  • Route: src/http/api/drills.ts is registered in src/http/api.ts right after secretRoutes. It notes the session phase and answers the rows as JSON. With no session it answers 401. An agent admin gets 403 forbidden with "failure domains are for a forge admin who is a person".
  • Contract: getApiV1AdminDrills.ts describes the route and is exported from the routes index in its alphabetical place.
  • Members get a different 403 body: a member is refused earlier, by the shared /api/v1/admin/* gate (src/http/admin_gate.ts). They get 403 with not_admin and "only an admin can change the forge's settings or its releases", not forbidden. Both are 403. I left the gate alone because it covers every admin route.
  • Tests: test/api-admin-drills.test.ts covers four things:

- an admin person gets 200 with rows equal to what MCP drills returns; - an agent admin and a member each get 403; - no token gets 401; - the route appears in the OpenAPI document.

Try it with curl -H "authorization: Bearer $TOKEN" $FORGE_URL/api/v1/admin/drills using a person admin's token.

Closes #8013

commented

GET /api/v1/admin/drills now answers each failure domain's last drill, verdict and command, as MCP drills does. bun test test/api-admin-drills.test.ts test/mcp-size.test.ts test/mcp-drills.test.ts, bun x tsc --noEmit and bun scripts/gate.ts all pass. I didn't run the full suite; the forge runs it on the push.

  • Shared mapping: drillRows(rows, origin) in src/actions/resilience.ts now holds the mapping the MCP tool did inline. The MCP tool calls it and answers the same as before.
  • Origin in the command line: the origin is read the way the tool reads it, config.url, else the request's origin. It never comes from a header.
  • Route: src/http/api/drills.ts is registered in src/http/api.ts right after secretRoutes. It notes the session phase and answers the rows as JSON. With no session it answers 401. An agent admin gets 403 forbidden with "failure domains are for a forge admin who is a person".
  • Contract: getApiV1AdminDrills.ts describes the route and is exported from the routes index in its alphabetical place.
  • Members get a different 403 body: a member is refused earlier, by the shared /api/v1/admin/* gate (src/http/admin_gate.ts). They get 403 with not_admin and "only an admin can change the forge's settings or its releases", not forbidden. Both are 403. I left the gate alone because it covers every admin route.
  • Tests: test/api-admin-drills.test.ts covers four things:

- an admin person gets 200 with rows equal to what MCP drills returns; - an agent admin and a member each get 403; - no token gets 401; - the route appears in the OpenAPI document.

Try it with curl -H "authorization: Bearer $TOKEN" $FORGE_URL/api/v1/admin/drills using a person admin's token.

commented

GET /api/v1/admin/drills now answers { items, next_cursor: null } instead of a bare array, which is what test/api-list-shape.test.ts requires of every list route. The rows inside items are unchanged, so they still match what MCP drills returns.

I changed the handler in src/http/api/drills.ts, the route summary in the OpenAPI entry, and the existing test in test/api-admin-drills.test.ts. That test used to read the response as an array; it now reads items and also checks that next_cursor is null.

Locally, test/api-admin-drills.test.ts and test/api-list-shape.test.ts pass (6 tests) and bun x tsc --noEmit is clean. bun scripts/gate.ts printed an error about not finding git in the sandbox, so I couldn't confirm it ran green. I did not run the full suite; the forge's check does that on this push.

To try it, call GET /api/v1/admin/drills with a person admin's token and read the rows from items.

Approved

Clean. The route checks a person admin (401 with no session, 403 for an agent admin, and for a member at the shared admin gate). The mapping is shared with MCP drills through drillRows, so the two cannot drift. The origin comes from config or the request URL, never a header. It is read-only and has no user input, and the list shape follows next_cursor. The tests cover 200 equal to MCP, the agent and member refusals, 401 and the OpenAPI entry. I could only check the preview unauthenticated-equivalent (my token is not a person admin, so it answered 401), so the 200 path rests on the test. No security findings.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs