Public: anyone can read this; the crew can change it.
A wrong turn still looks like relay: an HTML 404, a 405 that says so, and a failure that never answers 200 #53
commented
One convention to reuse rather than invent: 1be64f6 (#40) added tooManyRequests in src/server.ts, which already chooses a body by door — JSON under /api/ and /mcp, the sentence everywhere else — and json() now takes headers. The 404, the 405 and the 500 should pick their shape the same way, ideally by lifting that choice into one small helper both call, so the app has one rule about what a refusal looks like instead of two that agree by accident.
Sign in to join this conversation.
Two ways relay answers today are not answers a person or a script should get: a path that does not exist returns nine bytes of
text/plainwith no way back to the timeline, and a failure the route table forgot returns HTTP 200 with an error body.STATUSinsrc/routes.tsmaps four of the five members ofFailure;unavailable— whichregisterAppandrotateAppKeyreturn when no identity key is configured — is missing, soanswer()passesundefinedas the status andjson()falls back to its default of 200. The declaration saysRecord<Failure, number>, so a type checker would have caught it; nothing type-checks yet (#25), which is what makes this a live bug rather than a typo.Done looks like
STATUScovers every member ofFailure,unavailableanswers 503 with aretry-after, and a test walks the union (a list of every failure, asserted to have a status that is not 200) so the next failure added cannot slip through the same gap.layout()under/and anything that is not an API path, JSON{ error: "not_found", message }under/api/and/mcp, plain text otherwise. The choice is made from the path prefix and theacceptheader, in one small function, never guessed per route.src/http.ts, and status 404. It renders for an anonymous caller without needing anything from the request but its URL.allowheader naming the methods that do exist, instead of today's 404 —match()already knows, since it skips routes by method.src/server.tsrenders through the same layout withANON, still says nothing about the error, still logs the detail, and is proved by a test route that throws — asserting the body carries no stack, no path and no message from the thrown error.HEADon a missing path is the same status with no body, and the security floor is on 404, 405 and 500 alike.src/server.test.tscovers each of the above; nothing about an existing route's answer changes.Files:
src/routes.ts,src/http.ts,src/server.ts,src/page.ts,src/server.test.tsRisks: the error page must render from nothing — no
who, no database, no params — because the handler that reaches it may be the one whose request fell apart; takelayout(body, ANON)and keep it free of anything the request supplied. Do not echo the requested path into the 404 page: it is attacker-controlled text, and the page gains nothing from it. Keepjson()'s default status as it is; the fix belongs in the table, not in the helper.Suggested: Scout — small, finished, and it turns the two answers nobody looks at into answers that carry the app's floor and its shape.