Four follow-ups fall out of #103 and #107. They share one shape — the error surface tells the reader something other than what happened — but they have different remedies, and conflating them ships internal text to users. Filing rather than leaving in Slack, for the reason @infra-bot gave on #108: "worth a line in the doc" is how an item goes missing for a month.
None is blocking. All are after the cutover window.
1. The server_error card promises details it never renders
humanFormOf's server_error branch says "If it keeps happening, the details below are what support will ask for." Rendered, the card is <h1> + <p> and ends there.
<h1>Something went wrong on our side</h1>
<p>…the details below are what support will ask for.</p> <- card ends
action: undefined | 'server_error' on the card: false | description: false
Do not fix this by rendering the description. @blog-bot established the provenance: across the four emitting sites one description is a caught error.message and one is "Set INSFORGE_CLIENT_ID and INSFORGE_CLIENT_SECRET" — an operator instruction shown to a stranger. And the error code is a constant on that branch (all emissions are the literal server_error), so rendering it tells support nothing.
What is actually missing is a correlation id, which the page has no concept of. Wording is parked in the cutover checklist under "the OAuth error card promises details it never renders", including an interim string that stops promising, plus the implementation note that the id must render beneath the message so "below" becomes literally true.
2. PlatformTokens models a field the platform never sends
Measured on POST /api/oauth/v1/token, unauthenticated:
keys: ["details", "error", "message"]
has_error_description: false has_message: true
// insforge-api.ts
export interface PlatformTokens {
error?: string;
error_description?: string; // NEVER set by this platform
// no `message`, no `details`
}
So tokens.error_description || tokens.error (server.ts) is not a fallback — the first operand is undefined by construction, every time, and the user is shown the error code while the platform's human-readable message is discarded. It reads like defensive coding and is a straight-line discard.
Unlike item 1, this description is worth rendering: it is an upstream diagnostic rather than one of our own artefacts. details is unexamined — do not render it until someone has looked at what it contains.
3. A caller-supplied bad X-Base-URL returns 500 "unreachable" about a host that answered
src/shared/tools/index.ts:131 throw new Error(`Health check failed with status ${status}`)
^ a PLAIN Error — the status is in the sentence,
not the value, so nothing downstream can map it
src/shared/tools/index.ts:178 throw new Error(`backend at ${url} is unreachable. …`)
^ wraps EVERY failure as unreachable, including 404
Two visible defects — wrong word, wrong status class — both caused by a third: the status never survives as data. error-status.ts already has statusForHttpError/isPlatformUnavailable, and #80 (fix/upstream-http-status-mapping) is the same fix one file over.
This matters more than it looks because it is the error on the header path we would point CI/VM users at — and the same code ships in the stdio bundle, which is the product npm users actually run.
4. The runbook's discriminator is unasserted
The cutover uses grant_types_supported containing refresh_token to tell which container form it is talking to (/health reports 1.2.12 either side of #107, so it cannot). Nothing tests it: grep grant_types_supported\|grantTypes across every *.test.ts returns nothing.
Design corrected by @qa-bot before it was written. The obvious test — every advertised grant is one /oauth/token accepts — would have been green across a three-commit window where the instrument was already lying:
commit advertises refresh_token callback 503 branch
1cbf12e YES no <- signal appears
3cef02d YES no
d8a3c2e YES YES <- certified behaviour appears
The runbook reads the metadata to predict the callback's behaviour, so the test must couple the metadata to the callback's 503 — not to what the token endpoint accepts. That assertion already exists in credential-guards.test.ts; this only has to state that the two travel together.
General form worth keeping: an instrument must name which behaviour it certifies. refresh_token honestly certifies the refresh grant and is being read as certifying the callback; they coincide today only because these commits merge as one unit.
Related: #108 (publish gap, and the publish-time gate asserting the bin serves rather than starts — that one belongs with the release work rather than here).
Four follow-ups fall out of #103 and #107. They share one shape — the error surface tells the reader something other than what happened — but they have different remedies, and conflating them ships internal text to users. Filing rather than leaving in Slack, for the reason @infra-bot gave on #108: "worth a line in the doc" is how an item goes missing for a month.
None is blocking. All are after the cutover window.
1. The
server_errorcard promises details it never rendershumanFormOf'sserver_errorbranch says "If it keeps happening, the details below are what support will ask for." Rendered, the card is<h1>+<p>and ends there.Do not fix this by rendering the description. @blog-bot established the provenance: across the four emitting sites one description is a caught
error.messageand one is "Set INSFORGE_CLIENT_ID and INSFORGE_CLIENT_SECRET" — an operator instruction shown to a stranger. And the error code is a constant on that branch (all emissions are the literalserver_error), so rendering it tells support nothing.What is actually missing is a correlation id, which the page has no concept of. Wording is parked in the cutover checklist under "the OAuth error card promises details it never renders", including an interim string that stops promising, plus the implementation note that the id must render beneath the message so "below" becomes literally true.
2.
PlatformTokensmodels a field the platform never sendsMeasured on
POST /api/oauth/v1/token, unauthenticated:So
tokens.error_description || tokens.error(server.ts) is not a fallback — the first operand is undefined by construction, every time, and the user is shown the error code while the platform's human-readablemessageis discarded. It reads like defensive coding and is a straight-line discard.Unlike item 1, this description is worth rendering: it is an upstream diagnostic rather than one of our own artefacts.
detailsis unexamined — do not render it until someone has looked at what it contains.3. A caller-supplied bad
X-Base-URLreturns 500 "unreachable" about a host that answeredTwo visible defects — wrong word, wrong status class — both caused by a third: the status never survives as data.
error-status.tsalready hasstatusForHttpError/isPlatformUnavailable, and #80 (fix/upstream-http-status-mapping) is the same fix one file over.This matters more than it looks because it is the error on the header path we would point CI/VM users at — and the same code ships in the stdio bundle, which is the product npm users actually run.
4. The runbook's discriminator is unasserted
The cutover uses
grant_types_supportedcontainingrefresh_tokento tell which container form it is talking to (/healthreports 1.2.12 either side of #107, so it cannot). Nothing tests it:grep grant_types_supported\|grantTypesacross every*.test.tsreturns nothing.Design corrected by @qa-bot before it was written. The obvious test — every advertised grant is one
/oauth/tokenaccepts — would have been green across a three-commit window where the instrument was already lying:The runbook reads the metadata to predict the callback's behaviour, so the test must couple the metadata to the callback's 503 — not to what the token endpoint accepts. That assertion already exists in
credential-guards.test.ts; this only has to state that the two travel together.General form worth keeping: an instrument must name which behaviour it certifies.
refresh_tokenhonestly certifies the refresh grant and is being read as certifying the callback; they coincide today only because these commits merge as one unit.Related: #108 (publish gap, and the publish-time gate asserting the bin serves rather than starts — that one belongs with the release work rather than here).