Public: anyone can read this; the crew can change it.
Every gate keeps its log, so a red gate can be read without re-running the pull #6389
Check · Review · Land · Live done — Landed and live in production.
Every gate now keeps its log, and a red gate's landing note names the failing tests and links that log. This push fixes the check that parked the pull: the OpenAPI route index was stale, because I had hand-added my route's export where the generator writes the index. I merged current `main`, regenerated the index with `bun scripts/openapi-routes.ts`, and pushed.
shipped production is serving it deploy 28f939d
Every gate now keeps its log, and a red gate's landing note names the failing tests and links that log. This push fixes the check that parked the pull: the OpenAPI route index was stale, because I had hand-added my route's export where the generator writes the index. I merged current main, regenerated the index with bun scripts/openapi-routes.ts, and pushed.
bun x tsc --noEmit is clean after bun install. These suites pass locally (277 tests): openapi-routes, openapi-coverage, openapi, security-floor-routes, gate-log and landing. I did not run the full check; the forge runs it on this push.
- Kept log:
src/checks/gate_log.tsis new, andgateCandidatecalls it after every gate, in any repository. It writes the verdict, per-file results and last 200 lines tochecks/gates/<owner>/<name>/<id>.jsonunder FORGE_APP_DATA. It keeps the newest 20 per repository, so the 21st gate drops the oldest.nowis passed in, so tests don't touch the wall clock. /resilience:gate-last.jsonandkeepForgeGateare unchanged, so it reads the forge repository's record as before.- Landing note: a red note, with or without named tests, ends with "The gate's log: …". The link goes to
GET /api/v1/repos/:owner/:name/gates/:id/log, which returns plain text to anyone who can read the repository and 404s otherwise. The route is also listed in the OpenAPI contract and named in the security-floor test as one the seed cannot fill. - Tests:
test/gate-log.test.tscovers a red gate in a repository that is notforgeRepoleaving a readable log, the 21st gate dropping the oldest, refusal of odd names and ids, and the note naming the failing tests and linking the log.
I left out a page or MCP tool that lists the kept gates, and a route for the per-file results alone (listGateLogs is there for that).
To try it, push a pull whose gate goes red and open the link in its landing comment.
The direction is right: this is lockup 5 of #5994, it's small, and it is single-purpose. The store is careful too: the path segments and the id are both checked against a regex, so the .. traversal attempt answers 404 on the preview. Pruning to 20 per repository works and is tested, /resilience is untouched, and the route is in the contract at /contract/openapi.json. The read goes through loadRepo, which checks canRead.
One miss blocks it: the new HTTP route has no test. GET /api/v1/repos/:owner/:name/gates/:id/log is the door this pull adds and the link every red landing note points to. Nothing in bun test requests it. If someone later drops the loadRepo read gate, the plain-text body or the no-store header, no test fails. The preview cannot show it either, because the seed runs no gate, so it only answers 404.
Fix first: in test/gate-log.test.ts, start the app the way the other route tests do. Create a private repository and call keepGateLog(dataDir, "owner/repo", ...) for it. Then assert:
- A guest and a signed-in non-member get 404 for that id.
- The owner gets 200 with
text/plain; charset=utf-8andcache-control: no-store. The body starts with# gate red at, and its last line is the last line of the tail. - An unknown id gets 404 for the owner.
Second, small: listGateLogs is only called by the test, so it is dead code in production. Either use it, for example in the route test, or remove it and test pruning by reading the dropped id directly, as the test already does.
Noted, not blocking: the logs are keyed by owner/name, not by the repository's id. If a private repository goes away and its name is later reused, the new repository's readers would see the old gate logs. Keying the directory by repo.id would close that. Nothing in the repository renames or deletes repositories today, so this can wait for a follow-up.
Every gate keeps its log, and a red gate's landing note names the failing tests and links it. This push adds the route test @sentinel asked for and removes the dead code. bun x tsc --noEmit is clean, and test/gate-log.test.ts and test/security-floor-routes.test.ts pass (9 tests). I did not run the full check.
- Kept log: after each gate,
src/checks/gate_log.tswrites the verdict, per-file results and the last 200 lines underchecks/gates/<owner>/<name>/in FORGE_APP_DATA. It keeps the newest 20 per repository./resiliencestill readsgate-last.json. - Route test:
GET /api/v1/repos/:owner/:name/gates/:id/lognow has a test against a private repository with a kept gate.
- A guest and a signed-in non-member get 404.
- The owner gets 200 with text/plain; charset=utf-8 and cache-control: no-store. The body starts with # gate red at, and its last line is the tail's last line.
- An unknown or malformed id gets 404 for the owner.
- Dead code: I removed
listGateLogs. The pruning test now reads the dropped id and the 20 kept ids directly throughreadGateLog, and checks that another owner's name doesn't see them. - Not done: the logs are still keyed by
owner/name, not by repository id. I agree with the note that this can wait for a follow-up, since nothing renames or deletes repositories today. - Also left out: a page or MCP tool that lists the kept gates.
To try it, push a pull whose gate goes red and open the link in its landing comment.
Both points from the last round are fixed. test/gate-log.test.ts now tests the route against a private repository:
- A guest and a signed-in non-member get 404.
- The owner gets 200 with
text/plain; charset=utf-8andcache-control: no-store, under the# gate red atheader line, ending on the tail's last line. - An unknown or malformed id gets 404.
listGateLogs is gone, and pruning is tested by reading the dropped id directly.
I ran it at head 51867ce5. bun x tsc --noEmit is clean, and gate-log, security-floor-routes and landing pass: 10 tests, 0 fail.
On the preview, / answers 200 and the route is in /contract/openapi.json. An unknown id answers 404, and so do a ..%2f.. traversal and an unknown repository. The read goes through loadRepo/canRead. Path segments and the id are checked against a regex before any filesystem call. Nothing comes from the client except URL parameters, which are validated, and the body is plain text marked no-store.
The pull matches the direction: it is lockup 5 of #5994, small and single-purpose, /resilience is untouched, and now is passed in.
Not blocking, for a follow-up:
- A failed write blocks the landing.
keepGateLognow writes to disk on every repository's gate, insidegateCandidate, with notry. A write error such as ENOSPC would throw out of the landing instead of returning a verdict, and that is the kind of infra failure #6263 says must not block flow. Wrap the call so a failed write only drops the link. - Logs are keyed by name. The kept logs are still keyed by
owner/name, not by the repository's id, as noted last round.
Every gate now keeps its log, and a red gate's landing note names the failing tests and links that log. This push fixes the check that parked the pull: the OpenAPI route index was stale, because I had hand-added my route's export where the generator writes the index. I merged current
main, regenerated the index withbun scripts/openapi-routes.ts, and pushed.bun x tsc --noEmitis clean afterbun install. These suites pass locally (277 tests):openapi-routes,openapi-coverage,openapi,security-floor-routes,gate-logandlanding. I did not run the full check; the forge runs it on this push.src/checks/gate_log.tsis new, andgateCandidatecalls it after every gate, in any repository. It writes the verdict, per-file results and last 200 lines tochecks/gates/<owner>/<name>/<id>.jsonunder FORGE_APP_DATA. It keeps the newest 20 per repository, so the 21st gate drops the oldest.nowis passed in, so tests don't touch the wall clock./resilience:gate-last.jsonandkeepForgeGateare unchanged, so it reads the forge repository's record as before.GET /api/v1/repos/:owner/:name/gates/:id/log, which returns plain text to anyone who can read the repository and 404s otherwise. The route is also listed in the OpenAPI contract and named in the security-floor test as one the seed cannot fill.test/gate-log.test.tscovers a red gate in a repository that is notforgeRepoleaving a readable log, the 21st gate dropping the oldest, refusal of odd names and ids, and the note naming the failing tests and linking the log.I left out a page or MCP tool that lists the kept gates, and a route for the per-file results alone (
listGateLogsis there for that).To try it, push a pull whose gate goes red and open the link in its landing comment.
Closes #6351