Skip to main content

cratefield_core/
problems.rs

1//! The registry of every problem slug the harness can emit (architecture
2//! section 6): stable `type` URIs, statuses and descriptions. Modules add
3//! their own slugs as `const ProblemDef`s in the same shape, and every
4//! slug — core's and modules' — must be unique workspace-wide:
5//! `errors-doc` collects the definitions from source and fails CI on a
6//! slug defined twice (issue #419).
7
8use axum::http::StatusCode;
9
10/// Definition of one problem slug.
11#[derive(Debug, Clone, Copy)]
12pub struct ProblemDef {
13    /// Last path segment of the `type` URI.
14    pub slug: &'static str,
15    pub status: StatusCode,
16    /// Short, stable human title (RFC 9457 `title`).
17    pub title: &'static str,
18    /// One-line description for reviewers; not part of the response.
19    pub description: &'static str,
20}
21
22/// Every slug core emits. Keep sorted by status.
23pub struct Slugs {
24    /// 400: a request body/query did not deserialize or failed validation.
25    pub validation_failed: ProblemDef,
26    /// 400: a captcha token was missing or rejected.
27    pub captcha_failed: ProblemDef,
28    /// 400: a signed link/token is malformed, tampered or expired.
29    pub invalid_token: ProblemDef,
30    /// 400: the request named a resource outside the configured set
31    /// (e.g. an unknown waitlist product).
32    pub unknown_product: ProblemDef,
33    /// 401: a credential was presented and did not verify, or none was
34    /// presented where one is required. The one 401 for every
35    /// unauthenticated caller — the auth-client bearer check, the tables
36    /// API's access decision and the modules reusing them all answer
37    /// with it.
38    pub unauthenticated: ProblemDef,
39    /// 401: admin endpoints are disabled (`ADMIN_TOKEN` unset) or the
40    /// request carried no bearer token.
41    pub admin_unauthorized: ProblemDef,
42    /// 401: a developer API key was missing, malformed, unknown, revoked
43    /// or failed its hash check — one uniform answer for all five, so a
44    /// probe cannot tell which (issue #532).
45    pub api_key_unauthorized: ProblemDef,
46    /// 403: the presented API key is valid but lacks the scope the route
47    /// requires (issue #532).
48    pub api_key_forbidden: ProblemDef,
49    /// 403: the presented admin token is wrong.
50    pub admin_forbidden: ProblemDef,
51    /// 401: a request reached a sidecar-guarded route (`/v1/*` or
52    /// `/__surface` on a sidecar that requires it) without a valid gateway
53    /// token from the trusted host (issue #131).
54    pub sidecar_unauthorized: ProblemDef,
55    /// 503: the deployment declares production but cannot satisfy the
56    /// abuse controls its own routes declare, so the guarded routes are
57    /// refused rather than served unprotected (issue #143).
58    pub not_production_ready: ProblemDef,
59    /// 413: the request body exceeded the 64 KiB `/v1/*` limit.
60    pub request_too_large: ProblemDef,
61    /// 429: the rate limit for this IP or address was exceeded.
62    pub rate_limited: ProblemDef,
63    /// 429: a metered allowance is spent for the current period (issue
64    /// #588). A quota, not a throttle: the subject must wait for the
65    /// period to roll over, which `Retry-After` names.
66    pub allowance_exhausted: ProblemDef,
67    /// 404: no route matched.
68    pub not_found: ProblemDef,
69    /// 500: unhandled error; body carries no internals.
70    pub internal: ProblemDef,
71    /// 503: the mailer is not configured (no API key / unverified sending
72    /// domain); forms should degrade to a direct address.
73    pub mail_not_configured: ProblemDef,
74    /// 503: readiness probe failed (`/__ready`): database missing, slow or
75    /// erroring.
76    pub not_ready: ProblemDef,
77    /// 404: the request's host resolves to no tenant in the registry. A
78    /// 404 and not a 403: which hosts are tenants is not a secret worth
79    /// a distinguishable answer, and "no such tenant" is the truth
80    /// (RECONCILIATION.md §6, TENANT-ROUTING.md §3).
81    pub unknown_tenant: ProblemDef,
82    /// 503: a sidecar-mounted module could not be reached (ADR 0009). Only
83    /// that prefix fails; every in-process module keeps serving.
84    pub sidecar_unavailable: ProblemDef,
85    /// 503: a sidecar answered with a different `HARNESS_API` than this
86    /// harness speaks, so its responses cannot be trusted.
87    pub sidecar_contract_mismatch: ProblemDef,
88    /// 503: the request's tenant is in the registry but `degraded` — its
89    /// schema is behind or its database is unreachable, so serving it
90    /// would read a shape the code no longer expects
91    /// (RECONCILIATION.md §6, TENANT-ROUTING.md §3).
92    pub tenant_degraded: ProblemDef,
93}
94
95pub const SLUGS: Slugs = Slugs {
96    validation_failed: ProblemDef {
97        slug: "validation-failed",
98        status: StatusCode::BAD_REQUEST,
99        title: "Request validation failed",
100        description: "The request body or query did not deserialize into a valid request.",
101    },
102    captcha_failed: ProblemDef {
103        slug: "captcha-failed",
104        status: StatusCode::BAD_REQUEST,
105        title: "Captcha verification failed",
106        description: "The captcha token was missing or rejected; retry the challenge.",
107    },
108    invalid_token: ProblemDef {
109        slug: "invalid-token",
110        status: StatusCode::BAD_REQUEST,
111        title: "Invalid or expired token",
112        description: "A signed link or token is malformed, tampered with, or expired.",
113    },
114    unknown_product: ProblemDef {
115        slug: "unknown-product",
116        status: StatusCode::BAD_REQUEST,
117        title: "Unknown product",
118        description: "The named product is not on this waitlist.",
119    },
120    unauthenticated: ProblemDef {
121        slug: "unauthenticated",
122        status: StatusCode::UNAUTHORIZED,
123        title: "Not authenticated",
124        description: "A credential was presented and did not verify, or none was presented where one is required.",
125    },
126    admin_unauthorized: ProblemDef {
127        slug: "admin-unauthorized",
128        status: StatusCode::UNAUTHORIZED,
129        title: "Admin access unauthorized",
130        description: "Admin endpoints are disabled or the request has no bearer token.",
131    },
132    api_key_unauthorized: ProblemDef {
133        slug: "api-key-unauthorized",
134        status: StatusCode::UNAUTHORIZED,
135        title: "API key unauthorized",
136        description: "The request's API key was missing, malformed, unknown, revoked or invalid; one uniform answer for all five.",
137    },
138    api_key_forbidden: ProblemDef {
139        slug: "api-key-forbidden",
140        status: StatusCode::FORBIDDEN,
141        title: "API key lacks the required scope",
142        description: "The presented API key is valid but does not carry the scope this route requires.",
143    },
144    admin_forbidden: ProblemDef {
145        slug: "admin-forbidden",
146        status: StatusCode::FORBIDDEN,
147        title: "Admin token rejected",
148        description: "The presented admin token is wrong.",
149    },
150    sidecar_unauthorized: ProblemDef {
151        slug: "sidecar-unauthorized",
152        status: StatusCode::UNAUTHORIZED,
153        title: "Unauthorized sidecar caller",
154        description: "A request to a sidecar-guarded route could not be established as coming from the trusted gateway.",
155    },
156    not_production_ready: ProblemDef {
157        slug: "not-production-ready",
158        status: StatusCode::SERVICE_UNAVAILABLE,
159        title: "Not ready for production traffic",
160        description: "This deployment declares production but cannot satisfy the abuse controls its routes declare.",
161    },
162    request_too_large: ProblemDef {
163        slug: "request-too-large",
164        status: StatusCode::PAYLOAD_TOO_LARGE,
165        title: "Request body too large",
166        description: "The request body exceeded the 64 KiB limit for /v1 endpoints.",
167    },
168    rate_limited: ProblemDef {
169        slug: "rate-limited",
170        status: StatusCode::TOO_MANY_REQUESTS,
171        title: "Rate limit exceeded",
172        description: "Too many requests from this IP or address; retry after the pause.",
173    },
174    allowance_exhausted: ProblemDef {
175        slug: "usage/allowance-exhausted",
176        status: StatusCode::TOO_MANY_REQUESTS,
177        title: "Usage allowance exhausted",
178        description: "The subject's metered allowance is spent for the current period; retry after the period resets.",
179    },
180    not_found: ProblemDef {
181        slug: "not-found",
182        status: StatusCode::NOT_FOUND,
183        title: "Not found",
184        description: "No route matched the request.",
185    },
186    unknown_tenant: ProblemDef {
187        slug: "unknown-tenant",
188        status: StatusCode::NOT_FOUND,
189        title: "Unknown tenant",
190        description: "The request's host resolves to no tenant in the registry.",
191    },
192    internal: ProblemDef {
193        slug: "internal",
194        status: StatusCode::INTERNAL_SERVER_ERROR,
195        title: "Internal error",
196        description: "Unhandled error; no internals are exposed in the body.",
197    },
198    mail_not_configured: ProblemDef {
199        slug: "mail-not-configured",
200        status: StatusCode::SERVICE_UNAVAILABLE,
201        title: "Mail is not configured",
202        description: "No sending domain is verified; use the direct address shown by the form.",
203    },
204    not_ready: ProblemDef {
205        slug: "not-ready",
206        status: StatusCode::SERVICE_UNAVAILABLE,
207        title: "Service not ready",
208        description: "Readiness probe failed: the database is missing, erroring or too slow.",
209    },
210    sidecar_unavailable: ProblemDef {
211        slug: "sidecar-unavailable",
212        status: StatusCode::SERVICE_UNAVAILABLE,
213        title: "Sidecar module unavailable",
214        description: "A sidecar-mounted module could not be reached; other modules are unaffected.",
215    },
216    sidecar_contract_mismatch: ProblemDef {
217        slug: "sidecar-contract-mismatch",
218        status: StatusCode::SERVICE_UNAVAILABLE,
219        title: "Sidecar contract mismatch",
220        description: "A sidecar answers a different HARNESS_API than this harness speaks.",
221    },
222    tenant_degraded: ProblemDef {
223        slug: "tenant-degraded",
224        status: StatusCode::SERVICE_UNAVAILABLE,
225        title: "Tenant is degraded",
226        description: "The tenant's schema is behind or its database is unreachable; its neighbours are unaffected.",
227    },
228};
229
230/// Every core slug definition, for tests and docs.
231pub fn registry() -> Vec<&'static ProblemDef> {
232    vec![
233        &SLUGS.validation_failed,
234        &SLUGS.captcha_failed,
235        &SLUGS.invalid_token,
236        &SLUGS.unknown_product,
237        &SLUGS.unauthenticated,
238        &SLUGS.admin_unauthorized,
239        &SLUGS.api_key_unauthorized,
240        &SLUGS.api_key_forbidden,
241        &SLUGS.admin_forbidden,
242        &SLUGS.sidecar_unauthorized,
243        &SLUGS.not_production_ready,
244        &SLUGS.request_too_large,
245        &SLUGS.rate_limited,
246        &SLUGS.allowance_exhausted,
247        &SLUGS.not_found,
248        &SLUGS.unknown_tenant,
249        &SLUGS.internal,
250        &SLUGS.mail_not_configured,
251        &SLUGS.not_ready,
252        &SLUGS.sidecar_unavailable,
253        &SLUGS.sidecar_contract_mismatch,
254        &SLUGS.tenant_degraded,
255    ]
256}