Story: Report a refused request as a refusal
Table of Contents
This page documents a story in Sprint 27. It captures the goal, current status, acceptance criteria, and the tasks that compose it.
1. Goal
Make every client report a refusal as a refusal, naming the reason the server gave, so a permission problem is never mistaken for a malformed payload.
2. Status
| Field | Value |
|---|---|
| State | DONE |
| Parent sprint | Sprint 27 |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-10-07 |
3. Acceptance
- A request refused with X-Error: forbidden prints a message naming the refusal and the subject, and never mentions unpacking or decoding
- X-Error: unauthorized and bad_request are surfaced the same way, each with its own literal message
- X-Error: token_expired keeps its current re-authentication behaviour
- Tests pin each header value against a literal expected message
4. Tasks
| Task | State | Start | End | Description |
|---|---|---|---|---|
| Scaffold story: Report a refused request as a refusal | DONE | 2026-10-07 | 2026-10-07 | Story scaffolding rides this task: documents, sprint wiring, and the scaffold PR. Close it before merging that PR. |
| Surface a refusal instead of a decode failure | DONE | 2026-10-07 | Initial task for: Report a refused request as a refusal |
5. Decisions
6. Out of scope
7. Result
A refusal now reaches the caller as a refusal. The check lives in the NATS
decode helpers, so a caller that reads the X-Error header itself keeps
the reply it inspects, and only a caller that would decode the empty body
is stopped.