496303471d
---ci--- project: atelier phase: 7 milestone: v0.1 status: complete phase_role: final milestone_complete: true requirements: covered: [ATELIER-01, ATELIER-02, ATELIER-03, ATELIER-04, ATELIER-05, ATELIER-06, ATELIER-07, ATELIER-08, ATELIER-09, ATELIER-10, ATELIER-11, ATELIER-12, ATELIER-13, ATELIER-14, ATELIER-15, ATELIER-16, ATELIER-17, ATELIER-18, ATELIER-19, ATELIER-20, ATELIER-21, ATELIER-22, ATELIER-23, ATELIER-24, ATELIER-25, ATELIER-26, ATELIER-27, ATELIER-28, ATELIER-29, ATELIER-30, ATELIER-31, ATELIER-32, ATELIER-33, ATELIER-34, ATELIER-35] partial: [] ship: milestone: v0.1 type: NFR tag: v0.0.7 merge: milestone/v0.1-atelier -> main release: https://git.cloudinit.dev/cloudinit-bot/atelier/releases/tag/v0.0.7 ---/ci--- Milestone v0.1 — Initial Framework (NFR, complete). 8 core principles (C1-C8), 11 domains, 110 domain principles, 27 derived docs, 4 good + 3 bad examples, 4 language docs, full matrix, 3 review docs. All 35 requirements covered. 7 patches (v0.0.0 pre-execution through v0.0.7 final). v0.0.7 IS the v0.1.0 milestone release.
2.6 KiB
2.6 KiB
Error Responses — Derived Rules
Derives from
domains/api/first-principles.mdP9 (Error Transparency) anddomains/errors/first-principles.md.
The Error Contract
Every error response is a JSON object with:
{
"error": {
"code": "STRING_ERROR_CODE",
"message": "Human-readable description",
"details": {},
"request_id": "uuid"
}
}
code: machine-consumable, stable, UPPER_SNAKE_CASE. Never a free-text message.message: human-readable, for logs and developers. Not for end users (seedomains/errors/P8).details: structured, typed additional context (which field, what value, what constraint).request_id: correlation ID for tracing. Every error is traceable.
Error Codes (P3 Predictability, P9)
- Codes are stable. Renaming an error code is a breaking change.
- Codes are specific:
VALIDATION_FAILEDnotBAD_REQUEST.DUPLICATE_EMAILnotCONFLICT. - Codes are namespaced:
USER_NOT_FOUND,ORDER_NOT_FOUND— not justNOT_FOUND.
Status Code Mapping (P1 Contract Fidelity)
| Code | Meaning | Error code example |
|---|---|---|
| 400 | Malformed request | MALFORMED_REQUEST |
| 401 | Auth required | AUTH_REQUIRED |
| 403 | Forbidden | FORBIDDEN |
| 404 | Not found | <RESOURCE>_NOT_FOUND |
| 409 | Conflict | DUPLICATE_<RESOURCE> |
| 422 | Semantic invalid | VALIDATION_FAILED |
| 429 | Rate limited | RATE_LIMITED |
| 500 | Server bug | INTERNAL_ERROR |
- Never return 200 with an error body. The status code is the first signal.
- Never return 500 for a client error. 500 means "the server has a bug."
Information Disclosure (P8 Security, domains/security P9)
- Error messages do not leak internal state: no stack traces, no SQL fragments, no file paths.
- A 401 does not say "user not found" vs "wrong password" — both say "invalid credentials."
- A 404 does not confirm the resource exists but is forbidden — return 404, not 403, for unauthenticated requests to hidden resources.
- Detailed errors are logged server-side with
request_id; the client gets the safe version.
Retryability (P6 Idempotency)
- Errors that are safe to retry: 409, 422 (if the fix is applied), 429 (after backoff), 5xx.
- Errors that are not safe to retry: 400, 401 (without re-auth), 403.
- The error body indicates retryability:
retryable: true/falseor via the code's known semantics.
Partial Errors (GraphQL, see domains/api/graphql.md)
- GraphQL returns data and errors together. Do not conflate.
- A null field with no error is a bug. A null field with an error is a partial failure.