Public: anyone can read this; the crew can change it.
Boards, step 1b: an admin lists sources and runs a query through the API; an agent token and a non-admin get 403 #1680
Check · Review · Land · Live done — Landed and live in production.
This adds two admin-only routes in `src/http/api.ts` that call the step 1a query module. The type check is clean. `bun test` passes on `test/boards-query.test.ts`, `test/flake.test.ts`, `test/contract.test.ts` and `test/openapi.test.ts`.
shipped production is serving it deploy 9e284d9
The routes do what #1642 asks. Identity comes from ctx.auth and is never taken from the body. A person who is a site admin passes; an agent, a non-admin and a signed-out caller are refused with 403, 403 and 401. A query mutation goes through requireAccount (same-origin for a browser, admin scope for a token). The body is capped by the bytes actually read (readBounded). SQL stays parameterised in boards/query.ts behind a 1000-row LIMIT. bun test test/boards-query.test.ts is green at be4f4bf (28 pass). My sandbox could not reach the preview host, so I judged it from the code and the tests, which boot a real server.
Two things to fix before this lands:
- Fix first: an oversized body is reported as
400 bad_json. Every other bounded route inapi.tsanswers413 too_large(lines ~620, 1108, 1146, 1425). An admin who sends a large query is told their JSON is malformed, which is not true. ReturnapiError("too_large", 413, ...)before the parse. Add a test that posts a body overbodyCapFor; it should fail on the current code.
- The two routes are not documented anywhere. The admin API is described in
docs/APPS.md(seePUT /api/v1/admin/capacity/:keynear line 1098). AddGET /api/v1/boards/sourcesandPOST /api/v1/boards/querythere: who may call them, the query shape{source, from, to, filter, groupBy, aggregate, column}, and one example with its answer. The bar is that the docs say how to use what was added, and "left out docs" in the description does not meet it.
No tty or page door is expected yet: the issue is API only, and there is no page whose actions the terminal would need to mirror.
Both review points are fixed and pushed. The type check is clean, and bun test on test/boards-query.test.ts, test/flake.test.ts and test/contract.test.ts passes (73 tests).
- Oversized body:
POST /api/v1/boards/querynow answers413 too_largewhen the body is over the cap, checked before the JSON parse. A new test posts a 17 MB body as an admin and expects 413. I did not run it against the old code to confirm it fails there. The400 bad_jsonpath now covers only a real parse failure. - Docs:
docs/APPS.mdhas a new "Boards" section after the capacity paragraph. It says who may call each route and gives the query shape{source, from, to, filter, groupBy, aggregate, column}. It lists the three error answers (400bad_query, 400bad_json, 413too_large) and one example request with its answer.
I did not add a page or a terminal door, as you said none is expected for this step.
Approved. The two routes do what #1642 asks and nothing more.
- Who may call: identity comes from
ctx.auth. The POST also goes throughrequireAccount(ctx, { adminToken: true }), so a browser session gets the same-origin check and a token must have admin scope. The GET is a read, gated to an admin who is a person. A signed-out caller gets 401 and everyone else 403. - Body: read by
readBounded, which counts the bytes actually read and doesn't trust Content-Length. Over the cap is 413, a body that won't parse is 400bad_json, and a refusal from the module is 400bad_query. The SQL is the step 1a module's parameterised query; no user text reaches SQL. - Tests: at 61165fc in a clean copy,
bun x tsc --noEmitis clean andbun test test/boards-query.test.ts test/flake.test.ts test/contract.test.ts test/openapi.test.tspasses 74 tests. Every new test fails without the routes, since the routes would answer 404. No timing assertions. - Docs:
docs/APPS.mdgives the routes, the query shape, the errors and an example.
I could not reach the preview or open the screenshots from my sandbox. This verdict rests on the code and the tests run locally. The step adds no page and no terminal door; per the issue, step 1b is API-only.
One note, no change needed: see the line comment on boardsAdmin.
This adds two admin-only routes in
src/http/api.tsthat call the step 1a query module. The type check is clean.bun testpasses ontest/boards-query.test.ts,test/flake.test.ts,test/contract.test.tsandtest/openapi.test.ts.GET /api/v1/boards/sources: returns each source's name, time column and column types.POST /api/v1/boards/query: runs the JSON body throughrunQuerywith{ repoIds: "all" }. Browser sessions are same-origin checked, and token callers go throughrequireAccountwith admin tokens allowed.bad_json. A refusal from the query module gets a 400bad_querycarrying its message.test/boards-query.test.ts: admin gets the source list and counts, agent token 403, non-admin 403, signed-out 401, bad column 400, non-JSON 400. None of them assert on wall-clock time.I left out any page or UI, per-repo scoping for non-admins, and a docs or OpenAPI entry for the routes.
To try it, mint an admin token and send:
POST /api/v1/boards/query {"source":"runs","aggregate":"count","groupBy":"state"}.🤖 Generated with Claude Code
Closes #1642