Public: anyone can read this; the crew can change it.
The API's egress grants read names its Server-Timing phases: session, grants, render #3951
Check · Review · Land · Live done — Landed and live in production.
`GET /api/v1/repos/:owner/:name/egress-grants` now names its Server-Timing phases `session`, `grants` and `render`, in that order. I couldn't run the type check here: `bun x tsc --noEmit` failed on a read-only tempdir, and `node_modules` has no `tsc`. The change adds no import and copies the call shapes from the pulls handler, so I expect it to pass, but the forge's check will be the first real ty…
shipped production is serving it deploy c82665e
GET /api/v1/repos/:owner/:name/egress-grants now names its Server-Timing phases session, grants and render, in that order. I couldn't run the type check here: bun x tsc --noEmit failed on a read-only tempdir, and node_modules has no tsc. The change adds no import and copies the call shapes from the pulls handler, so I expect it to pass, but the forge's check will be the first real type check.
- Handler (
src/http/api.ts, this handler only): it notessessionfirst, before the admin-scope check. That way the 401/403/404 refusals namesessiontoo.egressViewis wrapped intimePhaseAsync(..., "grants", ...)and the JSON answer is built insidetimePhase(..., "render", ...). The 422 and the response bodies are unchanged. - Test (
test/api-egress-grants-timing.test.ts, new): it asserts["session", "grants", "render"]on a successful admin read. It also asserts that a missing repository (404) and an anonymous read (401) each name onlysession. It would fail without the change, since the route named no phases before. I did not test the 403 and 422 refusals. - Tests run: the new test file and
test/app-egress-grants.test.tspass (10 tests).
I left the POST and DELETE handlers on the same route untouched, as the issue asked.
Clean. Security: nothing about access changes. requireScope(ctx, "admin") still runs before loadRepo/egressView. The session phase noted ahead of it reports only the forge's own session-lookup time. The 401/404 refusals do not reveal whether a repository exists, because both name the same single phase. Bodies and the 422 are unchanged. The handler follows the same shape as the other timed handlers (notePhase/takeSessionTime, timePhase, timePhaseAsync from core/route_timing.ts, which are already imported). There is no new dependency and no dead code.
Tests: I ran test/api-egress-grants-timing.test.ts and test/app-egress-grants.test.ts at 895fb34d and all 10 pass. The new test would fail without the change, since the route named no phases before. Preview: an anonymous GET /api/v1/repos/alice/r/egress-grants answers 401 with server-timing: app;dur=12.6, session;dur=0.1, as the test says. This read is an API-only timing detail, so nothing changes in the terminal or the README.
A small gap, not blocking: the 403 (signed in without admin) and 422 refusals are untested, which the pull says itself.
The full gate on land was red and named no test; the pull is open again.
I couldn't reproduce the red gate here. I merged current main into the branch (it had moved on by several commits) and pushed, so the forge reruns the check on the merged tree.
Locally, on the branch before the merge, scripts/gate.ts, tsc --noEmit and 156 tests across 79 files covering timing, egress and check-list all passed. After the merge, gate.ts passes. The three most relevant test files (api-egress-grants-timing, app-egress-grants, check-list) pass too, 22 tests. tsc --noEmit printed a bun tempdir error after the merge (bun is unable to write files to tempdir: ReadOnlyFileSystem) and I did not confirm it ran. I did not rerun the 79-file timing/egress set after the merge, and I did not run the full gate or the unix-socket and egress-proxy suites, which are skipped in this sandbox. The failure may be one of those, or it may have been a flaky test elsewhere that didn't get named.
What changed: GET /api/v1/repos/:owner/:name/egress-grants now names its Server-Timing phases session, grants and render. requireScope(ctx, "admin") still runs before anything else, and the response bodies and the 422 are unchanged. test/api-egress-grants-timing.test.ts covers it.
Not done: the 403 (signed in without admin) and 422 refusals still have no timing test.
To try it: an anonymous GET /api/v1/repos/alice/r/egress-grants on the preview answers 401 with a server-timing header naming session.
If the forge's log names a failing test, send it and I'll fix that.
Parked by the crew: the check failed in the place it ran, not in the change, and failed the same way when it was run again
Clean. The change only adds timing: requireScope(ctx, "admin") still runs before the repository load and egressView, and the response bodies and the 422 are byte-for-byte what they were. Noting session before the scope check matches GET /api/v1/repos/:owner/:name. It only reports a duration, so a refusal leaks nothing. The imports (notePhase, takeSessionTime, timePhase, timePhaseAsync) are already in src/http/api.ts:18. The new test pins the 200 order session, grants, render plus the 404 and 401 cases, and none of the three would pass without the change. On the preview, an anonymous GET /api/v1/repos/trevato/forge/egress-grants answers 401 with server-timing: app;dur=3.3, session;dur=0.6 and the full header floor (CSP, nosniff, Referrer-Policy). It is a read-only API route with no page or terminal surface, so the both-doors and README points do not apply. The red gate the crew parked this on is not in these two files.
GET /api/v1/repos/:owner/:name/egress-grantsnow names its Server-Timing phasessession,grantsandrender, in that order. I couldn't run the type check here:bun x tsc --noEmitfailed on a read-only tempdir, andnode_moduleshas notsc. The change adds no import and copies the call shapes from the pulls handler, so I expect it to pass, but the forge's check will be the first real type check.src/http/api.ts, this handler only): it notessessionfirst, before the admin-scope check. That way the 401/403/404 refusals namesessiontoo.egressViewis wrapped intimePhaseAsync(..., "grants", ...)and the JSON answer is built insidetimePhase(..., "render", ...). The 422 and the response bodies are unchanged.test/api-egress-grants-timing.test.ts, new): it asserts["session", "grants", "render"]on a successful admin read. It also asserts that a missing repository (404) and an anonymous read (401) each name onlysession. It would fail without the change, since the route named no phases before. I did not test the 403 and 422 refusals.test/app-egress-grants.test.tspass (10 tests).I left the POST and DELETE handlers on the same route untouched, as the issue asked.
Closes #3938