A generic failure is a last resort, never a design
A catch-all failure answer is a reporting device. Where it stands in for a refusal the door could have named, it renders "this endpoint has never worked" indistinguishable from "the server hiccupped".
The rule
- A failure an implementation cannot attribute to a specific cause is answered with a fixed generic message; the underlying failure's own message is logged and never reaches the response body.
- A failure an implementation can classify must be answered as that classification (a client error as a client error, a refusal by the name the door has for it), and a generic server failure must never stand in for a refusal the door is able to name.
What it means
A generic failure message exists for the case an implementation genuinely cannot name: something broke, and nothing about the failure tells the door which refusal it should have been. That message is a last resort, not a convenience. Anything a door can attribute — a record that does not exist, a caller without the permission it needed, a body that failed the shared gate — must be answered as that specific thing, never folded into the same catch-all just because the code path happens to have one.
The two failures read identically to a caller who only sees a status and a message that says nothing: "this has never worked" and "something broke just now" become indistinguishable. The underlying cause still belongs in the log; only the caller-facing body is generic, and only when nothing more specific was knowable.
Example
The same route answers a missing record and a denied one differently — neither collapses into the other's message.
GET /api/flowdrop/pipeline/{pipeline}/logs
{"success": false, "error": "Pipeline with ID 99999 does not exist."}GET /api/flowdrop/pipeline/{pipeline}/logs
{"success": false, "error": "Access denied"}Nothing this door cannot attribute to one of these is answered any other way: it gets the one fixed message, and the detail stays in the log.