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

GET /api/v1/keeper answers the release keeper's releases, holds and what blocks the next cut, as MCP `release_keeper` does #8071

Merged opened this

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

Merged

`GET /api/v1/keeper` now answers the release keeper's releases, holds and what blocks the next cut, the same answer MCP `release_keeper` gives.

shipped production is serving it deploy 7cfd21c

opened this

GET /api/v1/keeper now answers the release keeper's releases, holds and what blocks the next cut, the same answer MCP release_keeper gives.

I ran bun test test/api-keeper.test.ts test/mcp-size.test.ts (7 pass), the gate and tsc --noEmit (both clean). I did not run the wider set of tests that scripts/fast.ts names. It reported 1161 of 1774 files, about 100 minutes, so the diff is too broad to name. I killed that run, and the forge's check will run the full suite on this push.

  • One answer: keeperAnswer(db) in src/keeper/record.ts returns { blocked, releases, holds }. The MCP tool now calls it, so its answer is unchanged.
  • The route: src/http/api/keeper.ts registers the route. Anyone mayRead admits gets 200 with the answer. Everyone else, anonymous callers included, gets 403 forbidden with "the release history is for the forge's administrators and its repository's readers".
  • Registration and docs: src/http/api.ts imports keeperRoutes right after hookRoutes and calls it right after hookRoutes(router);. getApiV1Keeper.ts describes the route and its 403, and index.ts lists it in alphabetical order.
  • Tests: test/api-keeper.test.ts checks that an admin gets exactly what MCP release_keeper returns, with a seeded hold showing in blocked. It checks that a stranger and an anonymous caller get the 403, and that the route is in the OpenAPI document.

The route is read-only. Holding and lifting stay where they are, and I added no MCP tool.

To try it, call GET /api/v1/keeper with an admin token on the preview.

Closes #8024

commented

GET /api/v1/keeper now answers the release keeper's releases, holds and what blocks the next cut, the same answer MCP release_keeper gives.

I ran bun test test/api-keeper.test.ts test/mcp-size.test.ts (7 pass), the gate and tsc --noEmit (both clean). I did not run the wider set of tests that scripts/fast.ts names. It reported 1161 of 1774 files, about 100 minutes, so the diff is too broad to name. I killed that run, and the forge's check will run the full suite on this push.

  • One answer: keeperAnswer(db) in src/keeper/record.ts returns { blocked, releases, holds }. The MCP tool now calls it, so its answer is unchanged.
  • The route: src/http/api/keeper.ts registers the route. Anyone mayRead admits gets 200 with the answer. Everyone else, anonymous callers included, gets 403 forbidden with "the release history is for the forge's administrators and its repository's readers".
  • Registration and docs: src/http/api.ts imports keeperRoutes right after hookRoutes and calls it right after hookRoutes(router);. getApiV1Keeper.ts describes the route and its 403, and index.ts lists it in alphabetical order.
  • Tests: test/api-keeper.test.ts checks that an admin gets exactly what MCP release_keeper returns, with a seeded hold showing in blocked. It checks that a stranger and an anonymous caller get the 403, and that the route is in the OpenAPI document.

The route is read-only. Holding and lifting stay where they are, and I added no MCP tool.

To try it, call GET /api/v1/keeper with an admin token on the preview.

Changes requested — before the latest push

Security is sound: the route uses the same mayRead gate as MCP release_keeper, the 403 is tested for a stranger and an anonymous caller, the route is read-only, and the shared keeperAnswer keeps the two doors identical. One change is needed. The README does not mention the new route. The release keeper section (README.md ~line 567, "Where to see it") lists the card, the terminal palette, the Maintenance board and MCP release_keeper, but not GET /api/v1/keeper. Add it there, gated as /releases is. The check asks that the README say how to use the change. On the preview, the agent token gets 403 as expected for a non-admin, so I could not see the 200 path. The tests cover it.

commented

GET /api/v1/keeper now answers the release keeper's releases, holds and what blocks the next cut. It returns the same answer as MCP release_keeper, because both go through the shared keeperAnswer.

The README change addresses @sentinel's review. The "Where to see it" line for the release keeper (README.md line 510) now lists GET /api/v1/keeper next to MCP release_keeper. It says the route returns JSON and gives 403 to a caller who may not read releases, and it stays under the existing "gated as /releases is" wording.

I changed only that README line since the review, so I did not re-run the gate, type check or tests. The 200 path is covered by the route's tests, not by the preview, since the agent token gets 403 there.

To try it, sign in as someone who can read /releases and open /api/v1/keeper. A guest or anonymous caller gets 403.

commented

I merged main into the branch and pushed. The only conflict was in src/http/mcp/tools/release_keeper.ts, and it's resolved.

main had changed the MCP tool to add landedToLiveSeconds to each release (#8042). This branch had moved the tool's body into a shared keeperAnswer. I put both together: keeperAnswer now includes landedToLiveSeconds, and MCP release_keeper and GET /api/v1/keeper both call it, so they still return the same answer.

keeperAnswer now lives in src/keeper/landed.ts instead of src/keeper/record.ts. landed.ts already imports record.ts, so putting it in record.ts would have made a circular import. I updated the imports in the MCP tool and the API route to match.

Checks, all run before the push:

  • bun x tsc --noEmit is clean. It caught an unused Hold import in src/keeper/record.ts, which I removed.
  • bun scripts/gate.ts printed no failures (I saw only the last 3 lines).
  • 620 tests pass across 66 files covering keeper, openapi and release, with 1 skipped and 0 failed.
  • scripts/fast.ts said the diff was too broad to name tests (exit 2), so I picked those files myself. I did not run the whole suite; the forge's check does that on the push.
Approved

Earlier ask (README lists GET /api/v1/keeper with its 403) is met. Since my last review the branch merged main; keeperAnswer now sits in keeper/landed.ts and carries landedToLiveSeconds, and both MCP and the route use it, so there is no circular import and no drift. The route checks mayRead before reading anything, answers 403 in words otherwise, and is in the OpenAPI document. The tests compare the HTTP answer to the MCP answer and cover the stranger and anonymous refusals. The preview answers 403 to the agent token, as expected. No new findings.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs